Hello world in Haskell with Servant
連載一覧
Haskell Servant フレームワークの便利な使い方などを紹介する連載記事です。
今後は以下に記事を追加していきます。
(2016年7月22日 6つ目の記事を追加しました!)
概要
本記事では Haskell Servant の導入方法をご紹介します。
記事内のすべてのコードは github リポジトリに置いてあります。
http://github.com/algas/haskell-servant-cookbook
想定する読者
- Haskell の入門書を読んだことがある
- 言語を問わず Web アプリケーションを触ったことがある
- RDB (Relational Database) の基本的な知識がある
Haskell や Web フレームワーク自体について本記事では詳しく説明しません。
Haskell Servant とは?
Haskell の Web フレームワークで、以下の特徴があります。
- 機能は少なめでシンプル(フルスタックではない)
- 型による強い制限を持つ
- Client と Server を同じ API から生成できる
プロジェクト作成
haskell-stack がインストールできているものとします。
Terminal で下記のコマンドを実行すると、myproject ディレクトリに servant プロジェクトの雛形が作成されます。
$ stack new myproject servant
API
"/" で Hello World を返し、
"/users/(name)/(age)" で User を返すシンプルな API を定義します。
User 型は name, age という属性を持っています。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module HelloApi where
import Data.Aeson
import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics
import Servant.API
data User = User
{ name :: Text
, age :: Int
} deriving (Eq, Show, Read, Generic)
instance FromJSON User
instance ToJSON User
type HelloAPI = Get '[PlainText] Text
:<|> "user" :> Capture "name" Text :> Capture "age" Int :> Get '[JSON] User
helloApi :: Proxy HelloAPI
helloApi = Proxy
Server
HelloApi を呼び出し、サーバとして機能させます。
{-# LANGUAGE OverloadedStrings #-}
module Main where
import HelloApi
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import Servant.API
server :: Server HelloAPI
server = hello :<|> user
where
hello = return "Hello world"
user n a = return $ User n a
app :: Application
app = serve helloApi server
main :: IO ()
main = do
run 8080 app
Client
HelloApi を呼び出し、クライアントとして機能させます。
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.Trans.Except (ExceptT, runExceptT)
import Data.Text (Text)
import qualified Data.Text as T
import HelloApi
import Network.HTTP.Client (Manager, defaultManagerSettings,
newManager)
import Servant.API
import Servant.Client
hello :<|> user = client helloApi
queries :: Manager -> BaseUrl -> ExceptT ServantError IO (Text, User)
queries manager baseurl = do
h <- hello manager baseurl
us <- user "John Smith" 26 manager baseurl
return (h, us)
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
let baseUrl = BaseUrl Http "localhost" 8080 ""
res <- runExceptT $ queries manager baseUrl
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right p -> do
print p
動作確認
- ビルドする
$ stack build
- サーバを起動する
$ stack exec hello-server
- curl でサーバにアクセスしてみる
$ curl http://localhost:8080
Hello world
$ curl http://localhost:8080/users/hoge/3
{"age":3,"name":"hoge"}
- クライアントでサーバにアクセスする
$ stack exec hello-client
("Hello world",User {name = "John Smith", age = 26})
クライアントからサーバにアクセスして値を取得することができました。
サーバとクライアントで同じAPIを使っているので、冗長な記述を減らせるのが Servant のメリットの1つです。
動作環境
- stack:lts-6.0
- servant-0.7
参考文献
連載予定
Haskell Servant 入門シリーズを連載で執筆予定です。
お楽しみに。