HaskellでOAuth(1): Request Token取得
追記)署名アルゴリズムに誤りがあったので訂正
HTTP通信のやり方を適当に抑えた所で、いよいよHaskellでOAuthプロトコル通信をやってみました。
大体は下のサイトあたりを参考にしました。
OAuthプロトコルの中身をざっくり解説してみるよ
http://d.hatena.ne.jp/yuroyoro/20100506/1273137673
HaskellでOAuthとTwitter API
http://d.hatena.ne.jp/kenkov/20110503/1304441705
が、ものすごく細かい仕様でハマりまくりましたので、ハマりポイントを晒します。
(1)Authorizationヘッダに設定する区切り文字は' & '(アンパサンド)ではなく' , '(コンマ)
→一つ目のサイトでは間違ってます。仕様変更?
(2)署名生成時、パラメータをURIエンコードするのは' & '、' = 'でつないだ後
→つまり、署名生成文字列には' & ' → ' %26 '、' = ' → ' %3D 'と変換されたものが入る。
(3)リクエストするURIは、SSH通信しないなら"https:/ ~ "ではなく"http:// ~ "
→これにハマりまくりました。提示されてる方はhttpsの方なので、駄目ならsをとって試したほうがいいかもです。
あとは、アプリのアクセス権限を"Read Only"から"Read and Write"に変更するとかも必要?
調べてみるとみなさん色々な所でUnauthorizedで弾かれているようです……
このくらいです。
実際、あんまり細かい書式には拘っていないようでした。コンマの後にスペース入れたり、パラメータのダブルクオートが無かったりしても平気でした。
署名生成のアルゴリズムとBase64エンコードはライブラリがあるのでそれを使用。
それぞれSHA1パッケージとdataencパッケージをcabalで取ってくる。
以下コード。OAuth型はとりあえず今回必要な分だけ。アクセストークンの値入れたりするフィールドは絶対いるけど。
HaskellでHTTPリクエスト送信(2)・HTTPレスポンス取得
HTTPリクエストを送信する方法を前の記事で書き忘れたけど、Request型の値をそのまま送信するには
simpleHTTP :: HStream ty => Request ty -> IO (Result (Response ty))
を使えばいいようです。当然リクエスト送信なので返り値はIO型。
ty型はとりあえずStringやByteStringが使えて、返り値の型はこんな感じ。
type Result a = Either ConnError a data Response a = Response { rspCode :: ResponseCode , rspReason :: String , rspHeaders :: [Header] , rspBody :: a } type ResponseCode = (Int, Int, Int)
ステータスが
・"302 Found " なら rspCode = (3, 0, 2), rspReason = "Found "
・"200 OK " なら rspCode = (2, 0, 0), rspReason = "OK "
が入ります。
rspHeadersにはRequest型と同じく各種ヘッダに対応するHeader型が、rspBodyにはResponse Bodyが入ります。
・Output
rspCode: (2,0,0) rspReason: "OK " rspHeaders: [Date: Sun, 18 Mar 2012 14:23:27 GMT ,Server: Apache/2.2.9 (Debian) mod_python/3.3.1 Python/2.5.2 ,Last-Modified: Wed, 22 Feb 2012 15:08:10 GMT ,ETag: "1886002-aba-4b98ee5476a80" ,Accept-Ranges: bytes ,Content-Length: 2746 ,Vary: Accept-Encoding ,Connection: close ,Content-Type: text/html ] rspBody: "<!DOCTYPE HTML ......
HaskellでHTTPリクエスト送信(1)
HaskellでのHTTPリクエスト送信について調べたことメモ。
このへんのページを参考に
"HaskellでHTTP"
http://d.hatena.ne.jp/kenkov/20110430/1304162021
"HaskellでOAuthとTwitter API"
http://d.hatena.ne.jp/kenkov/20110503/1304441705
"Haskell で OAuth"
http://d.hatena.ne.jp/kenkov/20110503/1304441705
hackage
Network.Browser
http://hackage.haskell.org/packages/archive/HTTP/4000.0.8/doc/html/Network-Browser.html
Network.HTTP
http://hackage.haskell.org/packages/archive/HTTP/4000.0.8/doc/html/Network-HTTP.html
Network.URI
http://hackage.haskell.org/packages/archive/network/2.1.0.0/doc/html/Network-URI.html
ただHTTP Requestを生成するだけならNetwork.HTTPに関数が用意されています。
・GETリクエスト生成
getRequest :: String -> Request_String
・使用例
・Output
GET http://www.google.co.jp/ HTTP/1.1 Content-Length: 0 User-Agent: **********
リクエスト先URIを受け取って、request bodyなしのGETリクエストを生成する関数。
URIの形式が文法的におかしい(isn't a syntactically valid URL)ときはerrorを吐くらしい。
・POSTリクエスト生成
postRequest :: String -> Request_String
上の関数のPOSTリクエスト版。
このような関数もあるんですが、OAuthにアクセスするためにAuthorizationヘッダにパラメータを設定して云々とかやるんなら、普通にRequest型の各フィールドに値を設定したほうが良さそうな気がします。
type Request_String = Request String
data Request a = Request { rqURI :: URI , rqMethod :: RequestMethod , rqHeaders :: [Header] , rqBody :: a }
rqURI、rqMethod,rqHeaders,rqBodyに対応する値を突っ込んでいけばいいわけです。
とりあえずBodyは置いとくとして、MethodはだいたいMethod名そのまんまの値(GET,POSTなど)がRequestMethod型に用意されていますので、これを入れればいいっぽいです。
残る問題はURI型とHeader型の値の生成。
・URI型の値生成
parseURI :: String -> Maybe URI
URIの文字列を受け取って、正常にパースできる形式のURIならJustに対応するURI型の値をくるんで渡す関数。 もちろんパースエラーならNothingで返ってきます。
・Header型の値の生成
mkHeader :: HeaderName -> String -> Header
HeaderName型は、各種ヘッダ名の頭に"Hdr"がついた値が用意されています。Authorizationなら HdrAuthorization :: HeaderName を使えばOK。
第2引数にヘッダの内容をStringで与えるとHeader型の値が生成できます。
とりあえずパラメータは置いといて、空のAuthorizationヘッダを持ったリクエストトークン取得のためのHTTPリクエストのヘッダはこんな感じになるハズ……
・Output
POST https://api.twitter.com/oauth/request_token HTTP/1.1 Authorization: OAuth
(わざわざIOで包んでるのは、パラメタにタイムスタンプと乱数を使う場面があるからで、現時点では意味は無いです。)