taslamの日記

>>mizincogrammerに移転しました。こちらは、今後更新されません。<<

2008-08-11

[]Nginxでリバースプロキシ

2つのRailsアプリにリバースプロキシを使ってそれぞれ振り分ける例。

ディレクトリベースで振り分ける例

user              nginx;
worker_processes  4;

error_log         /var/log/nginx/error.log;

pid               /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {

  # Railsアプリの静的コンテンツはmongrel_clusterだと遅いのでもっと高速なサーバで
  upstream static {
    server 192.168.0.12:80;
    server 192.168.0.13:80;
  }

  # Railsアプリ mongrel_cluster
  upstream app1 {
    server 192.168.0.12:3000;
    server 192.168.0.12:3001;
    server 192.168.0.13:3000;
    server 192.168.0.13:3001;
  }
  upstream app2 {
    server 192.168.0.12:3010;
    server 192.168.0.12:3011;
    server 192.168.0.13:3010;
    server 192.168.0.13:3011;
  }

  # 静的ページかCGIだけ apache
  upstream web {
    server 192.168.0.8:80;
  }

  server {
    listen      80;
    server_name .taslam-example.jp;

    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  Host            $http_host;
    proxy_redirect    false;
    proxy_max_temp_file_size          0;

    # 静的コンテンツ
    location ~ ^/(app1|app2)/(javascripts|stylesheets|images)/.* {
      proxy_pass http://static;
      break;
    }

    # アプリケーション1に振り分け
    location /app1 {
      proxy_pass http://app1;
      break;
    }

    # アプリケーション2に振り分け
    location /app2 {
      proxy_pass http://app2;
      break;
    }

    # 上に当てはまらないものはwebサーバに振り分け
    location / {
      proxy_pass http://web;
      break;
    }
  }

}

ドメインベースで振り分ける例

user              nginx;
worker_processes  4;

error_log         /var/log/nginx/error.log;

pid               /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {

  # Railsアプリの静的コンテンツはmongrel_clusterだと遅いのでもっと高速なサーバで
  upstream static {
    server 192.168.0.12:80;
    server 192.168.0.13:80;
  }

  # Railsアプリ mongrel_cluster
  upstream app1 {
    server 192.168.0.12:3000;
    server 192.168.0.12:3001;
    server 192.168.0.13:3000;
    server 192.168.0.13:3001;
  }
  upstream app2 {
    server 192.168.0.12:3010;
    server 192.168.0.12:3011;
    server 192.168.0.13:3010;
    server 192.168.0.13:3011;
  }

  # 静的ページかCGIだけ apache
  upstream web {
    server 192.168.0.8:80;
  }

  # アプリケーション1に振り分け
  server {
    listen      80;
    server_name app1.taslam-example.jp;

    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  Host            $http_host;
    proxy_redirect    false;
    proxy_max_temp_file_size          0;

    # 静的コンテンツ
    location ~ ^/(javascripts|stylesheets|images)/.* {
      proxy_pass http://static;
      break;
    }

    location / {
      proxy_pass http://app1;
      break;
    }
  }

  # アプリケーション2に振り分け
  server {
    listen      80;
    server_name app2.taslam-example.jp;

    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  Host            $http_host;
    proxy_redirect    false;
    proxy_max_temp_file_size          0;

    # 静的コンテンツ
    location ~ ^/(javascripts|stylesheets|images)/.* {
      proxy_pass http://static;
      break;
    }

    location / {
      proxy_pass http://app2;
      break;
    }
  }

  # Webサーバに振り分け
  server {
    listen      80;
    server_name www.taslam-example.jp;

    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  Host            $http_host;
    proxy_redirect    false;
    proxy_max_temp_file_size          0;

    location / {
      proxy_pass http://web;
      break;
    }
  }
}

2008-07-05

毒餃子中国国内では入手困難、混入は日本で」の農薬、やっぱり中国で手に入ることが判明

シラミ発見で頭髪に農薬塗る 8歳女児が死亡

http://news.searchina.ne.jp/disp.cgi?y=2008&d=0703&f=national_0703_018.shtml

重慶市永安村の8歳女児が6月30日、シラミを退治するために髪の毛に農薬メタミドホスを塗ったことが原因で死亡した。7月3日付で重慶晨報が伝えた。

