2018年3月5日月曜日

[haskell] http-clientライブラリを利用してHaskellでHTTPクライアント機能を実装する

Haskellでは、http-clientライブラリを用いることで、HTTPクライアント機能を簡単に実装できます。http-client以外にも何種類かライブラリがありますが、今回はhttp-client, http-client-tlsの機能と使い方をまとめておきます。

本エントリで紹介するhttp-client, http-client-tlsライブラリの機能:
  • 単純なHTTP GETリクエスト
    • 主要な型の説明
  • Managerのカスタマイズ 
    • https
    • proxy設定
    • タイムアウト値の設定
  • Requestのカスタマイズ
    • ベーシック認証
    • リクエストヘッダ
  • Responseの操作
    • ストリーミング受信
    • レスポンスヘッダの参照
  • エラーハンドリング


単純なHTTP GETリクエスト

{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)

main :: IO ()
main = do
  manager <- newManager defaultManagerSettings

  request <- parseRequest "http://httpbin.org/get"
  response <- httpLbs request manager

  putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
  print $ responseBody response

デフォルトManagerと、"http://httpbin/org/get"へのRequestを生成し、それらを引数にhttpLbsを呼び出して、サーバーからのResponseを得てその内容を表示するサンプルです。
文字列リテラルをRequest, ByteStringに変換する目的でOverloadedStrings言語拡張を利用しています。

主要な型の説明:

  • Manager
    • Managerはサーバーとの間に生成するコネクションの管理するもの。
    • 複数サーバーのコネクションを管理する前提で実装されており、クライアントの中では1つのインスタンスを共有することが推奨されている。 
    • Managerに対しては以下のような設定ができる。デフォルト設定を併記。
      • プロキシ設定:環境変数(http_proxy)の値を利用
      • TLS(https):サポートなし
      • 1サーバーあたりのkeep-aliveコネクション維持数: 10
      • 最大同時オープンコネクション数: 512
      • 受信タイムアウト: 30秒
  • Request
    • 特定サーバーに対して送信する1つのリクエストを表す型。
    • parseRequest関数などでHTTP METHOD, URIを指定してインスタンスを生成する。
    • Request単位の細かい設定ができる
      • ベーシック認証情報(アカウント&パスワード) 
      • プロキシ設定、プロキシ認証情報
      • クエリストリング、bodyなどの送信データ
    • HTTPメソッド(GET, POST, DELETEなど)、ヘッダ は生成したRequestインスタンスに対して、record構文で設定する
      • メソッドのデフォルトはGET
      • Content-Length, Transfer-Encoding, Accept-Encodingが自動で設定される
  • Response 
    • サーバーに送信したRequestに対応するサーバーからのレスポンスを表す型。HTTPステータスコード、レスポンスヘッダ、レスポンスボディなどを取り出すことができます。


Managerのカスタマイズ

defaultManagerSettingsの代わりにtlsManagerSettingsを用いてManagerを生成することでhttps通信が可能になります。 以下のサンプルではhttps対応に加えて、プロキシサーバーとして"127.0.0.1:8080"をManagerに設定しています。
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
import Network.HTTP.Client.TLS (tlsManagerSettings) -- 新たにimport文を追加

main :: IO ()
main = do
  -- manageSetProxyでデフォルトのプロキシ設定を上書く。
  -- tlsManagerSettingsを利用するとこでhttps通信が可能になる。
  manager <- newManager $ managerSetProxy (useProxy $ Proxy "127.0.0.1" 8080) tlsManagerSettings

  request <- parseRequest "https://httpbin.org/get" -- https通信に変更
  response <- httpLbs request manager

  putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
  print $ responseBody response

プロキシの設定:

  • デフォルトの挙動。環境変数(http_proxy/https_proxy) を参照する。
    • defaultProxy :: ProxyOverride
  • プロキシサーバーの設定を無視して通信する。
    • noProxy :: ProxyOverride
  • コードで明示的にプロキシサーバーを設定する。
    • useProxy :: Proxy -> ProxyOverride

レコード構文でManagerSettings値を生成する方法:

