wvogel日記

自分用の技術備忘録が多めです.

HaskellでHTTPリクエストを作る

先日,ATCoderの問題を解けずにAC (Accepted)が獲れなかったら,代わりにGVS法で頭部にAC電流を流すことで,
確実にACが獲できるシステム"GVSCoder"を開発し,動画にして公開しました.
GVSCoderの中身はHaskellで書かれており,その時にはまったhttpリクエストの作り方について備忘録を記録しておきます.

一応,動画のリンクを貼っておきます.

AtCoderでAC取れなかったら頭にAC電流流す

また,ソースコードおよび電気刺激手法:GVSについての詳細はこちらにあります
GitHub - wvogel00/gvscoder: if you get other than AC, you get GV Stimulus!

HTTPリクエス

Network.HTTP.Clientでリクエストを作成する場合,Request型には次のフィールドがあります.

method :: ByteString
secure :: Bool
host :: ByteString
port :: Int
path ::ByteString
queryString :: ByteString
requestHeaders :: [(HeaderName, ByteString)]
requestBody :: RequestBody
proxy :: Maybe Proxy

ByteStringがたくさん出てくるので,OverloadedStringsプラグマを使いましょう.

そしてPOSTする場合,通常(?)であれば,requestBodyにユーザー情報などを設定するはずなのですが,うまくいきません.
試しに下のコードを走らせると,2回目のリクエストに対しては400エラーが返ってきます.

-- これはatcoderサイトでは動かない
post = do
    manager <- newManager tlsManagerSettings
    req <- parseRequest "https://atcoder.jp/login"
    res <- httpLbs req manager
    -- クッキーの取得
    let cookie = responseCookieJar res
    -- クッキー挿入
    -- setLoginInfoは,リクエストボディにユーザー情報を付与する関数
    req' <- attatchCookie (setLoginInfo req user) cookie
    -- ログインリクエストを投げる
    loginRes <- httpLbs req' manager
    BS.putStrLn.toStrict $ responseBody loginRes

400エラーはCSRFトークンエラーです.
2回目のリクエストを作成した時にCSRFトークン取得に失敗しているのかと思い,コンソール状にリクエストの中身を表示させてみると,CSRFの値は正しくrequestHeaderに装入されています.

結論から言うと,atcoderでは,CSRFトークンはrequestHeaderではなく,クエリに設定してPOSTしてやる必要があります.(最初,CSRF取得は自前でやる必要があるのだと勘違いしていたのでParserを組んでしまいましたが,これが役に立ちました)
すなわち,setLoginInfo関数の中身は,次のようにします.

data User = User{
    name :: BS.ByteString,
    pass :: BS.ByteString,
    csrf :: BS.ByteString
} deriving (Eq,Show)

setLoginInfo :: Request -> User -> Request
setLoginInfo req user = req{
    method = "POST",
    queryString = BS.concat [
            "?csrf_token=", csrf user,
            "&username=", name user,
            "&password=", pass user
            ]
    }

無事にログインに成功します.
少し面倒ですが,レスポンスを受け取るたびにcookieの値を更新してやることでセッションを維持することができます.
(不勉強で,cookieは使い回すものだと思っていました)

おそらくatcoderのリクエストの作り方は少し変わっていると思うので,
色々なサイトで試してみたいです.
web周りはほとんど触ったことがなかったので,今回初心者の詰まり方をしたおかげで色々勉強になりました.
コードがすぐに動いて結果がみられるというのはなかなか楽しいですね.