http-conduitというパッケージが使いやすそうです。
前準備
ghciでやりましょう。以降のコードスニペットは全て、ghci上にコピペすれば動くはずです。
http-conduit
、http-types
、aeson
、lens
、lens-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
型として推論してくれます。戻り値はモナドに包まれていますが、上記のres
はIO
のコンテクスト内で中身を取り出した状態になっているので、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
以外にもいくつかリクエストを送る関数が用意されています。
httpBS
はhttpLBS
とほぼ同じですが、戻り値の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
に対して使うべきです。ヘッダーを設定する関数は他にもsetRequestHeader
とaddRequestHeader
があります。
次にクエリパラメータを設定します。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
とするだけです。