yukata

日々出会ったIT技術関連の所感やら紹介やら

PUSHERの利用に認証を含める

前回の記事の内容に、認証処理を含めてみます。

以下のサイトを参考にしました。認証シーケンスや、使い方が書かれています。
http://pusher.com/docs/authenticating_users

細かい部分を省いてポイントだけシンプルに書きます。

websocketを利用するブラウザへ送信するJavascriptを修正

・Pusher.channel_auth_endpointを追記
・チャネル名のプレフィックスに「private-」を追加

・・・
  Pusher.channel_auth_endpoint = 'pusher_auth'; //←認証ページへのパスを書く。
  var pusher = new Pusher('********'); // ←API取得時に控えたAPI keyを書く。
  var channel = pusher.subscribe('private-channel');
・・・


認証用のページを作成

  if (認証OKの場合) {
    $pusher = new Pusher($key, $secret, $app_id);
    echo $pusher->socket_auth($_POST['channel_name'], $_POST['socket_id']);
  } else {
    header('', true, 403);
    echo "Forbidden";
  }


これだけで、認証が成功した場合はwebsocketのコネクションが生成され、pushリクエストを送ったときに前回のようにポップアップが表示されます。

認証に失敗した場合は接続されません。

PHP+PUSHERを利用してWebsocket通信を行う

現在、とあるWebアプリケーションの開発の中で、ブラウザ間のリアルタイム同期が必要となりました。Websocketを使う場合、サーバを用意するのが面倒だと思っていたら、PUSHERというクラウドサービスがあることを知って、検討するためにちょっと使ってみましたので以下に手順を書いていきます。

次の構成を想定します。

f:id:maehira:20120929232901p:plain

手順1 PUSHERの登録

まずは以下のURLからアカウント登録をします。
http://pusher.com/

f:id:maehira:20120929215520p:plain

登録後、入力したメールアドレスに確認用のメールアドレスが送信されますので、送られてきたURLにアクセスし、登録を完了させます。

手順2 PUSHERへアクセスするためのライブラリを取得

http://pusher.com/docs/server_libraries へアクセスし、PUSHERへAPIリクエストを行うためのライブラリを取得します。各言語のものがあります。

今回はPHPを利用しますので、以下を入れました。
https://github.com/pusher/pusher-php-server

手順3 websocketのコネクションを生成するページを作成(ブラウザAのアクセス用)

  <script src="http://js.pusher.com/1.11/pusher.min.js" type="text/javascript"></script>
  <script type="text/javascript">
    Pusher.log = function(message) {
      if (window.console && window.console.log) window.console.log(message);
    };

    WEB_SOCKET_DEBUG = true;

    var pusher = new Pusher('********'); // API Key
    var channel = pusher.subscribe('test_channel');
    channel.bind('test_event', function(data) {
      alert(data);
    });
  </script>
  <p>Browser A</p>

手順4 Push通信を要求するためのページを作成(ブラウザBのアクセス用)

  // 各キー等は、PUSHERのサイトの「API access」タブに記載されています
  $pusher = new Pusher($key, $secret, $app_id);
  $pusher->trigger('test_channel', 'test_event', 'hello pussher!!');
  printf("trigger called.");

手順5 動作確認

ブラウザを2つ立ち上げて、動作確認を行います。
片方のブラウザ(ブラウザA)で、手順3で作成したページへアクセスし、もう片方のブラウザ(ブラウザB)で、手順4で作成したページへアクセスすると、ブラウザAにポップアップが表示されます。


以上。簡単ですが、今回はここまでです。

XAMPPでApacheを起動して、http://localhost にアクセスしてからXAMPPのトップページが表示されるまで

WindowsでPHPをさわってみるためにXAMPPを入れてみました。http://localhostにアクセスするとまずXAMPPのページが表示されますが、これが表示されるまでの流れを書いておきます。Apacheをさわることがあまりないので、色々思い出すいい機会になりました。

もちろん、パスは違いますがLinuxもWindowsもMacも基本は同じです。ちなみにxamppは、Cドライブ直下にインストールしています。

まずはApacheの設定(C:\xampp\apache\conf\httpd.conf)で、