12歳になる姉も同様にメタミドホスを頭髪にまぶしたため病院で手当てを受けた。姉は危険な状態から脱したという。

姉は「大人の女性がシラミ対策のため頭髪に農薬を塗る様子を目撃したことがあるので真似をした」と語った。

メタミドホスってアレだよね、中国毒餃子の。

  • 中国国内では入手はまず無理、日本で混入。
  • 日本人は弱い

とかいっておきながら、なんすか、これ。

マスコミ政府はつっこめよ。

2008-03-31

[]ドコモ、携帯電話の「識別番号」・コンテンツ会社に通知

http://www.nikkei.co.jp/news/main/20080330AT1D280EI29032008.html

 NTTドコモは31日から携帯電話の「識別番号」をコンテンツ会社に通知するサービスを開始する。コンテンツ会社は識別番号を活用し、携帯でサイトを閲覧した履歴などが把握できるようになる。利用者の特性に応じた広告を提供することができるなど、携帯向けのネットサービスを活発にするのが狙い。

 ドコモがコンテンツ会社に情報提供するのは、携帯の電話番号ごとに付与される「iモードID」と呼ばれる識別番号。電話番号とは異なる英数字の組み合わせで構成。「氏名やメールアドレスは含まれておらず、個人情報開示には当たらない」(ドコモ)という。

おーこれで、ドコモでもやっと、「機種変しても変わらない」ユーザ識別情報を(公式サイトでなくても)取得できるわけですね。認証が少し楽になります。

ただ、ここの書き方、記者がよく理解できていないのか変に不安をあおる感じですね。曖昧すぎて。

http://workingnews.blog117.fc2.com/blog-entry-899.html

↑こんなかんじに。

一応書いておくけど、識別情報から「どこの誰か」なんて分かりません。

また、どこのサイトでどんなものを買ったのか、なんてのもわかりません。

趣向がわかるっていうのは、そのサイト内でどんなページをみたのかってのがわかるってだけのことです。

取得してみる

手順は以下の通り

  • GETやPOSTのパラメータとして、「guid=ON」を付加
  • HTTP_X_DCMGUIDヘッダとして7ケタの半角英数字で渡される
実験用CGIスクリプト

こんなかんじので取得できた。

#!/usr/bin/ruby

require 'cgi'
cgi = CGI.new("html4")

cgi.out() do
  cgi.body() do
    <<-HTML
<html>
  <body>
    <a href="?guid=ON">取得</a><br />
    あなたのGUIDは「#{ENV["HTTP_X_DCMGUID"]}」です。
  </body>
</html>
HTML
  end
end

問題点

ゲートウェイがリクエストの内容を見て、ヘッダを付加するかたちで実装されているため、暗号化通信時にGUIDを取得することは不可能。(ゲートウェイが内容を見れないから)

追記

詳しく調べてくれてる人がいた。

urekatのスカンク日記3

2008-02-07

[][]認証APIいろいろをRubyで使う

はてな認証API

http://auth.hatena.ne.jp/

Rubyではてな

Hatena::API::AuthのRuby版をid:secondlifeさんが公開してくれてる。

gem install hatenaapiauth
require 'rubygems'
require 'hatena/api/auth'
require 'cgi'

cgi = CGI.new

params = {
  :api_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}
auth = Hatena::API::Auth.new(params)
begin
  hatena = auth.login(cgi['cert'])
  # hatena = {
  #   "name" => "taslam",
  #   "image_url" => "http://www.hatena.ne.jp/users/ta/taslam/profile.gif",
  #   "thumbnail_url" => "http://www.hatena.ne.jp/users/ta/taslam/profile_s.gif"
  # }
rescue Hatena::API::AuthError => e
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクトではてなの認証ページに。
end

livedoor Auth

http://auth.livedoor.com/

Rubyでlivedoor Auth
gem install livedoorauth
require 'rubygems'
require 'livedoorauth'
require 'cgi'

params = {
  :app_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}

