haskell

HaskellでHttpリクエストをする

http-conduitというパッケージが使いやすそうです。

前準備

ghciでやりましょう。以降のコードスニペットは全て、ghci上にコピペすれば動くはずです。
http-conduithttp-typesaesonlenslens-aesonをあらかじめインストールして使えるようにしておいてください。ghciを立ち上げたら最初に以下で必要な拡張設定やパッケージインポートをします。

:set -XOverloadedStrings
:set -XDeriveGeneric
:set +m
import Data.Aeson
import Data.Aeson.Types
import Data.Char (toLower)
import Network.HTTP.Simple
import Network.HTTP.Types
import GHC.Generics (Generic)
import Control.Lens
import Data.Aeson.Lens

GETしてみる

単に指定URLへGETリクエストを送るだけであればとても単純で、

res <- httpLBS "https://google.com"

これだけです。

httpLBS :: MonadIO m => Request -> m (Response ByteString)

httpLBSの引数はRequest型ですが、OverloadedStrings拡張を有効にしておけば通常の文字列をRequest型として推論してくれます。戻り値はモナドに包まれていますが、上記のresIOのコンテクスト内で中身を取り出した状態になっているので、Reponse ByteString型になっています。このままprintもできますが、中の値を取り出す関数もちゃんと用意されていて、

-- ステータスを取得
getResponseStatus res

-- ステータスコードをIntで取得
getResponseStatusCode res

-- ヘッダーを全部取得(HeaderName, ByteString)のタプルのリスト
getResponseHeaders res

-- 指定したヘッダーを取得
-- Dateヘッダーを取得
getResponseHeader hDate res
-- Cache-Controlヘッダーを取得
getResponseHeader hCacheControl res

-- レスポンスボディを取得
getResponseBody res

という感じです。getResponseHeaderは、第一引数でHeaderNameという型を受け取るのですが、主要なヘッダーはNetwork.HTTP.Types.Headerに定義されています。自前でヘッダーを定義していてそれを取得したい場合は、以下のようにして文字列をHeaderName型として扱うことができます。

:t "My-Header-Key" :: HeaderName

実際にこのヘッダーの値を取得する場合は、(今回のres内には存在しませんが)単に以下のように

-- このヘッダーは存在しないので空リストが返ってくる
getResponseHeader "My-Header-Key" res

とすれば良いです。

リクエストを送る他の関数

実は、ドキュメントを見れば書いていますがhttpLBS以外にもいくつかリクエストを送る関数が用意されています。

  • httpBShttpLBSとほぼ同じですが、戻り値のByteStringがLazyではありません。
  • httpJSONはJSONレスポンスを想定し、それをAesonでパースする場合に使用可能です。WebAPIを叩くときに使えます。JSONでない場合は例外を投げるので要注意です。
  • httpJSONEitherは戻り値がEitherになっているので例外を投げない安全な関数です。

以下のように使用可能です。

-- ボディはJSONだが、単にByteStringとして返す
getResponseBody <$> httpBS "https://httpbin.org/json"

-- httpJSONやhttpJSONEitherを使うため、想定するレスポンスのJSONを定義
-- ちょっと面倒
:{
data Slide = Slide
  { slideItem :: Maybe [String]
  , slideTitle :: String
  , slideType :: String
  } deriving (Show, Generic)
instance FromJSON Slide where
  parseJSON = genericParseJSON $ defaultOptions {fieldLabelModifier = map toLower . drop 5}
data SlideShow = SlideShow
  { slideShowAuthor :: String
  , slideShowDate :: String
  , slideShowSlides :: [Slide]
  , slideShowTitle :: String
  } deriving (Show, Generic)
instance FromJSON SlideShow where
  parseJSON = genericParseJSON $ defaultOptions {fieldLabelModifier = map toLower . drop 9}
data ResBody = ResBody
  { resBodySlideShow :: SlideShow } deriving (Show, Generic)
instance FromJSON ResBody where
  parseJSON = genericParseJSON $ defaultOptions {fieldLabelModifier = map toLower . drop 7}
:}

-- JSONデータをResBody型としてパースして返す
getResponseBody <$> httpJSON "https://httpbin.org/json" :: IO ResBody

-- パースできないあるいはJSONでない場合は例外を投げる
getResponseBody <$> httpJSON "https://httpbin.org/json" :: IO String

-- Eitherで返す
getResponseBody <$> httpJSONEither "https://httpbin.org/json" :: IO (Either JSONException ResBody)

-- パースできないあるいはJSONでない場合に例外を投げずLeftを返す
getResponseBody <$> httpJSONEither "https://httpbin.org/json" :: IO (Either JSONException String)

Requestを作成する

ヘッダーをつけたりクエリパラメータを設定する時は、Requestを作成します。最もシンプルに作成するにはparseRequestでURL文字列を引数にするだけです。返ってくるのはモナドです。IO内で普通に使えます。ちなみにRapidAPIのYahoo Finance APIを例にしています。会員登録すれば、無料でも使えます。

initReq <- parseRequest "https://apidojo-yahoo-finance-v1.p.rapidapi.com/market/get-summary"

これでRequestを作成できました。ではここにヘッダーをつけてみましょう。setRequestHeadersを使います。

:{
headers =
  [ ("x-rapidapi-host", "apidojo-yahoo-finance-v1.p.rapidapi.com")
  , ("x-rapidapi-key", "api key string")
  ]
:}

reqWithHeader = setRequestHeaders headers initReq

ヘッダーデータは、RequestHeadersという型なのですが、OverloadedStrings拡張を有効にしておけば、キーと値のタプルをリストにするだけで作れるので、特に型を気にする必要はありません。これでヘッダーを含むリクエストができました。この関数の注意点としては、このリクエストにすでにヘッダーが何か設定されていた場合、それが全て捨てられてしまうことです。なのでこの関数を使う場合は、まだヘッダーが一つもついていないRequestに対して使うべきです。ヘッダーを設定する関数は他にもsetRequestHeaderaddRequestHeaderがあります。

次にクエリパラメータを設定します。setRequestQueryStringを使います。

:{
queryParams =
  [ ("region", Just "US")
  , ("lang", Just "en")
  , ("symbol", Just "^DJI")
  , ("interval", Just "1d")
  , ("range", Just "1d")
  ]
:}

req = setRequestQueryString queryParams reqWithHeader

クエリパラメータも、専用のデータ型が定義されていますが、ヘッダーの時と同じく簡単に作れます。他にもaddToRequestQueryStringという関数もあります。

これでヘッダーとクエリパラメータ付きのRequestが作成できました。実際にリクエストする時は、初めに使ったhttpLBSを使って、

res <- httpLBS req

とするだけです。

参考

Haskellから簡単にWeb APIを叩く方法

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です