<IfModule dir_module>
	DirectoryIndex index.php index.pl index.cgi index.asp index.shtml index.html index.htm \
               	default.php default.pl default.cgi default.asp default.shtml default.html default.htm \
               	home.php home.pl home.cgi home.asp home.shtml home.html home.htm
</IfModule>

となっているため、C:\xampp\htdocs\index.php が呼び出されます。

そして、C:\xampp\htdocs\index.php 内に書かれている、

header('Location: '.$uri.'/xampp/');

により、http://localhost/xampp/ にリダイレクトされます。

リダイレクト後は先ほどと同様の原理で、C:\xampp\htdocs\xampp\index.php が呼び出されます。

C:\xampp\htdocs\xampp\index.php 内ではbodyタグ内にはなにも記載されていませんが、headタグ内に、「frameset」というタグで、head.phpとnavi.php、start.phpが呼び出されて表示されます。これらphpで各コンテンツが表示されます。(framesetタグは、HTML5では廃止予定らしい。iframeとかに置き換えろと。)

C:\xampp\htdocs\xampp\index.php の内容は以下です。

<html>
<head>
<meta name="author" content="Kai Oswald Seidler">
<meta http-equiv="cache-control" content="no-cache">
<?php include("lang/".file_get_contents("lang.tmp").".php"); ?>
<title>XAMPP <?php include('.version');?></title>

<frameset rows="74,*" marginwidth="0" marginheight="0" frameborder="0" border="0" borderwidth="0">
	<frame name="head" src="head.php" scrolling=no>
<frameset cols="150,*" marginwidth="0" marginheight="0" frameborder="0" border="0" borderwidth="0">
	<frame name="navi" src="navi.php" scrolling=no>
	<frame name="content" src="start.php" marginwidth=20>
</frameset>
</frameset>
</head>
<body bgcolor=#ffffff>
</body>
</html>

この内容がXAMMPのトップページとなります。

rails3.2.8の起動を追いかける その2

前回の続き

それでは、sampleアプリケーション内の、「sample/script/rails」ファイルで読み込まれた、「rails/commands」以降の内容を確認していきたいと思います。

rails/commands」の中身

「/Library/Ruby/Gems/1.8/gems/railties-3.2.8/lib/rails/commands.rb 」

ちょっと長くなってしまうので、実行される部分のみ抜粋しました。また、行数が追いにくくなってしまうので2つにわけています。

require 'active_support/core_ext/object/inclusion'

ARGV << '--help' if ARGV.empty?

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner"
}

command = ARGV.shift
command = aliases[command] || command
1行目:

ActiveSupport関連の読み込みですね。
ActiveSupportは、Rubyに便利な機能を付け加えるものです。Railsだけをやっている人は、ActiveSupportの機能なのかRubyの機能なのかわからずに使っている機能も多いかと思います。例えば、"blank?"っていうメソッドをよく使いますが、これはActiveSupportが提供するもので、irbとかでいきなり実行しても使えません。
これに関しては今後の記事で取り上げたいと思います。ここでは読み飛ばします。

3行目:

引数がなかった場合"--help"を付けたのと同じようにしてくれるんですね!親切ですね!!

5~15行目:

引数の別名をハッシュで持っておき、変数"command"に対応する正式名を格納しています。今回の引数は"s"なので、"server"が"command"に格納されます。

case command
  # ~省略~
when 'server'
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless
File.exists?(File.expand_path("config.ru"))

  require 'rails/commands/server'
  Rails::Server.new.tap { |server|
    require APP_PATH
    Dir.chdir(Rails.application.root)
    server.start
  }

  # ~省略~
end
1行目:

先ほどの変数"command"に格納された値は、"server"なので、上記コードの部分が実行されます。

4、5行目:

"config.ru"が存在しなければ、"APP_PATH"に格納されてあるディレクトリへ移動します。"config.ru"は存在しますので、これは実行されません。
ちなみに"APP_PATH"には、前回の記事で説明した箇所で、"config/application"が格納されています。

7、8行目:

"::Rack::Server"クラスを継承する、"Rails::Server"クラスを読み込みます。
"Rails::Server"クラスをインスタンス化して、"tap"メソッドにブロックを指定して実行しています。
"tap"メソッドは、Objectクラスのインスタンスメソッド(拡張)で、受け取ったブロックに自身のインスタンスを引数として渡して実行するだけです。

9~11行目:

