Haskellで超簡単にWebアプリケーションを作る(モナドも出てこないよ)

# はじめに

Haskellは静的純粋関数型プログラミング言語と言われるジャンルの言語であり、型や状態に厳しくWeb開発に向いていないと思われる方が多いと思います。

**しかし**

- 型の力によるクリーンアーキテクチャ
- 抽象化による高級な記述
- 圧倒的な型推論による軽量言語のような書き心地
- 意外と高いシングルスレッド性能
- パフォーマンスも非常に高く, 書きやすい並行並列処理

上記のような利点があり、実はWeb開発に非常に有用な言語であると思っています。

HaskellでのWeb開発における標準的なインターフェースである`wai`(web application interface)、そのインターフェースのアプリケーションを動作させる標準的なサーバーである`warp`という二つのライブラリを用いてボトムアップでWebアプリを作っていき、徐々に応用させていこうと思います。

`wai`と`warp`はHaskellのWeb開発であるデファクトスタンダードのようなWAFである`Yesod`と`Servant`の内部で使われています。

### Version

stackを利用しており、resolverはlts-15.11となります。

# Hello Wai!

8000番のポートにアクセスすると常に "hello wai" というレスポンスを返すサーバーを作ってみましょう。

## 依存するパッケージ

```yaml:package.yaml
dependencies:
- base >= 4.7 && < 5
- wai
- warp
- http-types
```

## ソースコード

ソースコード全体はこちらですが、チラ見程度でいいでしょう。

importや、型注釈を消せば実質**実装は2行**という簡単さです。

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as HTypes

main :: IO ()
main = Warp.run 8000 helloApp

helloApp :: Wai.Application
helloApp req send = send $ Wai.responseBuilder HTypes.status200 [] "hello wai"
```

こちらのソースコードをビルドして実行すると、`localhost:8000`で`hello wai`と表示されます。

全体像が掴みやすいように、main関数からトップダウンで一つ一つ解説します

## 解説

### run

main関数はこちらになります。

```haskell
main :: IO ()
main = Warp.run 8000 helloApp
```

run関数は`Application`型と`Port`型を引数にとって、Webサーバー立ち上げてくれる関数です。

```haskell
Warp.run :: Port -> Application -> IO ()
```

PortはIntの型シノニムになっています。

```haskell
type Port = Int
```

### Application

run関数に渡すApplicationとは何かって話になりますが、ただのリクエストを受けてレスポンスを返す関数です。

```haskell
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceivedSource
```

正しくは第二引数に`Response `を`IO ResponseRecived`にする関数(sendやrespondと呼ぶ)が渡されるので、その関数をresponseに適用して返す必要がありますが、私たちの興味は`Response`を構築することだけです。

```haskell
helloApp :: Wai.Application
helloApp req send = send response -- このresponseを構築したい、あとはsendに渡すだけで良い
```

responseを構築しましょう。

```haskell
helloApp :: Wai.Application
helloApp req send = send $ Wai.responseBuilder HTypes.status200 [] "hello wai"
```

`responseBuilder`関数は**ステータスコード**と**ヘッダーのリスト**と**ボディ**を引数にして、responseを作る関数です。

```haskell
responseBuilder :: Status -> ResponseHeaders -> Builder -> Response
```

ここでいう`Builder`とは`response`を作るという意味ではなく、ボディの文字列の型を指しています。
`LazyByteString`型の文字列からresponseを作りたい場合は`responseLBS`という関数も提供しています。


### Hello Wai!

これらを組み合わせることで、どんなrequestが来ても ステータスコード200で"hello wai"を返すWebサーバーが完成しました。

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as HTypes

main :: IO ()
main = Warp.run 8000 helloApp

helloApp :: Wai.Application
helloApp req send = send $ Wai.responseBuilder HTypes.status200 [] "Hello Wai!"
```

# ルーティング

上記のアプリケーションはrequestの情報を一切利用せずに常に同じレスポンスを返していました。

requestを利用する第一段階として、URLによるルーティング機能を追加してみましょう。

## 仕様

rootは最初に作ったレスポンス、追加で"foo"と"not found"を作り、いい感じにルーティングします。

|URL|Response|Status Code|
|---|---|---|
|/|"hello wai"|200|
|/foo  |"bar buz"|200|
|other |"not found"|404|

