誰得スクリプトで学ぶOAuthとRack(Rubyだよ)
元ネタ
ゆーすけべー先生が先日、以下のような誰得スクリプトを書いておられました。
Facebookのポスト内容をWebアプリケーションとして実行する - ゆーすけべー日記
まじ誰得w しかしよく見てみると勉強になる要素満載で、Rubyで書いたらOAuthとRackの勉強になるぞと思ったのでやってみた
謎スクリプト企画の全容
しかしそもそもこの謎スクリプト、企画の全容が謎w なので、まずは企画の全容を明らかにする。折角なので先生の著作Webサービスのつくり方に書かれている企画メソッドを借パクしてあてはめてみる。(名前は勝手にfacepostとする)
謎いw
内部設計(のようなもの)
折角なので内部設計(のようなもの)も真面目にやってみる。
3. 技術調査
作るにあたって、技術的に良くわからない箇所があるので、調査する。まず、facebookに投稿した内容を取得するにはどうすればよいのか?というのが良くわからないので、facebookのdeveloper siteを見る。
投稿に関する情報は以下のページ。どうやらaccess_tokenを取得する必要がある。
https://developers.facebook.com/docs/reference/api/post/
access_tokenを取得する方法は以下のページ。今回はlocalhostを使用するので、Server-side Loginの部分が該当箇所となる。数回リダイレクトしてaccess_tokenを取得することが分かった。
https://developers.facebook.com/docs/concepts/login/login-architecture/
3-2. Rack
今回はWebサービスとしてlocalhostで動かすので、WAFを使うか?使うなら何使うか?という調査・選択が必要。RailsやSinatraを使うまでもない規模なので、その大元であるRackを単体で使ってみることにした。Rack単体で使うのは初めてなので、調査。調べてみると、Rackとは
・WebサーバとWAFをつなぐプログラム
・Rackアプリケーションに対してrackupコマンドを実行すれば、Webサービスが立ち上がる
・RailsやSinatraもRackアプリケーションとして動いている
・Rackアプリケーションとは、以下の用件を満たすもの
・callというメソッドを持っていること ・callメソッドの引数としてWebサーバからのリクエストを受けること ・callメソッドは,次の要素を含むレスポンスを返すること ・ステータスコード ・レスポンスヘッダ(Hash) ・レスポンスボディ(Array)
引用:第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社
といったことが分かる。といってもよく分からないので、sandboxというフォルダを作って色々いじってみたです。
4. 簡単な仕様
設計の最後に、これから書くプログラムの簡単な仕様を書いた。ざっと以下のような感じ。
・facebookの投稿idを引数にrackupするとlocalhost:9292が立ち上がる
・ユーザーがhttp://localhost:9292/にアクセスすると、OAuth認証が行われる
・コールバックURLはhttp://localhost:9292/callbackとする
・ユーザーがfacebookに投稿したコードは標準出力になんらかの文字列を出力するコードであることを期待する
・標準出力に出力された文字列をブラウザに表示する
実装
いよいよ実装。Rackアプリケーションの書き方や、標準入出力の扱い方など結構はまった。けど動いたよ!
config.ru
#coding: utf-8 require 'open-uri' require 'json' require 'pit' config = Pit.get("facebook.com") app_id = config["app_id"] app_secret = config["app_secret"] post_id = ARGV[0] map '/' do run lambda {|env| uri = URI.escape("#{env['REQUEST_URI']}callback") redirect_url = "http://www.facebook.com/dialog/oauth?client_id=#{app_id}&redirect_uri=#{uri}&scope=read_stream" [302, {'Content-Type' => 'text', 'location' => redirect_url}, []] } end map '/callback' do run lambda {|env| # get access_token req = Rack::Request.new(env) code = req[:code] uri = URI.escape("#{req.base_url}#{req.path}") token_url = "https://graph.facebook.com/oauth/access_token?client_id=#{app_id}&redirect_uri=#{uri}&client_secret=#{app_secret}&code=#{code}" access_token = '' open(token_url) {|f| access_token = f.read} # get facebook post post_url = "https://graph.facebook.com/#{post_id}?#{access_token}" json = '' open(post_url) {|f| json = f.read} fb_post = JSON.parse(json) fb_message = fb_post['message'] # exec facebook post as code # treat stdout as response body body = '' IO.pipe {|r, w| w.instance_eval fb_message w.close body = r.read } # create response res = Rack::Response.new {|r| r.status = 200 r['Content-Type'] = 'text/html;charset=utf-8' r.write body } res.finish } end
できあがり!
以下のような感じでFacebookに投稿したコードをlocalhostで実行できた!
1. facebookにコードを投稿。その投稿IDを控える
2. 上記config.ruのあるディレクトリで以下のコマンドを実行
$ rackup 控えたID config.ru