"APP_PATH"、つまり「sample/config/application.rb 」を読み込み、railsアプリケーションのルートディレクトリへ移動しています。
そして、"server.start"が実行されます。


"server.start"で実行されるコード

"::Rack::Server"の持っている"start"メソッドを、"Rails::Server"クラスがオーバーライドしていますので、"Rails::Server"クラスの"start"メソッドが実行されます。
以下、「/Library/Ruby/Gems/1.8/gems/railties-3.2.8/lib/rails/commands/server.rb」のstartメソッド

def start
  url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
  puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
  puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
  puts "=> Call with -d to detach" unless options[:daemonize]
  trap(:INT) { exit }
  puts "=> Ctrl-C to shutdown server" unless options[:daemonize]

  #Create required tmp directories if not found
  %w(cache pids sessions sockets).each do |dir_to_make|
    FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
  end

  super
ensure
  # The '-h' option calls exit before @options is set.
  # If we call 'options' with it unset, we get double help banners.
  puts 'Exiting' unless @options && options[:daemonize]
end
2~7行目:

SSLが有効であれば"https://host:port"、有効でなければ"http://host:prot"を変数"url"へ格納します。
そして、railsの起動情報を標準出力へ表示します。"Ctrl-C"でのサーバ停止も設定されています。

10~12行目:

Railsのルートディレクトリ直下の"tmp"ディレクトリ(sample/tmp)に、"cache", "pids", "sessions", "sockets"ディレクトリを作成します。

14行目:

"super"メソッドが最後に実行され、親クラスである"::Rack::Server"の"start"メソッドが実行されます。ここで実行されるstartメソッドにより、Rackの仕様に従ってWebサーバが起動します。今回はオプションを指定していませんので、Webrickサーバが起動します。
Rackとは、Webアプリケーションサーバとアプリケーションフレームワーク間のインターフェスの仕様で、以下の決まりがあります。

  • 「call」というメソッドを実装している
  • 「call」メソッドの引数としてWebサーバからのリクエストを受け取る
  • 「call」メソッドは,下記をレスポンスとして返す


上記を満たしていることによって、ユーザからのリクエストを受け付けたときに、Rackの仕様でHTTPリクエストを受け取り、インスタンス毎にRailsで書いたコードの処理が行われ、ユーザへHTTPレスポンスを返します。

細かいところはだいぶ省きましたが、だいたいの流れはこんな感じだと思います。今後はもう少し細かい部分を個別に追っていきたいと思います。

rails3.2.8の起動を追いかける

プログラムコードを、スーパーpre記法できれいに書けたものの、行数をつける方法がわからない・・・とりあえず一旦諦めます。。orz


さて、本題。

railsアプリケーションの起動中、どんな流れでプログラムが実行されているのかを追ってみました。Railsのバージョンは3.2.8です。

下記のように実行したあと、サーバの起動が完了するまでの流れをざっくりと追いかけて行きたいと思います。

$ rails new sample
$ cd sample
$ rails s


rubyrails等のインストールディレクトリは環境によって異なりますので、適宜読み替えて下さい。私の環境は、Mac OS X 10.7.4で以下のようになっています。

rubyコマンド /usr/bin/ruby
railsコマンド /usr/bin/rails
gemコマンド /usr/bin/gem
gemライブラリ /Library/Ruby/Gems/1.8/gems


まずは、最初に実行するrailsコマンドの中身

/usr/bin/rails

require 'rubygems'

version = ">= 0"

if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
  version = $1
  ARGV.shift
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)
1行目:

"rubygems"を読み込み。読み込まれるファイルは、「/Library/Ruby/Site/1.8/rubygems.rb」
この「rubygems.rb」の中で色々な定義やファイルが読み込まれますが、とりあえず読み飛ばします。

3行目:

使用するrailsのデフォルトバージョン。

5~8行目:

・引数の最初が"_xxx_"のような形式(文字列の両端がアンダースコア)かどうかを判断
・上記形式にマッチした場合、"$1"にマッチした文字列(アンダースコアの間の部分のみ)が格納される
・上記且つ、バージョンの記述形式にマッチしていた場合、if文の中身が実行される
 (バージョン記述形式はこのファイルを参照:「/Library/Ruby/Site/1.8/rubygems/version.rb 」)
