yunomuのブログ

酒とゲームと上から目線

Wai + WarpでWebサーバを作る

WarpってWebサーバらしいですよ。
いやまあ今回はそういう前置きはいいんですけど、

Yesodの人ことSnoymanさんが作っているWaiとWarpでWebサーバを書いてみようと思います。

まずは調査。
Waiとはなんぞや。
Network.Wai
Web Application Interfaceらしいです。単体だとナンノコッチャという感じですが、ドキュメントを見るとHTTPのRequestとResponseっぽい型の定義があって、それらを扱うApplicationという型があります。

定義:

type Application = Request -> ResourceT IO Response

RequestをもらってResponseを返す関数がApplicationです。まあそのまんまですね。
RequestはHTTPの定義通りなので省略、Responseはなんか若干めんどくさいけど後で少し。

それとは別にWarpというのがあります。
こいつは、いわゆるWebサーバの本体っていうか、HTTPのコネクションを管理したり、ソケットを管理したりするやつみたいです。
Network.Wai.Handler.Warp

run :: Port -> Application -> IO ()

こいつを使います。

PortはIntなのでいいんですが、このためにはApplicationを作らなければいけません。

ということで、簡単に作りました。

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Blaze.ByteString.Builder.Char.Utf8

server :: Application
server _ = return $ ResponseBuilder status200 [] $ fromString "hello"

main :: IO ()
main = run 8080 server

動かす。

% ghci Main.hs
...略
*Main> main

クライアント側。

% curl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: Warp/1.3.0
Transfer-Encoding: chunked

hello

おわり。

ここで、Responseの型がこんななので、

import qualified Network.HTTP.Types as H
import qualified Data.Conduit as C

data Response
    = ResponseFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)
    | ResponseBuilder H.Status H.ResponseHeaders Builder
    | ResponseSource H.Status H.ResponseHeaders (C.Source (C.ResourceT IO) (C.Flush Builder))
  deriving Typeable

今回はとりあえず文字列を返すということでResponseBuilderを使ってみましたが、ファイルをそのまま返したりもできるみたいです。

server :: Application
server _ = return $ ResponseFile status200 [] "index.html" Nothing

さっきと同様に、データを準備して動かす。

% echo 明日は晴れるでしょう > index.html
% curl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: Warp/1.3.0
Content-Length: 31

明日は晴れるでしょう

ファイル用意せずにやるとちゃんと404 NotFoundが返ってきます。賢いですね。
レスポンスヘッダにContent-typeくらい書けよという気もしますが。

あとは定義の通り、ConduitのSourceを返すこともできる。

それと、わざわざWarpでサーバ立てなくてもいいように、Waiだけをテストするためのパッケージもあります。
Network.Wai.Test
さすが気が利くというか、ありがたいことです。