Hatena::ブログ(Diary)

urekatのスカンク日記3 このページをアンテナに追加 RSSフィード

2010-02-20

[]GoogleAppEngine/JRuby+RailsscaffoldからTwitterBotまで

Ruby(MRI),Rails2.3.5,Java1.6はインストール済みからスタート

(Java1.5な場合は1.5でもOKかもしれないのでそのままやってみて上手く出来たら教えてください。)

% java -version
java version "1.6.0_03-p3"
Java(TM) SE Runtime Environment (build 1.6.0_03-p3-landonf_19_aug_2008_14_55-b00)
Java HotSpot(TM) Server VM (build 1.6.0_03-p3-landonf_19_aug_2008_14_55-b00, mixed mode)

% ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin8]

% rails -v
Rails 2.3.5

google-appengineのインストール

% sudo gem install google-appengine
Successfully installed appengine-rack-0.0.6
Successfully installed appengine-sdk-1.3.0
Successfully installed appengine-jruby-jars-0.0.6
Successfully installed bundler-0.8.1
Successfully installed appengine-tools-0.0.9
Successfully installed google-appengine-0.0.9
6 gems installed

# バージョン確認
% gem list | grep -E "(appe|bundler)"
appengine-jruby-jars (0.0.6)
appengine-rack (0.0.6)
appengine-sdk (1.3.0)
appengine-tools (0.0.9)
bundler (0.8.1)
google-appengine (0.0.9)

Rails,scaffold

http://gist.github.com/269075

をやります。DataMapperを使いたいならhttp://gist.github.com/268192

% mkdir mybot

% cd mybot

% wget http://appengine-jruby.googlecode.com/hg/demos/rails2/rails2_td_appengine.rb

% ruby rails2_td_appengine.rb
=> Bundling gems
Calculating dependencies...
Updating source: http://gems.rubyforge.org
Caching: actionmailer-2.3.5.gem
Caching: actionpack-2.3.5.gem
Caching: activerecord-2.3.5.gem
Caching: activeresource-2.3.5.gem
Caching: activesupport-2.3.5.gem
Downloading appengine-apis-0.0.12.gem
Caching: appengine-rack-0.0.6.gem
Caching: builder-2.1.2.gem
Caching: i18n-0.3.3.gem
Caching: rack-1.0.1.gem
Caching: rails-2.3.5.gem
Caching: rails_tiny_ds-0.0.2.gem
Caching: rake-0.8.7.gem
Caching: tiny_ds-0.0.2.gem
Caching: tzinfo-0.3.16.gem
Installing builder (2.1.2)
Installing activesupport (2.3.5)
Installing i18n (0.3.3)
Installing actionmailer (2.3.5)
Installing rack (1.0.1)
Installing actionpack (2.3.5)
Installing appengine-rack (0.0.6)
Installing appengine-apis (0.0.12)
Installing tiny_ds (0.0.2)
Installing rake (0.8.7)
Installing activerecord (2.3.5)
Installing activeresource (2.3.5)
Installing rails (2.3.5)
Installing rails_tiny_ds (0.0.2)
Installing tzinfo (0.3.16)
Done.
=> Packaging gems
=> Installing JRuby
=> Installing JRuby-Rack
=> Installing appengine-sdk
=> Generating configuration files
      exists  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  config/initializers
      create  config/locales
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/performance
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  Rakefile
      create  README
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  config/database.yml
      create  config/routes.rb
      create  config/locales/en.yml
      create  db/seeds.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_rails_defaults.rb
      create  config/initializers/session_store.rb
      create  config/environment.rb
      create  config/boot.rb
      create  config/environments/production.rb
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  script/about
      create  script/console
      create  script/dbconsole
      create  script/destroy
      create  script/generate
      create  script/runner
      create  script/server
      create  script/plugin
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  test/test_helper.rb
      create  test/performance/browsing_test.rb
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/index.html
   identical  public/favicon.ico
      create  public/robots.txt
      create  public/images/rails.png
      create  public/javascripts/prototype.js
      create  public/javascripts/effects.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  doc/README_FOR_APP
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
+ ./LICENSE
+ ./README
+ ./Rakefile
+ ./init.rb
+ ./lib/active_record/connection_adapters/nulldb_adapter.rb
+ ./lib/nulldb_rspec.rb
+ ./spec/nulldb_spec.rb
+ ./tasks/database.rake
##
## Now type 'dev_appserver.rb .'
##