・ifの中身は、"$1"に格納されたバージョン情報を、"version"へ代入し、引数のリスト"ARGV"をshiftしてるだけ。

10行目:

指定されたバージョンのrailtiesを読み込み。
railtiesは、Railsのブートプロセス、アプリケーションひな形作成等、Railsに必要な周辺ライブラリ関係が詰め込まれている。
railtiesも話が広がりすぎるので、一旦読み飛ばします。

11行目:

指定したバージョンのrailsコマンドファイルが読み込まれます。
また、このrailsコマンドファイルの読み込みに、引数"s"を引き継ぎます。
ちなみに「Gem.bin_path('railties', 'rails', version)」の実行結果は、以下のようになります。

$ irb
>> Gem.bin_path('railties', 'rails', "3.2.8")
=> "/Library/Ruby/Gems/1.8/gems/railties-3.2.8/bin/rails"


上記ソースコードの末尾でloadしたrailsコマンドファイルの中身

/Library/Ruby/Gems/1.8/gems/railties-3.2.8/bin/rails

if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"
1~4行目:

railtiesがGit管理であれば、railtiesのlibをロードパスに追加(すみません、なぜかよくわかりません・・・)
"$:"は、rubyの組み込み変数で、ロードパスが格納されています。("$LOAD_PATH"と同じ)
これは"load"や"require"がファイルをロードする時に検索するディレクトリのリストです。

5行目:

「/Library/Ruby/Gems/1.8/gems/railties-3.2.8/lib/rails/cli.rb」を読み込み。


cli.rbの中身

require 'rbconfig'
require 'rails/script_rails_loader'

Rails::ScriptRailsLoader.exec_script_rails!

require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }

if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin_new'
else
  require 'rails/commands/application'
end
1〜2行目:

必要なライブラリの読み込み。"rbconfig"は、「Rubyインタプリタ作成時に設定された情報を格納したライブラリ」らしい・・・

4行目:

script/railsスクリプトを、外部コマンドとして実行します。
あとで"exec_script_rails!"メソッドでなにをしているのか確認してみます。

7行目:

「Ctrl+C」でアプリケーションが終了するようにシグナルを登録しています。

9〜14行目:

引数は"plugin"ではなく"s"なので、"rails/commands/application"ライブラリを読み込みます。

以下、"exec_script_rails!"メソッドの中身です。

def self.exec_script_rails!
  cwd = Dir.pwd
  return unless in_rails_application? || in_rails_application_subdirectory?
  exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
  Dir.chdir("..") do
    exec_script_rails! unless cwd == Dir.pwd
  end
rescue SystemCallError
    # could not chdir, no problem just return
end

3行目、及び4行目のif、5~7行目の再帰文では、カレントディレクトリがrailsアプリケーションディレクトリ内であるかどうかを確認しているだけです。
カレントディレクトリがrailsアプリケーション内であった場合、4行目で外部コマンド「ruby script/rails s」が実行されます。


sample/script/railsの中身

次に実行されるのは、sampleアプリケーション内にある、「script/rails」ファイルです。

APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'
1行目:

定数APP_PATHに、「config/application.rb」のパスを格納しています。

2行目:

「config/boot.rb」を読み込んでいます。あとで中身を確認します。

3行目:

"rails/commands"の読み込み。詳細は飛ばしたいとことですが、ここでスクリプトの流れが止まってしまっています。"rails/commands"の中で色々やっているのでしょう。

以下、「config/boot.rb」の中身。

require 'rubygems'

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

bundlerでgemファイルを管理している場合、ここでセットアップしてくれるようです。



ちょっと長くなりすぎてしまうので、次回「rails/commands」以降の流れを追っていきたいと思います。

DotCloud上でJenkinsはどこまで使えるか検討してみました

 

以下のような環境を作成しました。

 

