Google App Engine/JRubyã§Twitterã®èªåãã©ãã¼è¿ã
å æ¥Google App Engineã®SDKã1.2.6ã«ãã¼ã¸ã§ã³ã¢ããããã¢ããªã±ã¼ã·ã§ã³ãã¡ã¼ã«ãåä¿¡ã§ããããã«ãªãã¾ããã
Twitterã®èªåãã©ãã¼è¿ããå®ç¾ããã«ã¯ãããã¾ãã«åãã¦
- ãã©ãã¼éç¥ã®ã¡ã¼ã«ãåä¿¡ããã¿ã¤ãã³ã°ã§ããã°ã©ã ãå¼ã³åºã
- å®æçã«ã¡ã¼ã«ãµã¼ãã«ãã©ãã¼éç¥ã¡ã¼ã«ã確èªãã«è¡ã
- å®æçã«followingã¨followersã®å·®åããã§ãã¯ãã
ã¨ãã3ãã¿ã¼ã³ãããã¨æãã¾ãã
GAEã®ã¡ã¼ã«åä¿¡æ©è½ã使ãã°ããã®ä¸ã®1çªãæ軽ã«å®ç¾ã§ãããã§ãã
ã¨ãããã¨ã§ãããç°¡åãªèªåãã©ãã¼è¿ãã®ä»çµã¿ãGAE/JRubyã§ä½ã£ã¦ã¿ã¾ããã
ä¸æºå
GAE/JRubyã®ç°å¢ãæ´ããã¨ããã¾ã§ã¯ä»¥ä¸ã®è¨äºãåç §ãã¦ä¸ããã
gemã§ã¤ã³ã¹ãã¼ã«ã§ããApp Engine SDKã¯2009/10/18ç¾å¨ã¾ã 1.2.5ã§ãããç¹ã«åé¡ãªãåãããã§ããéçºãµã¼ãã§ã®ãã¹ãã¡ã¼ã«éä¿¡æ©è½ã使ããªãã®ã¯çãã§ããâ¦ã
ç°å¢ãæ´ã£ããããã£ã¬ã¯ããªãä½ã£ã¦å¿ è¦ãªgemã®ã¤ã³ã¹ãã¼ã«ããã¦ããã¾ãã
$ mkdir sample-twitter-follow-back $ cd sample-twitter-follow-back $ appcfg.rb gem install sinatra appengine-apis
è¨å®ãã¡ã¤ã«ã®ä½æ
rackupã®è¨å®ãã¡ã¤ã«ãæ¸ãã¾ãã
$ vim config.ru
require 'appengine-rack' AppEngine::Rack.configure_app( :application => 'your-application-id', :version => 1, # 1. ã¡ã¼ã«åä¿¡ãµã¼ãã¹ãæå¹åãã :inbound_services => [ :mail ] ) # 2. 管çè ã¢ã«ã¦ã³ã以å¤ããã®ã¢ã¯ã»ã¹ãç¦æ¢ãã use AppEngine::Rack::AdminRequired require 'main' run Sinatra::Application
ã½ã¼ã¹ä¸ã®ã³ã¡ã³ãé¨åãå°ãè£è¶³ãã¦ããã¾ãã
1. ã¡ã¼ã«åä¿¡ãµã¼ãã¹ãæå¹åãã
AppEngine::Rack.configure_appã¡ã½ããã¯:applicationã¨:version以å¤ã«ãããã¤ããªãã·ã§ã³å¼æ°ãåãã¾ãã*1
å種ãµã¼ãã¹ã«é¢ãã¦ã¯ãä¸ã®ããã«:inbound_servicesãªãã·ã§ã³ã«ä½¿ããããµã¼ãã¹åãæå®ãããã¨ã§WEB-INF/appengine-web.xmlã«ãã®å 容ãåæ ããããµã¼ãã¹ã使ããããã«ãªãã¾ãã
2. 管çè ã¢ã«ã¦ã³ã以å¤ããã®ã¢ã¯ã»ã¹ãç¦æ¢ãã
ä»åã¯ã¡ã¼ã«åä¿¡ãµã¼ãã¹ããå¼ã³åºããã以å¤ã®ä½¿ãæ¹ãæ³å®ãã¦ãã¾ããã®ã§ãå ¨ä½ã«â管çè ã®ã¿è¨±å¯âã®ã¢ã¯ã»ã¹å¶éãããã¦ãã¾ãã¾ãã
ä»ã«ãAppEngine::Rack::LoginRequiredãAppEngine::Rack::SSLRequiredãªã©ã®ã»ãã¥ãªãã£ãªãã·ã§ã³ããããRack::Builder#mapã¡ã½ããã¨çµã¿åããããã¨ã§é©ç¨ç¯å²ãæå®ãããã¨ãã§ãã¾ãã
ã¢ããªã±ã¼ã·ã§ã³ã®ä½æ
ã¢ããªã±ã¼ã·ã§ã³æ¬ä½ã®ã½ã¼ã¹ãæ¸ãã¾ãã
$ vim main.rb
require 'sinatra' require 'yaml' require 'appengine-apis/logger' require 'appengine-apis/urlfetch' # 1. ã¡ã¼ã«ãå±ãã¨ãã¡ã¼ã«ã¢ãã¬ã¹ãå«ãã URLãå¼ã³åºããã post '/_ah/mail/:account@:domain' do |account, domain| logger = AppEngine::Logger.new # 2. Twitterã¢ã«ã¦ã³ãæ å ±ãèªã¿è¾¼ã accounts = YAML.load_file('accounts.yaml') unless accounts[account] # 3. ä¸æãªã¢ã«ã¦ã³ãã®å ´åã¯ãã°ã«è¨é²ãã¦ãã logger.warn("Received a mail for an unkonwn account.") logger.warn("Account Name: #{account}") logger.warn("Domain: #{domain}") halt 'unkonwn account' end username = accounts[account]['username'] password = accounts[account]['password'] # 4. POSTãã¼ã¿ã¨ãã¦éããããã¡ã¼ã«ãèªã¿è¾¼ã mail_data = env['rack.input'].read # 5. ã¡ã¼ã«ã解æãã props = java.util.Properties.new session = javax.mail.Session.get_default_instance(props) stream = java.io.ByteArrayInputStream.new(mail_data.to_java_bytes) message = javax.mail.internet.MimeMessage.new(session, stream) # 6. Twitteréç¥ã¡ã¼ã«ã®åºæãããããå¿ è¦ãªæ å ±ãå¾ã mail_type = message.get_header('X-TwitterEmailType').to_a.first sender_id = message.get_header('X-TwitterSenderID').to_a.first if mail_type == 'is_following' && sender_id # 7. ãã©ãã¼éç¥ã¡ã¼ã«ãªã®ã§ãã©ãã¼è¿ãããã url = "https://twitter.com/friendships/create/#{sender_id}.json" request = Net::HTTP::Post.new('/') request.basic_auth(username, password) options = { :method => request.method, :payload => request.body, :headers => { 'Authorization' => request['Authorization'] } } response = AppEngine::URLFetch.fetch(url, options) unless response.code.to_i == 200 # 8. ãªã¯ã¨ã¹ã失ææã¯ãã°ã«è¨é²ãã¦500ã¨ã©ã¼ãè¿ã logger.error("Reguest failed of following back.") logger.error("Status: #{response.code} #{response.message}") logger.error("User ID: #{sender_id}") halt 500, 'request failed' end else # 9. ãã以å¤ã®ã¡ã¼ã«ã¯ãã°ã«è¨é²ãã¦ãã subject = message.subject from = message.from.to_a.join(', ') logger.warn("Received a mail which is not 'is_following'.") logger.warn("Subject: #{subject}") logger.warn("From: #{from}") end 'ok' end
å ã»ã©ã¨åãããã«ãã³ã¡ã³ãé¨åã«ã¤ãã¦å°ãè£è¶³ãã¦ããã¾ãã
1. ã¡ã¼ã«ãå±ãã¨ãã¡ã¼ã«ã¢ãã¬ã¹ãå«ãã URLãå¼ã³åºããã
ã¡ã¼ã«åä¿¡ãµã¼ãã¹ã®ããã¥ã¡ã³ããèªãã¨ãã¡ã¼ã«ãåä¿¡ããæ¯ã«"/_ah/mail/<address>"ã¨ããURLã«å¯¾ãã¦POSTãªã¯ã¨ã¹ããçºè¡ãããããã§ãã
URLã«å«ã¾ããã¡ã¼ã«ã¢ãã¬ã¹ããã¢ã«ã¦ã³ãåãåå¾ãã¦ããã¾ãã
2. Twitterã¢ã«ã¦ã³ãæ å ±ãèªã¿è¾¼ã
Twitterã®ã¢ã«ã¦ã³ãæ å ±ã¯å¥ãã¡ã¤ã«ã«ã¾ã¨ãã¦ãããã¨ã«ãã¾ãããããããã°ãè¤æ°ã®ã¢ã«ã¦ã³ãã®èªåãã©ãã¼ã1ã¤ã®ã¢ããªã±ã¼ã·ã§ã³ã§æ å½ãããã¨ãã§ãã¾ãã
ã¢ã«ã¦ã³ãæ å ±ãã¡ã¤ã«ã®è©³ç´°ã¯å¾è¿°ãã¾ãã
3. ä¸æãªã¢ã«ã¦ã³ãã®å ´åã¯ãã°ã«è¨é²ãã¦ãã
ã¡ã¼ã«ã¯èª°ã§ãéä¿¡ã§ãã¾ãã®ã§ãæ³å®ãã¦ããªãã¢ã«ã¦ã³ãã«å¯¾ãã¦ã¡ã¼ã«ãéããã¦ãããã¨ãããããããã¾ãããã©ããGAEã®ã¡ã¼ã«åä¿¡ãµã¼ãã¹ã¯ãªã¯ã¨ã¹ãã失æããã¨ã¡ã¼ã«ã®åéããããããã«ãªã£ã¦ãããããªã®ã§ãããã¯ãã°ã«æ¸ãæ®ãã¦ããã ãã§æ£å¸¸ãªã¬ã¹ãã³ã¹ãè¿ãã¦ããã¾ããããããªãã¨å»¶ã ã¨åéãããããã¦ãã¾ãã¾ãã
4. POSTãã¼ã¿ã¨ãã¦éããããã¡ã¼ã«ãèªã¿è¾¼ã
å ã»ã©ã®ããã¥ã¡ã³ãã«ãããã¾ããããã¡ã¼ã«ã½ã¼ã¹ã¯POSTãã¼ã¿ã¨ãã¦éããã¦ãã¾ãã®ã§ãä¸æ¦å ¨ã¦ã¡ã¢ãªä¸ã«èªã¿è¾¼ãã§ãã¾ãã¾ãã
5. ã¡ã¼ã«ã解æãã
èªã¿è¾¼ãã ã¡ã¼ã«ã½ã¼ã¹ãJavaã®MimeMessageã¯ã©ã¹ã使ã£ã¦è§£æãã¾ãããã®è¾ºã¯è©³ãã説æã¯çãã¾ããããããæµããªã®ã ã¨æã£ã¦ä¸ããããããã¯Javaã®ãªãã¡ã¬ã³ã¹ãåç §ãã¦ä¸ããã
6. Twitteréç¥ã¡ã¼ã«ã®åºæãããããå¿ è¦ãªæ å ±ãå¾ã
Twitterããã®ãã©ãã¼éç¥ã¡ã¼ã«ã«ã¯ãããã°ã©ã ãã¡ã¼ã«ãå¦çããããããã«ãããã¤ãåºæã®ããããä»å ããã¦ãã¾ãã
ããããæ å ±ãåå¾ããã°ãããããé¢åãªã¡ã¼ã«æ¬æã®è§£æãè¡ãå¿ è¦ã¯ããã¾ããã
7. ãã©ãã¼éç¥ã¡ã¼ã«ãªã®ã§ãã©ãã¼è¿ãããã
ãã©ãã¼è¿ãã¯AppEngine::URLFetch.fetchã¡ã½ããã使ã£ã¦è¡ã£ã¦ãã¾ããBasicèªè¨¼ãªã®ã§HTTPSã§ãªã¯ã¨ã¹ããã¾ãã
Net::HTTP::Postã¯ã©ã¹ã¯Basicèªè¨¼ã«ä½¿ãAuthorizationãããã®ã¨ã³ã³ã¼ãã¨ãPOSTãã©ã¡ã¼ã¿*2ã®ã¨ã³ã³ã¼ãã®ããã«ä½¿ã£ã¦ããã ãã§ãç´æ¥ãªã¯ã¨ã¹ãã«ã¯ä½¿ç¨ãã¾ãããèªåã§ã¨ã³ã³ã¼ããããªãNet::HTTP::Postã¯ã©ã¹ã使ãå¿ è¦ã¯ããã¾ããã
8. ãªã¯ã¨ã¹ã失ææã¯ãã°ã«è¨é²ãã¦500ã¨ã©ã¼ãè¿ã
åè¿°ã®éãããªã¯ã¨ã¹ã失ææã¯ã¡ã¼ã«ã®åéãããã¦ããããããªã®ã§ããã©ãã¼ã«å¤±æããå ´åã¯ããã¨500ã¨ã©ã¼ãè¿ãã¦ããã¾ãã
ãã ãä¾ãã°ããã¹ã¯ã¼ããééã£ã¦ããããªã©ã®ãåéãã¦ãæåããªãã±ã¼ã¹ãã ã£ãå ´åãåéã®ã«ã¼ãã«é¥ã£ã¦ãã¾ãã¾ãããã¡ãã¨ä½ããªããã®è¾ºãã®é æ ®ãå¿ è¦ã«ãªãã¾ãã
9. ãã以å¤ã®ã¡ã¼ã«ã¯ãã°ã«è¨é²ãã¦ãã
ãããä»ã¾ã§åºã¦ãããã¿ã¼ã³ã¨åãããã«ããã°ã«è¨é²ããä¸ã§åéãé²ãããã«æ£å¸¸çµäºãã¦ãã¾ãã
ã¢ã«ã¦ã³ãæ å ±ãã¡ã¤ã«ã®ä½æ
ããã§ã¢ããªã±ã¼ã·ã§ã³ã¯å®æã§ãã®ã§ãæå¾ã«ã¢ã«ã¦ã³ãæ å ±ãã¡ã¤ã«ãåããã£ã¬ã¯ããªã«ä¿åãã¦ããã¾ãã
$ vim accounts.yaml
user1: username: Sample_User1 password: 1234ABCD user2: username: testuser2 password: foobarbaz
ä¸ã®ä¾ã§è¨ãã¨"user1", "user2"ã®é¨åããã¢ã«ã¦ã³ãåãã«ãªãã¾ãããã¢ã«ã¦ã³ãåãã¨Twitterã¸ã®ãã°ã¤ã³ã«ä½¿ããã¦ã¼ã¶åãã¯åãã§ãç°ãªã£ã¦ãã¦ãæ§ãã¾ããã
è¨ãã¾ã§ãããã¾ãããããããã®ã¢ã«ã¦ã³ãæ å ±ãã¯ã©ã¦ãä¸ã«ã¢ãããã¼ããããã¨ã¯ãããªãã®ãªã¹ã¯ãä¼´ãã¾ãããã®è¾ºãã¯èªå·±è²¬ä»»ã§è¡ã£ã¦ä¸ããã
ã¡ã¼ã«ã¢ãã¬ã¹ã®ç»é²
èªåãã©ãã¼ãããTwitterã¢ã«ã¦ã³ãã®ãè¨å®ããã¼ã¸ã§ã¡ã¼ã«ã¢ãã¬ã¹ãç»é²ãã¾ããã¡ã¼ã«ã¢ãã¬ã¹ã¯
<ã¢ã«ã¦ã³ãå>@<ã¢ããªã±ã¼ã·ã§ã³ID>.appspotmail.com
ã«ãªãã¾ãããã¡ã¤ã³ã"appspotmail.com"ãªã®ã«æ³¨æãã¦ä¸ããã
ã¢ããªã±ã¼ã·ã§ã³ã®ã¢ãããã¼ã
æ¬æ¥ãªãããã§éçºãµã¼ãã§ã®ãã¹ããè¡ãããã¨ããã§ãããåè¿°ã®éãã2009/10/18ç¾å¨ã¯ã¾ã google-appengine gemã®åã¢ã¸ã¥ã¼ã«ãSDK 1.2.6ã®æ©è½ã«å¯¾å¿ãã¦ããªãããããã£ãã¨å®ç°å¢ã«ã¢ãããã¼ããã¦ããã¡ãã§åä½ç¢ºèªãã¦ãã¾ãã¾ãã*3
config.ruã®:applicationã¨:versionãæ£ãããã¨ã確èªãã¦ããã¦ä¸ããã
$ appcfg.rb update .
ã¢ãããã¼ããå®äºãããã試ãã«å ã»ã©ç»é²ããã¡ã¼ã«ã¢ãã¬ã¹ã«å¯¾ãã¦é©å½ãªã¡ã¼ã«ãéä¿¡ãã¦ã¿ãããå®éã«ä»ã®ã¢ã«ã¦ã³ããããã©ãã¼ãã¦ã¿ãããã¦åä½ã確èªãã¦ããã¨è¯ãã¨æãã¾ãã
åé¡ç¹
ããã§ã¨ããããèªåãã©ãã¼ã®ä»çµã¿ãå®æãã¾ããããããããã®ã¢ããªã±ã¼ã·ã§ã³ã«ã¯ããã¤ãã®é大ãªæ¬ é¥ãåå¨ãã¾ãã
å±ããã¡ã¼ã«ããã®ã¾ã¾å¤±ããã¦ãã¾ãã®ãæããåé¡ã§ããããããã©ãã¼éç¥ã¡ã¼ã«ãªãå¥ã«æ¶ããã£ã¦æ§ããªãããããã¾ãããããã¤ã¬ã¯ãã¡ãã»ã¼ã¸ã®ã¡ã¼ã«ããã®ä»ã®ã¡ã¼ã«ã¾ã§æ¶ãã¦ãã¾ãã®ã¯åé¡ã§ãã転éãããªãããã¼ã¿ã¹ãã¢ããã°ã«è¨é²ãã¦ãããªãããã»ããè¯ãã§ãããã
ãã¹ã¯ã¼ããå¹³æã§ã¢ãããã¼ãããªããã°ãããªãã®ãæ°ã«ãªãç¹ã§ãããã®ç¹ã¯OAuthã®ä»çµã¿ãå®è£ ãããã¨ã§è§£æ±ºã§ãããã§ãã
æãã©ãã«ããªããªãåé¡ã¯ããGAEã®1ãªã¯ã¨ã¹ã30ç§å¶éãã¨ãJRubyã¤ã³ã¹ã¿ã³ã¹ã®ãã¼ãã®é ããã®çµã¿åããã«ãããã¿ã¤ã ã¢ã¦ãã®é »çºãã§ããããã
ãã°ãç£è¦ãã¦ããã¨ãããã¾ãããã¶ã£ã¡ããã話ãJRubyç°å¢ããã¼ãããã ãã§æ®éã«30ç§å¶éãçªç ´ãã¦DeadlineExceededExceptionä¾å¤ãçºçãã¦ããããã¾ãã幸ãä½åº¦ãåéãããã¦ããããããã§ããã®ãã¡æåãã¦ããã¾ããâ¦ã
ä»å¾GAE/JRubyã®æé©åãé²ãã°è§£æ±ºããããããã¾ãããããã¯ãGAEã®Rubyãã¤ãã£ã対å¿ã欲ããã¨ããã§ãã
æ´ãªããã©ãã¯ã¼ç®¡ç
ãã¦ãããã§ããã©ãã¼éç¥ã¡ã¼ã«åå¿åã®èªåãã©ãã¼ãã¯å®ç¾ã§ãã¾ãããããã©ãã¯ã¼ã®å®å ¨èªå管çãè¡ãããã«ã¯ãããã ãã§ã¯å ¨ç¶è¶³ãã¾ããã
çããããçµé¨ãããããããã¾ãããããã©ãã¼éç¥ã¡ã¼ã«ã¯100ï¼ ä¿¡é ¼ã®ããããã®ã§ã¯ãªããå®éã«ã¯ããã©ãã¼ãããã®ã«ã¡ã¼ã«ãéããã¦ããªããã¨ãããã¿ã¼ã³ãããããã¾ãã
ããã«ãbotçã®ããã°ã©ã ã§ã¯ãã©ãã¼ã解é¤ãããå ´åã®ãèªåãã©ãã¼å¤ããæ©è½ãå¿ è¦ãªãã¨ãããããããã¾ããã
ãããã¯GAEã®Cronãµã¼ãã¹ã¨ãTwitterã®"friends/ids", "followers/ids" APIãçµã¿åããããã¨ã§è§£æ±ºå¯è½ã§ãããããã¯ã¾ãå¥ã®æ©ä¼ã«ã
- Scheduled Tasks With Cron for Java - Google App Engine - Google Code
- Twitter API Wiki / Twitter REST API Method: friends ids
- Twitter API Wiki / Twitter REST API Method: followers ids
ç°¡åã«ããæ¹ã ãæ¸ãã¦ããã¾ãã¨ãCronã§ä¸æ¥ä¸åã¿ã¹ã¯ãèµ·åããfriends/idsã¨followers/idsããã§ãã¯ãã¦ãå·®åIDã«å¯¾ãã¦ãã©ãã¼/ã¢ã³ãã©ãã¼ãå®è¡ãã¦ããã ãã§ãã
å®éã«ã¯åè¿°ã®30ç§å¶éãããã確å®ã«åä½ãããã«ã¯Task Queueãµã¼ãã¹ãå©ç¨ãããªã©ãè²ã 工夫ãå¿ è¦ã«ãªããããªæ°ããã¾ãã
â¦ã¾ããã¬ã³ã¿ã«ã§ãèªåã§ããèªç±ã«ä½¿ãããµã¼ãç°å¢ãç¨æã§ãããªãã°ãGAEã§ã¯ãªããã¡ãã使ã£ãã»ããç°¡åã ã£ããããã¾ããâ¦ã
*1:å ·ä½çã«ä½ãã¨ããã¯ãããã¥ã¡ã³ããè¦å½ãããªãã£ãã®ã§ã½ã¼ã¹ãèªããããªãã®ããããã¾ãããâ¦ã
*2:ä»åã¯POSTãã©ã¡ã¼ã¿ã¯åå¨ãã¾ãããã
*3:å®éã¯ã³ã¼ãã®ã»ããå¤æ´ãã¦éçºãµã¼ãã§ãåä½ç¢ºèªããã¦ãã¾ãã