% dev_appserver.rb .
=> Booting DevAppServer
=> Press Ctrl-C to shutdown server
=> Generating configuration files
SKIP: add_gem_load_paths
SKIP: add_gem_load_paths
SKIP: add_gem_load_paths
["Dir.glob2", ["/Users/takeru/demo/rails-tokyo-48/mybot/app/metal/**/*.rb"], []]
SKIP: add_gem_load_paths
The server is running at http://localhost:8080/

ブラウザhttp://localhost:8080/ を開く。

「About your application’s environment」をクリックして「Ruby version 1.8.7 (java)」等が見れればOK。

# addr/portを指定するには → dev_appserver.rb -a 0.0.0.0 -p 3000 .


scaffoldMRIで実行します。

いくつかMRIrubygemsインストール

% sudo gem install rails -v "2.3.5"
% sudo gem install rails_tiny_ds
% sudo gem install activerecord-nulldb-adapter
./script/generate scaffold article title:string summary:text url:string  pages:integer -f --skip-migration
./script/generate td_model article title:string summary:text url:string  pages:integer -f

# 今回はtimeとlistはいろいろ面倒なのでscaffoldでは無しで。

http://localhost:8080/articles

を開いて確認。

デプロイ

AppEngineでapp-idを取得。

https://appengine.google.com/start/createapp

config.ruの「:application => 'application-id'」の部分を取得したapp-idに書き換える。

% appcfg.rb update .

http://[application-id].appspot.com/

が見れればデプロイ成功。

http://[application-id].appspot.com/articles

Railsの動作を確認。(articlesの方がエラーの場合→この記事の最後を参照)

Twitter Bot

libにsimple-oauth.rbとtwitter_api.rbを入れる。

wget http://github.com/shibason/rb-simple-oauth/raw/master/simple-oauth.rb

1箇所だけ修正。

     request['Authorization'] = auth_header(method, url, request.body)
-    Net::HTTP.new(url.host, url.port).request(request)
+    Net::HTTP.new(url.host, url.port).request(request, request.body)
   end

