yunomuのブログ

趣味のこと

HaskellでCGI

最近「Haskellで~」ってタイトルばっかですね。
今回のも「CGIを書く」とかでいいのかもしれない。っていうほどHaskellのことばかり書いてるわけでもないのでそれはやり過ぎかもしれない。
たぶんそのうちRubyの記事も書きます。

Hello World

で、HaskellでCGIを書きます。
index.cgi.hs

main :: IO ()
main = do
  putStrLn "Content-type: text/plain"
  putStrLn ""
  putStrLn "It will be fine, tomorrow!"

おめでとうございます。これでHaskellでCGIが書けました!

でもこの程度ならシェルスクリプトでいいじゃんって話です。

CGIライブラリを使う

CGIライブラリを使って、入力を受け取ったりできるようにしてみましょう。

% cabal install cgi

これのHello World的なものはマニュアルに書いてあります。
Network.CGI

import Network.CGI

cgiMain :: CGI CGIResult
cgiMain = output "Hello World!"

main :: IO ()
main = runCGI (handleErrors cgiMain)

outputは自動的にヘッダを付加して出力してくれます。

ただし、ここで自動的に付加されるヘッダには何故かcharsetが指定されているので、日本語を表示しようとするとmetaタグを書こうが何をしようがブラウザによっては文字化けが起きる。
なので結局ヘッダは自前で設定する羽目になる。こういう感じ。

import Network.CGI

cgiMain :: CGI CGIResult
cgiMain = do
  setHeader "Content-type" "text/plain"
  output "Hello World!"

main :: IO ()
main = runCGI (handleErrors cgiMain)

setHeaderは1回以上呼びだすとデフォルトのヘッダが上書きされて消える。2回以上呼び出すと呼び出した数だけヘッダが付加される。まあ見たまんま感じたまんまですが、実装したくねぇなぁって感じです。

できたファイルはそのまま実行もできるので、パラメータが無い時の表示チェックとかに使えます。まあこの辺は普通のCGIスクリプトと同じです。

% ghc index.cgi.hs
% ./index.cgi
Content-type: text/plain

Hello World!

putStrLnとか書くのめんどくさい

ここで直接は関係ないんですが、HTMLを出力するの面倒ですよね。
Haskellにもテンプレートエンジンとかあるけど、そこまでやんなくても、とりあえずヒアドキュメント的なの無いんですか?
まあ、無いので作ります。
Str.hs

module Str(str) where

import Language.Haskell.TH
import Language.Haskell.TH.Quote

str :: QuasiQuoter
str = QuasiQuoter
    { quoteExp  = stringE
    , quotePat  = undefined
    , quoteType = undefined
    , quoteDec  = undefined
    }

これはこんな感じで使います。
StrTest.hs

{-# LANGUAGE QuasiQuotes #-}
import Str

main :: IO ()
main = putStr [str|こんにちは
こんにちは
こんばんは|]

実行。

% ghc StrTest.hs
% ./StrTest
こんにちは
こんにちは
こんばんは

変数の埋め込みとかはできませんが、やりたい方はruiccさんのブログとか読むといいんじゃないでしょうか。
続・Template Haskell入門 -- QuasiQuotes編 - think and error
TemplateHaskellに負けないで!
私はとりあえずいいや。

入力を受け取る

getInputを使います。

{-# LANGUAGE QuasiQuotes #-}
import Network.CGI

import Str

contents :: String -> String
contents body = header ++ body ++ footer
  where
    header = [str|<html>
<body>
<form action="." method="POST">
<input type="text" name="moge">
<input type="submit">
</form>
|]
    footer = "</body></html>"

cgiMain :: CGI CGIResult
cgiMain = do
  setHeader "Content-type" "text/html"
  moge <- getInput "moge"
  output $ contents $ maybe "" id moge

main :: IO ()
main = runCGI (handleErrors cgiMain)

これで、フォームに入れた文字がフォームの下に出たり出なかったり。

やっぱりテンプレートエンジン使ったほうがいいような気がします。