cgi = CGI.new
auth = Livedoor::API::Auth.new(params)
begin
  user = auth.validate_response(cgi.query_string)
  # user = {
  #   :userhash => 'ユーザを識別するアプリ毎に一意な文字列',
  #   :token => 'LivedoorID取得に用いるtoken',
  #   :userdata => '認証URL生成時に指定した引継ぎデータ',
  # }

  # 認証成功
  # URL生成時に:perms => 'id'を指定していればLivedoorIDを取得可能。
  # livedoor_id = auth.get_livedoor_id(user)
rescue
  # 認証失敗
  # ログイン用URIインスタンス取得
  # Livedoorへのログイン、アプリケーションの認証情報利用の許可など
  # 認証できたらあらかじめ設定しているコールバックURLにリダイレクト要求発生
  # userdataはそのままコールバックURLに付加されて帰ってくる
  # :permsのデフォルトはuserhash。idにすると、LivedoorIDを取得できるようになる。
  login_uri = auth.uri_to_login(:userdata => 'data')
  # login_uri にリダイレクトで、Livedoorに認証ページに。
end

YahooBBAuth

http://developer.yahoo.com/auth/

Yahoo!Japanのほうは、現在のところ一般公開はしていない模様(´・ω・`)

Google

GoogleAccountAuthenticationを使う。

http://code.google.com/apis/accounts/AuthForWebApps.html

RubyでGoogleAccountAuthentication

GoogleCalendarサービスでの認証を利用する。

今回はライブラリがあったので、利用させていただいた。

gem install gcalapi
begin
  GoogleCalendar::Service.new('taslam@example.com', 'password')
  # カレンダー取得メソッド内で認証してるので利用。
  # 直接認証メソッドを叩くなら、srv.instance_eval('auth')
  srv.calendars
rescue GoogleCalendar::AuthenticationFailed => e
  # 認証失敗
end

Flickr

http://flickr.com/services/api/auth.howto.web.html

RubyでFlickr認証

認証のための、使い易いライブラリが見つからなかったので簡単につくってみた。

flickr/api/auth.rb

require 'digest/md5'
require 'uri'
require 'open-uri'
require 'rexml/document'

module Flickr

  module API

    class AuthError < RuntimeError;end

    class Auth

      def initialize(params)
        @api_key = params[:api_key].to_s
        @secret = params[:secret].to_s
      end

      def login(frob)
        return nil if frob.to_s.empty?
        uri = service_uri('rest')
        params = {
          :api_key => @api_key,
          :method => 'flickr.auth.getToken',
          :frob => frob.to_s
        }
        uri.query = params.merge!(:api_sig => sigunature(params)).collect { |key, value| "#{key}=#{value}"}.join('&')
        doc = REXML::Document.new(uri.read)
        rsp = doc.elements['//rsp']
        if rsp.attributes['stat'] == 'fail'
          raise AuthError.new(rsp.elements['err'].attributes['msg'])
        end
        auth = doc.elements['//auth']
        {
          :token => auth.elements['token'].text,
          :perms => auth.elements['perms'].text,
          :user => {
            :nsid => auth.elements['user'].attributes['nsid'],
            :username => auth.elements['user'].attributes['username'],
            :fullname => auth.elements['user'].attributes['fullname'],
          }
        }
      end

      def uri_to_login(perm = 'read')
        uri = service_uri('auth')
        params = {
          :api_key => @api_key,
          :perms => perm
        }
        uri.query = params.merge!(:api_sig => sigunature(params)).collect { |key, value| "#{key}=#{value}"}.join('&')
        uri
      end

      private
      def service_uri(path = 'auth')
        URI.join('http://flickr.com/services/', path)
      end

      def sigunature(params)
        Digest::MD5.hexdigest(@secret+params.sort_by{|key, value| key.to_s }.flatten.join).to_s
      end

    end

  end

end

こんなかんじに使う。

require 'flickr/api/auth'
require 'cgi'

params = {
  :api_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  :secret=>"xxxxxxxxxxxxxxxx"
}

cgi = CGI.new

auth = Flickr::API::Auth.new(params)
if info = auth.login(cgi['frob'])
  # 認証成功
  # infoの例
  # info => {
  #   :user => {
  #     :nsid=>"000000@N00",
  #     :username=>"taslam",
  #     :fullname=>""
  #   },
  #   :token=>"72157603867040929-8ffc565113df0adc",
  #   :perms=>"read"
  # }
else
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクト要求
end

Jugem

http://jugemkey.jp/api/auth/

RubyでJugem認証

llameradaさんがライブラリを公開してくれてるのでそれを使わせていただく。

require 'jugem/auth'
require 'cgi'

params = {
  :api_key => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  :secret => "xxxxxxxxxxxxxxxx",
  :callback_url => "登録したURLを含むコールバック先。この例ではこのCGIのURL。",
  :perms => "read"
}

cgi = CGI.new
auth = Jugem::Auth.new(params)

begin
  jugem = auth.login(cgi['frob'].to_s)
  # 認証
rescue  Jugem::AuthError => e
  # 認証失敗
  # auth.uri_to_login.to_s にリダイレクト
end

OpenID

http://openid.net/

RubyでOpenID

ruby-openidライブラリを使う。OpenID2.0にも対応しているので、Yahoo!のでも大丈夫。

gem install ruby-openid

immediateを使わない場合のサンプル。普通はこれでいいと思います。

Yahoo!でOpenIDつくってるひとは、identifierは「yahoo.co.jp」だけで良いです。

openid.cgi

#!/usr/bin/ruby
require 'rubygems'
require 'openid'
require 'openid/store/filesystem'
require 'cgi'
require 'cgi/session'
require 'cgi/session/pstore'

@cgi = CGI.new
session = CGI::Session.new(@cgi,'database_manager' => CGI::Session::PStore)

def render(text)
  @cgi.out do
    text
  end
end

def redirect_to(url)
  print @cgi.header({'status' => '302 Found', 'Location' => url })
end

store = OpenID::Store::Filesystem.new('鍵交換データの保存先')
consumer = OpenID::Consumer.new(session, store)
if session[:verified_identifier]
  # ログイン済み
  render "#{session[:verified_identifier]}でログイン済み"
else
  if !@cgi['openid.mode'].empty?
    #認証結果をコールバックでもらった
    params = @cgi.params.clone
    params.each { |k, v| params[k] = v[0] }
    oid_res = consumer.complete(params, 'コールバックURL(自身のURL)')
    case oid_res.status
    when OpenID::Consumer::FAILURE
      # 認証に失敗した。メッセージがoid_res.messageで取得可能。
      render oid_res.message
    when OpenID::Consumer::SUCCESS
      session[:verified_identifier] = oid_res.display_identifier
      redirect_to 'ログイン後のページのURL'
    when OpenID::Consumer::CANCEL
      # 認証をユーザがキャンセルした
      render "キャンセルされました"
    end
  elsif !@cgi['openid_identifier'].empty?
    # フォームにIdentifier(URL)が入力されたので、認証処理を開始する

    # @cgi['openid_identifier']では以下の警告が出て動かない。
    # CAUTION! cgi['key'] == cgi.params['key'][0]; if want Array, use cgi.params['key']
    identifier = @cgi.params['openid_identifier'][0]
    oid_req = consumer.begin(identifier)
    # コンシューマ識別用URLはプロバイダの認証画面で表示されるURLっぽい。
    # 注意としては、コールバックURLはコンシューマ識別用URLが部分一致しなければならないこと。
    redirect_to oid_req.redirect_url('コンシューマ識別用URL', 'コールバックURL')
  else
    # フォーム表示
    render <<-FORM
<h1>OpenID サンプル</h1>
<form method="post">
  <input id="openid_identifier" name="openid_identifier" type="text" value="" />
  <input name="commit" type="submit" value="認証" />
</form>
FORM
  end
end

mixi

少なくとも一般公開されてる認証APIはいまのところなさげ。やるならMixiStationの足跡APIのハックとなりそう。

まちゅダイアリーで紹介されているように、ユーザーから受け取ったアカウントと、パスワード(のwsse:PasswordDigest)を使ってAPIを叩いて認証に成功するかどうか確認する。

Rubyでmixi認証
//wsse.jsを使わせていただく。
//http://rvr.typepad.com/wind/2005/07/wsse_for_javasc.html
function sendWSSE(form, requestURL) {
  var username = null;
  var password = null;
  var elements = form.getElements();
  elements.each(function(element, index) {
    if(element.getAttribute('name') == 'email') username = element.value;
    if(element.getAttribute('name') == 'password') password = element.value;
  });
    
  var wsseForm = document.getElementById('wsseForm') || document.createElement('form');
  wsseForm.setAttribute('id', 'wsseForm');
  wsseForm.action = requestURL;
  wsseForm.method = 'POST';
  var wsseField = document.getElementById('wsseField') || document.createElement('input');
  wsseField.setAttribute('id', 'wsseField');
  wsseField.name = 'wsse';
  wsseField.type = 'hidden';
  wsseField.value = wsseHeader(username, password);
  document.body.appendChild(wsseForm);
  wsseForm.appendChild(wsseField);

  wsseForm.submit();

  return false;
}
<form onsubmit="return sendWSSE(this, 'mixi.cgi');">
  mixi<br />
  e-mail:<input type="text" name="email" value="" /><br />
  password:<input type="password" name="password" value="" /><br />
  <input type="submit" value="ログイン" />
</form>
require 'cgi'
require 'open-uri'

def mixi_login(wsse)
  open('http://mixi.jp/atom/tracks', 'X-WSSE' => wsse)
  true
rescue  OpenURI::HTTPError => e
  false
end

@cgi = CGI.new
mixi_login(@cgi['wsse']) # => 認証成功したらtrue

2007-11-12

[][]Railsでなくとも使ってみる

RailsでARに慣れるとDBIなどでのアクセスはストレスです。

楽をしたい。

RailsじゃなくてもARを使いたい。

そこで使ってみました。今更ですが。

ちなみにActiveRecordはMITライセンスです。

CGIで使いたいなーという状況。

共用のレンタルサーバなんかでgemなんてあてにできません。

ここでは必要なファイルはすべてlib以下に入れておきます。

$ ls lib
active_record  active_record.rb  active_support  active_support.rb
$APPLICATION_ROOT = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift File.join($APPLICATION_ROOT, './lib')

# 接続
# MySQL
ActiveRecord::Base.establish_connection(:adapter => "mysql",
                                        :host => "localhost",
                                        :username => "user",
                                        :password => "password",
                                        :encoding => "utf8",
                                        :database => "db_test")
# PostgreSQL
ActiveRecord::Base.establish_connection(:adapter => "postgresql",
                                        :host => "localhost",
                                        :username => "user",
                                        :password => "password",
                                        :encoding => "utf8",
                                        :database => "db_test")
# SQLite3
# :dbfile => ':memory:'とすると実ファイルを作らずメモリ内で。
# 実行を終えると消えちゃう。テストに良さそう。
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3',
                                        :dbfile => 'db_file')

# あとはいつもどおりに
class User < ActiveRecord::Base
end

user = User.create(:name => 'lam')

Migrationを使いたい場合は、

require 'logger'
ActiveRecord::Base.logger = Logger.new(File.join($APPLICATION_ROOT, 'log/debug.log'))
# ActiveRecord::Base.logger.level = Logger::ERROR

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string, :nil => false
    end
  end
end

# Migration実行
CreateUsers.migrate(:up)

という風に。

また、Railsのように番号で管理して、希望のバージョンに戻したりいろいろしたいなら、

# migrate/001_create_boards.rb
class CreateBoards
  def self.up
    create_table :boards do |t|
      t.column :name, :string
    end
  end

  def self.down
    drop_table :boards
  end
end

# migrate/002_create_entries.rb
class CreateEntries
  def self.up
    create_table :entries do |t|
      t.column :title, :string
      t.column :body,  :string
    end
  end

  def self.down
    drop_table :entries
  end
end

# ActiveRecord::Migrator.migrate(
#   Migration用のコードのディレクトリ,
#   希望のバージョン。省略するかnilで最新)
ActiveRecord::Migrator.migrate("migrate", 2)

という具合。


ただ、生CGIで使うには頭の痛い問題もあって・・・

require 'benchmark'
Benchmark.bm do |x|
  x.report{ require 'active_record' }
  x.report {
    ActiveRecord::Base.establish_connection(
      :adapter => 'mysql',
      :host => 'localhsot',
      :username => 'root',
      :password => '',
      :encoding => 'utf8',
      :database => 'cookbook')
  }
end

で、

      user     system      total        real
  0.280000   0.700000   0.980000 (  1.142170)
  0.000000   0.000000   0.000000 (  0.000002)

requireだけでこれは・・・