class TwitterAPI
  # TwitterAPI.new.fetch_friend_ids(:screen_name=>"urekat")
  # TwitterAPI.new.fetch_friend_ids(:id=>5416352)
  def fetch_friend_ids(params)
    request_json(:get, "http://twitter.com/friends/ids.json", params)
  end

  # TwitterAPI.new.fetch_follower_ids(:screen_name=>"urekat")
  # TwitterAPI.new.fetch_follower_ids(:id=>5416352)
  def fetch_follower_ids(params)
    request_json(:get, "http://twitter.com/followers/ids.json", params)
  end

  # TwitterAPI.new.fetch_user(:screen_name=>"urekat")
  # TwitterAPI.new.fetch_user(:id=>5416352)
  def fetch_user(params)
    request_json(:get, "http://twitter.com/users/show.json", params)
  end

  # pp TwitterAPI.new.tweet("hey")
  def tweet(status)
    request_json(:post, 'http://twitter.com/statuses/update.json', {:status=>status})
  end

  def request_json(method, url, params={})
    response = case method
               when :get
                 unless params.blank?
                   url = url + "?" + params.collect{|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join("&")
                 end
                 response = simple_oauth.get(url)
               when :post
                 response = simple_oauth.post(url, params)
               else
                 raise "unknown method #{method}"
               end

    if response.code!="200"
      raise "request failed code=#{response.code} method=#{method} url=#{url}"
    end
    obj = JSON.parse(response.body)
    if obj.kind_of?(Hash) && obj["error"]
      raise "error response: #{obj.inspect}"
    end
    obj
  end

  def simple_oauth
    unless @simple_oauth
      secret = $secret["twitter"]
      @simple_oauth ||= SimpleOAuth.new(secret["CONSUMER_KEY"], secret["CONSUMER_SECRET"], secret["TOKEN"], secret["TOKEN_SECRET"])
    end
    @simple_oauth
  end

  # ruby -rlib/twitter_api.rb -e "TwitterAPI.get_token"
  def self.get_token
    require 'rubygems'
    require 'oauth'
    print "Consumer Key: "
    consumer_key = gets.chomp.strip
    print "Consumer Secret: "
    consumer_secret = gets.chomp.strip
    consumer = OAuth::Consumer.new(consumer_key, consumer_secret, :site => 'http://twitter.com')
    request_token = consumer.get_request_token
    puts "Access this URL and approve => #{request_token.authorize_url}"
    print "Input OAuth Verifier: "
    oauth_verifier = gets.chomp.strip
    access_token = request_token.get_access_token(
      :oauth_verifier => oauth_verifier
    )
    puts "Access token: #{access_token.token}"
    puts "Access token secret: #{access_token.secret}"
  end
end

bot用のtwitterアカウントを取得する。

取得したbotアカウントログインして、twitterアプリ登録をする。

http://twitter.com/apps/new

Application TypeはClientで。

ConsumerKeyとConsumerSecretを次で使います。

% ruby -rlib/twitter_api.rb -e "TwitterAPI.get_token"
Consumer Key: aaaaaaaaaaaaaaaaa
Consumer Secret: bbbbbbbbbbbbbbbbb
Access this URL and approve => http://twitter.com/oauth/authorize?oauth_token=xxxxxxxxxxxxx
Input OAuth Verifier: 9999999
Access token: ccccccccccccccccc
Access token secret: ddddddddddddddddd

config/initializer/secret.rbを用意する。

$secret = {
  "twitter" => {
    "CONSUMER_KEY"    => "aaaaaaaaaaaaaaaaa",
    "CONSUMER_SECRET" => "bbbbbbbbbbbbbbbbb",
    "TOKEN"           => "ccccccccccccccccc",
    "TOKEN_SECRET"    => "ddddddddddddddddd"
  }
}

config/environment.rbの最後に

require "simple-oauth"

を追加。

Gemfileの最後に

gem 'json-jruby'

を追加。

appcfg.rb run -S irb -r config/environment
> TwitterAPI.new.fetch_user(:screen_name=>"urekat")
> TwitterAPI.new.fetch_user(:id=>5416352)
> TwitterAPI.new.fetch_friend_ids(:screen_name=>"urekat")
> TwitterAPI.new.fetch_follower_ids(:screen_name=>"urekat")
> TwitterAPI.new.tweet("hey")

その他は http://apiwiki.twitter.com/ を参照。

Cronでつぶやく

class TaskController < ApplicationController
  skip_before_filter :verify_authenticity_token
  before_filter :check_cron_header
  def check_cron_header
    if Rails.env=="development"
      return
    end
    case @action_name
    when /^cron_/
      if request.headers["X-AppEngine-Cron"]!="true"
        render :text=>"not cron"
      end
    when /^job_/
      # X-AppEngine-QueueName, the name of the queue (possibly default)
      # X-AppEngine-TaskName, the name of the task, or a system-generated unique ID if no name was specified
      # X-AppEngine-TaskRetryCount, the number of times this task has been retried; for the first attempt, this value is 0
      if request.headers["X-AppEngine-QueueName"].nil?
        render :text=>"not job"
      end
    else
      render :text=>"OK"
    end
  end

  def cron_tweet
    TwitterAPI.new.tweet("Hey, it's #{Time.now}.")
    render :text=>"OK"
  end
end

Cronの実行間隔の設定ファイルを書きます。

./cron.yaml

cron:
- description: tweet
  url: /task/cron_tweet
  schedule: every 3 minutes

バグ回避のためWEB-INF/cron.xmlを作る。(次リリースで修正されます。)

<?xml version="1.0" encoding="UTF-8"?><cronentries/>

デプロイすれば3分ごとに時刻をつぶやく。はず。

ログを見る

http://appengine.google.com/

に行って自分のアプリを開き、[Logs] →Minimum Severity:[Request Only]にする。

spinup/spindown

AppEngineはサーバリソースの節約のため、リクエストが無いアプリインスタンス

数分でシャットダウンさせます(spindown)。

シャットダウンしているアプリに対してアクセスすると

AppEngineは「新しいインスタンスを生成(spinup)」してからリクエストを処理します。

AppEngineには「30秒以内に処理を完了しないと強制終了」というルールがあり、

このルールはspinupにも適用されます。

困ったことに、JRuby+Railsアプリはspinupに30秒近くかかります(appengine-jruby-jars-0.0.6の場合)。

ぎりぎり29秒でspinupできればOKですが、30秒を超えると500エラーです。

spinupしたあとのインスタンスは普通に数十〜数百ミリ秒で処理を返します。

しかし数分アクセスが無いとspindownしてしまうので、次回またspinupからやり直しです。

(JRuby1.5を含んだappengine-jruby-jars-0.0.7がリリースされると16-18秒ぐらいになります。)

最後の手順

conceal-rsconceal-rs 2010/03/02 16:56 うまく動かんとです....

環境は SnowLeopard + MacPorts の Ruby 1.8.7 p249 でなんですが,「appcfg.rb bundler --update .」実行時に appengine-tools が見つからないという Gem のエラーになってしまいます.どうも Ruby とか Gem の問題のような気がするのですが,そのあたりの情報とかってありませんか?

urekaturekat 2010/03/02 17:14 gistあたりにべったり貼ってくだされば調べられるかもしれません。

あるいはこのあたり http://github.com/takeru/rails_appengine との統合作業でおかしくなってるかもしれません。

conceal-rsconceal-rs 2010/03/02 23:16 実行ログとってみました.

http://gist.github.com/319525

Debian ではうまく動くので,やはり Ruby の問題かなとは思ってます.

urekaturekat 2010/03/03 13:38 インストールしたgemの中のjarのパーミッションがおかしかったりしませんか?おかしかったらどうなっていたか教えていただけると助かります。

conceal-rsconceal-rs 2010/03/03 14:12 Debian の場合は appengine-tools-0.0.10/lib/jruby-rack-0.9.6.jar が 640 になってて,これを直したら問題ありませんでした.

SnowLeopard の場合はあとで確認しますが,確かパーミッションは問題なかったはずです.

conceal-rsconceal-rs 2010/03/03 23:06 gem の中の jar のパーミッションは大丈夫でした.

一体何が原因なのか・・・・

urekaturekat 2010/03/04 00:27 appcfg.rbはjrubyじゃなくてcrubyを起動するはずなのにjrubyが動いている?

conceal-rsconceal-rs 2010/03/04 12:22 このあと

/etc/paths に /opt/local/bin を追加
sudo /opt/local/bin/gem update --no-ri --no-rdoc #=> rack 1.1.0 がインストール

した状態で実行したら正常に動作しました.

今日帰ったら上記のを個別に試してみます.

conceal-rsconceal-rs 2010/03/04 22:23 そもそもの rails2_td_appengine.rb が新しくなっているようで,それで動作するようになった模様です.

いろいろありがとうございました.

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト

コメントを書くには、なぞなぞ認証に回答する必要があります。