非公開APIを利用するため、オススメの方法ではありませんが以下のようにレコード構文を用いることで、Managerの細かい設定をカスタマイズすることができます。
import Network.HTTP.Client.Internal
-- tlsManagerSettingsをベースにmanagerResponseTimeoutを30→5秒に、
-- managerConnCountを10→3に変更
mySettings :: ManagerSettings
mySettings = tlsManagerSettings
             { managerResponseTimeout = responseTimeoutMicro 5000000
             , managerConnCount = 3
             }


Requestのカスタマイズ

以下のコードはRequestに対して、リクエストヘッダ、クエリーパラメタ、ベーシック認証情報を設定するサンプルです。Requestに対して設定できる項目の詳細は、Request type and fieldsを参照してください。
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)

main :: IO ()
main = do
  manager <- newManager defaultManagerSettings

  initialRequest <- parseRequest "http://httpbin.org/anything"
  let request = initialRequest
          { method = "POST"   -- "GET", "PUT", "DELETE"などのメソッドを指定
          , queryString = "foo=bar&xxx=yyy" -- parseRequestのuriに記述してもよい
          , requestHeaders =  -- ヘッダはタプル(名前、値)のリストで指定
              [ ("User-Agent", "New Agent!")
              , ("Content-Type", "text/plain")
              , ("Added-Header", "hoge")
              ]
          , requestBody = "body string."
          }
  let authRequest = applyBasicAuth "user" "pass" request -- ベーシック認証情報付与
  response <- httpLbs authRequest manager

  putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
  print $ responseBody response



Responseの操作

httpLbs関数はRequestとManagerを引数にとり、IO (Response ByteString)を返します。Response型クラスの関数で、Responseからステータスコード、レスポンスヘッダなどの情報を取り出すことができます。ResonseのAPIリファレンスはこちら
httpLbsは全データを受信しますが、大きなデータをレスポンスとして受信する場合はhttpLbsの代わりに、withResponseとbrReadを利用して逐次受信することが推奨されています。以下にそのサンプルを記載します。
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
import qualified Data.ByteString as B

main :: IO ()
main = do
  manager <- newManager defaultManagerSettings

  request <- parseRequest "http://httpbin.org/get"
  withResponse request manager receiveResponse


receiveResponse :: Response BodyReader -> IO ()
receiveResponse response = do
  putStrLn $ "response version: " ++ (show $ responseVersion response)
  putStrLn $ "status code: " ++ (show $ statusCode $ responseStatus response)
  putStrLn $ "response header: " ++ (show $ responseHeaders response)

  -- receive body data block by block
  let loop = do
        bs <- brRead $ responseBody response
        if B.null bs
          then putStrLn "\nFinished response body"
          else do
            print bs
            loop
  loop



エラーハンドリング

以下は、urlのパース(parseRequest)、及び、サーバーとの通信(httpLbs)のエラーハンドリングを行うサンプルです。これらの関数はエラー時にはHttpExceptionをスローします。サンプルではtry関数を用い、Left eでスローされたHttpExceptionの情報を表示しています。
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
import Control.Exception (try)
import System.Environment (getArgs)


createRequest :: [String] -> IO Request
createRequest [] = do
  putStrLn "NOTE: no argument is given. so use not existing url."
  parseRequest "http://unknown-host:80/" -- valid but not exists
createRequest args = do
  let url = head args
  eRequest <- try $ parseRequest url
  case eRequest of
    Left e -> do
      print (e :: HttpException)
      putStrLn $ "given url (1st argument) is invalid: " ++ url
      error "error!"
    Right request -> return $ request


main :: IO ()
main = do
  args <- getArgs
  manager <- newManager defaultManagerSettings
  request <- createRequest args

  eResponse <- try $ httpLbs request manager
  case eResponse of
    Left e -> do
      print (e :: HttpException)
      putStrLn $ "cannot reach server with given url: " ++ (head args)
    Right response -> do
      putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
      print $ responseBody response


他のライブラリ:

このエントリではhttp-clientの使い方を紹介していますが、これ以外にも以下のようなライブラリがあるようです。これらのライブラリも利用する機会があれば比較などを含めて記事にしたいと思います。

参考:

0 件のコメント:

コメントを投稿