HaskellでTwitterクライアント開発blog(仮)

今すぐに挫折するかもしれない程度のモチベーションによるTwitterクライアント開発記

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で包んでるのは、パラメタにタイムスタンプと乱数を使う場面があるからで、現時点では意味は無いです。)

勢いで始動

HaskellTwitterクライアントを作ったらどうなるだろうかという、

ただの興味によりblogを立ち上げ。

ちょっとでも関係の有りそうなこと(HaskellのTipsやOAuth関連?)を適当に書きなぐっていくだけの場所になる予定。