【Servant】(1) Wai - Qiita
【Servant】(2) Servantチュートリアル - Qiita
【Servant】(3) エンドポイントを増やす - Qiita
【Servant】(4) URLパラメータをハンドラの引数とする - Qiita
【Servant】(5) JSON - Qiita
【Servant】(6) HTML - Qiita
【Servant】(7) Post Data - Qiita
【Servant】(8) Another Monad - Qiita
【Servant】(9) Handlerモナド - Qiita
【Servant】(10) SQLite - Qiita
【Servant】(11) Servant-Client - Qiita
【Servant】(12) Basic 認証 - Qiita
今回からは、以下のexampleを取り上げていきます。
Example Projects - haskell-servant/servant@github
今回の記事は、前回の記事「【Servant】(5) JSON 」の復習・応用になります。
1. HTMLを返すAPIを実装する
ここではHTMLを返すAPIを定義します。
まずは前回の記事「【Servant】(5) JSON」を踏まえて、content type "text/html" に対応する data型HTMLを定義し、AcceptクラスとMimeRenderクラスのインスタンスとして実装していきます。
data HTML -- Here is our HTML type that we will use in the type of the endpoint.
-- We don't need a constructor here since we ll ever have to deal with a value of this type.
instance Accept HTML where -- This instance is what makes the endpoints with this content type
contentType _ = "text/html" -- return content with a content type header with "text/html" in it.
instance MimeRender HTML String where -- This instance where we define how a value of type string is
mimeRender _ val = C.pack val -- is encoded as an html value. Note that we are not converting
-- the string to an value of type HTML, but just to a Bytestring that
-- represents the HTML encoding. As I said earlier, we won't ever
-- have to deal with a value of type HTML
instance MimeRender HTML Int where -- Same as before. This instance defines how an Int will be converted
mimeRender _ val = C.pack $ show $ val -- to a bytestring for endpoints with HTML content type.
まず 「data HTML」を宣言します。型名だけを定義し、値コンストラクタについては何も触れません。HTML型の値については今後触れることはないからです。
次に「instance Accept HTML」を定義し、contentType が "text/html" を返すようにします。
最後に「instance MimeRender HTML String」の定義です。StringをLazy ByteString へ変換(エンコーディング)します。このLazy ByteStringはHTML encodingを表現していますが、HTML型の値へ変換しているわけではありません。そもそも「data HTML」宣言においてHTML型の値は定義していませんでしたね。
pack :: [Char] -> ByteString
APIの定義です。 Get '[HTML] String と Get '[HTML] Int を利用します。これは上で「instance MimeRender HTML String」と「instance MimeRender HTML Int」を実装に対応します。
type ServantType = "name" :> Get '[HTML] String
:<|> "age" :> Get '[HTML] Int
APIに対するHandllerを定義します。
handlerName :: Handler String
handlerName = return "sras"
handlerAge :: Handler Int
handlerAge = return 30
server :: Server ServantType
server = handlerName :<|> handlerAge
APIとHandllerでApplicationを実装します。
app :: Application
app = serve (Proxy :: Proxy ServantType) server
2. 全ソース
HtmlContentソース本体
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module HtmlContent
( runServant
) where
import Servant ( QueryParam
, PlainText
, Get
, Proxy(..)
, type (:>) -- Syntax for importing type operator
, type (:<|>)
, (:<|>)(..)
, Accept(..)
, MimeRender(..)
)
import Servant.Server (Handler, Server, Application, serve)
import Network.Wai.Handler.Warp (run)
import Control.Monad.IO.Class (liftIO)
import Data.ByteString.Lazy.Char8 as C
-- In this example, we add an Html type and make endpoints
-- with this content type return a content type header with
-- "text/html" in it.
data HTML -- Here is our HTML type that we will use in the type of the endpoint.
-- We don't need a constructor here since we ll ever have to deal with a value of this type.
instance Accept HTML where -- This instance is what makes the endpoints with this content type
contentType _ = "text/html" -- return content with a content type header with "text/html" in it.
instance MimeRender HTML String where -- This instance where we define how a value of type string is
mimeRender _ val = C.pack val -- is encoded as an html value. Note that we are not converting
-- the string to an value of type HTML, but just to a Bytestring that
-- represents the HTML encoding. As I said earlier, we won't ever
-- have to deal with a value of type HTML
instance MimeRender HTML Int where -- Same as before. This instance defines how an Int will be converted
mimeRender _ val = C.pack $ show $ val -- to a bytestring for endpoints with HTML content type.
type ServantType = "name" :> Get '[HTML] String
:<|> "age" :> Get '[HTML] Int
handlerName :: Handler String
handlerName = return "<h1>sras</h1>"
handlerAge :: Handler Int
handlerAge = return 30
server :: Server ServantType
server = handlerName :<|> handlerAge
app :: Application
app = serve (Proxy :: Proxy ServantType) server
runServant :: IO ()
runServant = run 4000 app
-- Now let us see how the app behaves
--
--
-- curl -v 127.0.0.1:4000/age
-- * Trying 127.0.0.1...
-- * Connected to 127.0.0.1 (127.0.0.1) port 4000 (#0)
-- > GET /age HTTP/1.1
-- > Host: 127.0.0.1:4000
-- > User-Agent: curl/7.47.0
-- > Accept: */*
-- >
-- < HTTP/1.1 200 OK
-- < Transfer-Encoding: chunked
-- < Date: Sun, 12 Nov 2017 13:16:50 GMT
-- < Server: Warp/3.2.13
-- < Content-Type: text/html
-- <
-- * Connection #0 to host 127.0.0.1 left intact
-- 30
メイン
module Main where
import HtmlContent
main :: IO ()
main = runServant
使用するパッケージ
dependencies:
- base >= 4.7 && < 5
- servant
- servant-server
- aeson
- time
- wai
- warp
- http-media
- bytestring
3. 実行結果
nameを叩く
$ curl -v 127.0.0.1:4000/name
* Trying 127.0.0.1:4000...
* TCP_NODELAY set
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 127.0.0.1 (127.0.0.1) port 4000 (#0)
> GET /name HTTP/1.1
> Host: 127.0.0.1:4000
> User-Agent: curl/7.67.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Date: Sun, 23 Feb 2020 03:32:55 GMT
< Server: Warp/3.3.5
< Content-Type: text/html ###コンテントタイプ
<
{ [26 bytes data]
100 13 0 13 0 0 13000 0 --:--:-- --:--:-- --:--:-- 13000
<h1>sras</h1> ###返り値
ageを叩く
$ curl -v 127.0.0.1:4000/age
* Trying 127.0.0.1:4000...
* TCP_NODELAY set
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 127.0.0.1 (127.0.0.1) port 4000 (#0)
> GET /age HTTP/1.1
> Host: 127.0.0.1:4000
> User-Agent: curl/7.67.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Date: Sun, 23 Feb 2020 03:33:14 GMT
< Server: Warp/3.3.5
< Content-Type: text/html ###コンテントタイプ
<
{ [15 bytes data]
100 2 0 2 0 0 2000 0 --:--:-- --:--:-- --:--:-- 2000
30 ###返り値
今回は以上です。