## ソースコード

ソースコード全体はこちらですが、雰囲気を見る程度で大丈夫です。

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as HTypes

main :: IO ()
main = Warp.run 8000 router

router :: Wai.Application
router req =
  case Wai.pathInfo req of
    []       -> helloApp req
    ["foo"]  -> fooApp req
    _        -> notFoundApp req

helloApp :: Wai.Application
helloApp req send 
  = send $ Wai.responseBuilder HTypes.status200 [] "hello wai"

fooApp :: Wai.Application
fooApp req send
  = send $ Wai.responseBuilder HTypes.status200 [] "bar buz"

notFoundApp :: Wai.Application
notFoundApp req send
  = send $ Wai.responseBuilder HTypes.status404 [] "not found"
```

## 解説

`helloApp :: Wai.Application`
`fooApp :: Wai.Application`
`notFoundApp :: Wai.Application`
この三つを追加しましたが、特に解説は要らないと思います。

### router


routerはパスの情報を判定して上述の各アプリケーションを返す関数とすることで、ルーティングを表現します。

```haskell
router :: Wai.Application
router req send =
  case Wai.pathInfo req of
    []       -> helloApp req send
    ["foo"]  -> fooApp req send
    _        -> notFoundApp req send
```

`req`という引数は`Application`型の通り、`Request`型となっています。

```haskell
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceivedSource
```

### Request型

`Request`型はHTTPのリクエストをうまく抽象化したレコード型になっていて、リクエストの様々な情報に簡単にアクセスできるようになっています。

フィールドの一覧はこちらです。

```haskell
data Request = Request {
     requestMethod          :: H.Method
  ,  httpVersion            :: H.HttpVersion
  ,  rawPathInfo            :: B.ByteString
  ,  rawQueryString         :: B.ByteString
  ,  requestHeaders         :: H.RequestHeaders
  ,  isSecure               :: Bool
  ,  remoteHost             :: SockAddr
  ,  pathInfo               :: [Text]
  ,  queryString            :: H.Query
  ,  requestBody            :: IO B.ByteString
  ,  vault                  :: Vault
  ,  requestBodyLength      :: RequestBodyLength
  ,  requestHeaderHost      :: Maybe B.ByteString
  ,  requestHeaderRange     :: Maybe B.ByteString
  ,  requestHeaderReferer   :: Maybe B.ByteString
  ,  requestHeaderUserAgent :: Maybe B.ByteString
  }
```

リクエストの情報はこちらから全て取得することができます。

これらのフィールドにアクセスするための同名の関数がexportされているので、今回はpathの情報を取得するので、pathInfo関数を利用します。

```haskell
pathInfo :: Request -> [Text]
```

pathInfoの戻り値の例はこのようになります。

|URL|Return|
|---|---|
|/|`[]`|
|/foo  |`["foo"]`|
|/foo/bar |`["foo", "bar"]`|

この関数の戻り値をパターンマッチで各アプリケーションに振り分けることにより、routerを再現することができます。

最終的にrouterを走らせることにより、routing機能がついたwebサーバーが立ち上がります。

ソースコード再掲

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as HTypes

main :: IO ()
main = Warp.run 8000 router

router :: Wai.Application
router req =
  case Wai.pathInfo req of
    []       -> helloApp req
    ["foo"]  -> fooApp req
    _        -> notFoundApp req

helloApp :: Wai.Application
helloApp req send 
  = send $ Wai.responseBuilder HTypes.status200 [] "hello wai"

fooApp :: Wai.Application
fooApp req send
  = send $ Wai.responseBuilder HTypes.status200 [] "bar buz"

notFoundApp :: Wai.Application
notFoundApp req send
  = send $ Wai.responseBuilder HTypes.status404 [] "not found"
```

# まとめ

HaskellでのWeb開発における使いやすいインターフェースのwaiとそれを簡単に動かしてくれるwarpの紹介でした。
HaskellでもWeb開発いけそうやん！って思ってもらえたら幸いです。

# 次回予告
これだけだとまともなアプリケーションを作れないので、JSONを返すRESTful APIや、ログイン機能やCookie、Session、DBを利用するアプリケーションなどをwaiで作る記事でも書きたいなと思います。