2016年12月追記: この記事のDB(MySQL)編はこちら。
wai(Haskell製のWebアプリケーション規格)に準拠し、warp(wai準拠のHaskell製Webサーバ)上で動作するWebアプリケーションの開発手順をまとめる。基本性能を際立たせるため、便利なフレームワークはあえて使用しない。
1. サンプルアプリケーションの概要
サーバの現在時刻をクライアント側で装飾して表示するWebアプリケーション。動作サンプルは下記を参照。
このアプリケーションは、下記のエンドポイントからなる。
- /posixtime — GETでサーバの現在のUNIX時間(ミリ秒単位)を返す。
- /main.html — 画面のHTML。クライアント側のエントリポイント。
- /main.js — main.html で読み込まれる JavaScript。posixtime をGETして装飾してから画面に表示する。
2. 開発環境(stack) の準備
下記から stack をダウンロードしてインストールする。解凍してPATHを通すだけでよい。
http://docs.haskellstack.org/en/stable/README.html
下記のようにバージョンが表示できれば準備完了。
$ stack --version Version 1.0.2, Git revision fa09a980d8bb3df88b2a9193cd9bf84cc6c419b3 (3084 commits) x86_64
3. サンプルアプリケーションのコードの取得
コードは下記に置いてある。
https://github.com/mitsuji/wai-example
ソースコードを clone して、
$ git clone https://github.com/mitsuji/wai-example.git Cloning into 'wai-example'... remote: Counting objects: 12, done. remote: Compressing objects: 100% (10/10), done. remote: Total 12 (delta 0), reused 12 (delta 0), pack-reused 0 Unpacking objects: 100% (12/12), done. Checking connectivity... done.
ディレクトリに cd してから、
$ cd wai-example/
下記のコマンドを叩くとghc(Haskellのコンパイラ)などの環境が自動的に準備される。いろいろとダウンロードされるため、初めての時は少し時間がかかるかもしれない。
$ stack setup
ディレクトリ内のファイルのうち、開発に直接関係があるのは下記の三つだけである。
- app/Main.hs — Webアプリケーション本体のソースコード。
- static/main.html — Webアプリケーション動作時にアクセス可能となる main.html そのもの。
- static/main.js — Webアプリケーション動作時にアクセス可能となる main.js そのもの。
4. REPL(ghci) を使用した動作確認
下記のコマンドでHaskell の REPL である ghci に入ることが出来る。
$ stack ghci The following GHC options are incompatible with GHCi and have not been passed to it: -threaded Using main module: 1. Package `wai-example' component exe:wai-example-exe with main-is file: /home/mitsuji/Downloads/hoge/wai-example/app/Main.hs Configuring GHCi with the following packages: wai-example GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help [1 of 1] Compiling Main ( /home/mitsuji/Downloads/hoge/wai-example/app/Main.hs, interpreted ) Ok, modules loaded: Main. *Main>
REPLを使用すると、ソースコード内の関数を直接評価することが出来る。例えば、Main.hs内のこの関数は
getPOSIXTime' :: IO Int getPOSIXTime' = do pt_seconds <- getPOSIXTime return $ truncate $ pt_seconds * 1000
このようにして直接評価してみることが出来る。
*Main> getPOSIXTime' 1455622245689
ソースコードを書き換えたときは、下記のようにしてリロードすれば変更が反映される。
*Main> :l app/Main.hs [1 of 1] Compiling Main ( app/Main.hs, interpreted ) Ok, modules loaded: Main.
また、下記のようにしてコマンドラインパラメータ付きでmain関数を評価することもできる。この場合はWebサーバが実際に起動する。
*Main> :main 0.0.0.0 9999
ここで、Main.hs 内の関数を見てみよう。
main は Haskell のプログラムのエントリポイントとなる関数であり、プロセス起動時に最初に評価される。ここでは、コマンドラインパラメータからWebサーバの待ち受けIPアドレスとポート番号を取得して、warp を起動している。HTTPリクエスト発生時に評価される関数には routerApp が指定されている。
main :: IO () main = do host:port:_ <- getArgs Warp.runSettings ( Warp.setHost (fromString host) $ Warp.setPort (read port) $ Warp.defaultSettings ) $ routerApp
routerApp ではリクエストされたURLを元に評価する関数を振り分けている。/posixtime がリクエストされれば dateAppが、それ以外がリクエストされれば staticApp が評価される。
routerApp :: Wai.Application routerApp req respond | (["posixtime"] == path) = dateApp req respond | otherwise = staticApp req respond -- static html/js/css files where path = Wai.pathInfo req
dateApp は リクエスト毎にその時点でのサーバーの時刻をレスポンスとして返している。Int型で得られるUNIX時間をレスポンスとして返すため、Int => String => Text => ByteString(strict) => ByteString(lazy) の変換が行われている。
dateApp :: Wai.Application dateApp req respond = do pt_milliseconds <- getPOSIXTime' let pt_lbs = LBS.fromStrict $ encodeUtf8 $ T.pack $ show $ pt_milliseconds respond $ Wai.responseLBS H.status200 [("Content-Type","text/plain")] pt_lbs
staticApp は main.html や main.js を レスポンスとして返すための、通常のWebファイルサーバである。ここでは、staticディレクトリ以下のファイルを処理対象としている。
staticApp :: Wai.Application staticApp = Static.staticApp $ settings { Static.ssIndices = indices } where settings = Static.defaultWebAppSettings "static" indices = fromJust $ toPieces ["main.html"] -- default content
通常のWebファイルサーバであるため、Webアプリケーション動作中はコンパイルや再起動を行わなくても main.html や main.js を編集すれば次回リクエスト時に変更が反映される。
5. runghc を使用した動作確認
REPLに入らずに動作確認したいときは、下記のようにしてmain関数を評価することもできる。Haskellでは、スクリプト的な使い方も可能となっているのである。
$ stack runghc app/Main.hs 0.0.0.0 9999
6. ghc を使用した実行形式バイナリのビルド
下記のコマンドで実行形式バイナリがビルド(コンパイル)され、
$ stack build
下記のコマンドでインストールされ
$ stack install
~/.local/bin/wai-example-exe が生成される。実行形式バイナリの動作確認は下記のようにして行うことができるだろう。
$ ~/.local/bin/wai-example-exe 0.0.0.0 9999
wai(Webアプリケーションライブラリ)やwarp(Webサーバ)を含むすべてのライブラリがwai-example-exeに静的にリンクされるため、wai-example-exe と staticディレクトリ を ビルド環境と同一アーキテクチャ、同一OSのマシンにコピーすればそのまま動作する。
この時、カレントディレクトリ配下の static ディレクトリ内のファイルが静的Webコンテンツとして参照されるが、ビルド時に settings を下記のように書き換えると、staticディレクトリ内のファイルを実行形式バイナリにすべて埋め込むことができる。静的Webコンテンツも含め、Webアプリケーションを一つのファイルにまとめることができるので、場合によってはとても便利である。
staticApp :: Wai.Application staticApp = Static.staticApp $ settings { Static.ssIndices = indices } where settings = Static.embeddedSettings $(embedDir "static") indices = fromJust $ toPieces ["main.html"] -- default content
7. まとめ
- Haskell の 環境構築は stack を使えば超簡単。
- 一つのソースコードをまったく書き換えることなく REPL、スクリプト、実行形式バイナリの三態から利用可能。
- WebアプリケーションとWebサーバが一体のため、実行形式バイナリをフロントエンドエンジニア や Webデザイナー に渡せばそのまま開発環境として使ってもらえる。
- 実行形式バイナリでデプロイすれば、本番に必要な依存環境を最小化できるため、Vagrant や Docker などの環境構築ツールに頼らなくてよい。
- 静的Webコンテンツを実行形式バイナリに埋め込めば、より安全確実なバージョン管理とデプロイが可能に。
8. おまけ
筆者の利用実績はないが、IDE とか欲しい人向けの情報。
- Leksah -- Haskell で作られた Haskell用 IDE。
- Haskell for Mac -- Mac向け。本格的な開発ができるかは不明だけど楽しそう。
- haskell-idea-plugin -- IntelliJ用 のプラグイン。