f:id:maehira:20120830231033p:plain

 

    • GitHubアカウント登録、及びPHPサンプルアプリのリポジトリ作成
      GitHubの登録方法は割愛します。
      今回は、Jenkinsの動作確認のために、PHPUnitのテストコードが含まれているサンプルアプリを適当に持ってきて、リポジトリに登録しました。
       
    • DotCloudアカウント登録
      DotCloudのアカウント登録は、このサイト等を参考にしてすぐにできると思います。
       
    • dotcloudコマンドのインストール
       
      DotCloudへアプリケーションをデプロイするためのコマンドを、ローカルPCにインストールします。ローカル端末のターミナルを開き、下記コマンドを実行します。

      $ sudo easy_install pip && sudo pip install dotcloud
       
    • DotCloudのAPIキーを取得
      dotcloudコマンドでDotCloudへアクセスするために、認証用のAPIキーが必要です。まず下記URLにアクセスし、APIキーをコピーします。
      https://www.dotcloud.com/settings/

      続いて、ターミナル上で下記コマンドを実行し、APIキーを貼付けます。
      $ dotcloud
      Enter your api key (You can find it at http://www.dotcloud.com/account/settings): xxxxxxxxxxxxxxxxxx
       
    • DotCloudへJenkinsをデプロイ
      GitHubから、DotCloud用のJenkinsを取得

      $ mkdir -p /var/tmp/work //任意の作業用ディレクトリを作成
      $ cd /var/tmp/work
      $ git clone https://github.com/dotcloud/jenkins-on-dotcloud.git
      $ cd jenkins-on-dotcloud
      $ vi dotcloud.yml // dotcloud用のyamlファイルを作成
      jenkins:
          type: custom
          buildscript: jenkins/builder
          ports:
              www: http
          process: ~/run
      

      DotCloudアプリケーションを作成し、Pushする
      $ dotcloud create jenkins
      $ dotcloud push --all jenkins

      この時点で、DotCloud上にデプロイしたJenkinsへアクセスできます。
      http://xxxx.dotcloud.com/
       
    • DotCloudへサンプルPHPアプリをデプロイ
      $ cd sample-php
      $ vi dotcloud.yml
      www:
        type: php
      $ dotcloud push -all jenkins
      

 

    • SSHで接続確認(dotcloudコマンドでSSH
      $ dotcloud ssh jenkins.jenkins  // Jenkinsの環境へSSH
      $ dotcloud ssh jenkins.www  // PHPの環境へSSH
       
    • SSHで接続確認(SSHコマンド)
      独自のポート番号になっているため、まずポート番号を確認します。

      $ dotcloud info jenkins.www
      ・・・省略・・・
      ports:
        name: ssh
          url: ssh://dotcloud@xxxxxx.dotcloud.com:[ポート番号]
      ・・・省略・・・
      
      $ ssh -i ~/.dotcloud/dotcloud.key dotcloud@xxx.dotcloud.com -p [ポート番号]
      
       
    • DotCloudからDotCloudへSSHリモートリモートコマンドができることを確認
      $ dotcloud ssh jenkins.jenkins
      # $SHELL
      dotcloud@...$ mkdir work
      dotcloud@...$ cd work/
      dotcloud@...:~/work$ vi dotcloud.key
      -----BEGIN DSA PRIVATE KEY-----
      〜省略〜
      -----END DSA PRIVATE KEY-----
      dotcloud@...:~/work$ chmod 600 dotcloud.key
      dotcloud@...:~/work$ ssh -i dotcloud.key dotcloud@...dotcloud.com -p [ポート番号] ls
      
       
    • DotCloudからDotCloudへSCPができることも確認
      $ dotcloud ssh jenkins.jenkins // DotCloudへSSH
      dotcloud@...:~$ mkdir work // 作業用ディレクトリを作成
      dotcloud@...:~$ cd work/
      dotcloud@...:~/work$ vi blog.key // SSH用のキーファイルを作成
      -----BEGIN DSA PRIVATE KEY-----
      〜省略〜
      -----END DSA PRIVATE KEY-----
      dotcloud@...:~/work$ touch test.txt // コピー用のサンプルファイルを作成
      dotcloud@...:~/work$ scp -i blog.key -P [ポート番号] test.txt dotcloud@...dotcloud.com:/home/dotcloud

 

   

初投稿、疲れました・・・

まだ使い方に慣れていないので、体裁を整えるのに時間がかかりました。。

ちょっとまとまりがない感じになってしまいましたが、今日はこのくらいで。

 

これから色々試したいと思います。

とりあえず

ビール!!


ではなく、

遅ればせながら、私が出会ったIT技術関連について、気になったものをどんどん書き溜めていこうかと思います。

技術以外についても気になったことがあったら書いてみたりするかもしれません。