scotty使ってみた
scottyはHaskellの軽量なWebフレームワーク。
yesodちょっとゴツそうな感じがして若干ためらいがあって、個人的にはサーバサイドはAPI呼び出しだけぐらいの使い方が理想なので、もっとお手軽なかんじののがよいなーというのでscottyというのを使ってみた。
scottyのリポジトリ
https://github.com/scotty-web/scotty
とりあえず上記のリポジトリのwikiにリンクされてたscottyのチュートリアルがあったのでこれをやってみた。
http://adit.io/posts/2013-04-15-making-a-website-with-haskell.html
試してみた手元の環境はMacOSX10.10+ghc7.8.3+cabal1.20.0.3で、scottyのバージョンは0.9.0を使いました。
実際に動かして試してみたコードは以下に置いておいた。
https://github.com/minamijoyo/scotty-todo
まずはプロジェクトのディレクトリ作る。
$ mkdir scotty-todo $ cd scotty-todo $ cabal sandbox init
次にビルドの定義。scotty-todo.cabalを作って以下のようなかんじにした。
name: scotty-todo version: 0.0.1 synopsis: todo app using scotty homepage: https://github.com/minamijoyo/scotty-todo license: MIT author: minamijoyo maintainer: minamijoyo@gmail.com category: Web build-type: Simple cabal-version: >=1.8 executable scotty-todo main-is: Main.hs -- other-modules: build-depends: base ==4.7.* , wai , warp , http-types , resourcet , scotty , text , bytestring , blaze-html , persistent ==1.3.* , persistent-template ==1.3.* , persistent-sqlite ==1.3.* , persistent-postgresql ==1.3.* , monad-logger ==0.3.* , heroku , transformers , wai-middleware-static , wai-extra , time
ghcのバージョンとかの依存関係の都合でチュートリアルのサンプルからbaseとmonad-loggerのバージョンを若干修正してます。
Main.hsを以下のようなかんじで作ってまずはHello Worldする。
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty main = scotty 3000 $ do get "/" $ do html "Hello World!"
ビルドしてみる。
$ cabal update $ cabal install
ちなみにpostgresqlが入ってないと以下のようなエラーが出た。
cabal: Error: some packages failed to install: persistent-postgresql-1.3.1.3 depends on postgresql-libpq-0.9.0.1 which failed to install. postgresql-libpq-0.9.0.1 failed during the configure step. The exception was: ExitFailure 1 postgresql-simple-0.4.8.0 depends on postgresql-libpq-0.9.0.1 which failed to install. scotty-todo-0.0.1 depends on postgresql-libpq-0.9.0.1 which failed to install.
なんでsqliteとpostgresql両方使ってんのとか思ったら、なんかチュートリアルの後半でHerokuにデプロイしてるところでpostgresqlを使ってるっぽい。なので手元の環境にもpostgresqlをインストールしておく。既に入ってる人は気にしないで下さい。
$ brew install postgresql $ cabal install
で、ビルドできたら、以下のコマンドでWebサーバを起動する。
$ .cabal-sandbox/bin/scotty-todo
ブラウザでhttp://localhost:3000にアクセスしてHello World!が見えればOK。
とりあえずここまででscotty動かすまでできた。
scotty自体の使い方はチュートリアルの説明読んでもらった方が早いのだけど、チュートリアルの説明で若干捕捉。scottyのバージョンの違いからかヘッダーの取得でreqHeaderという関数がなかった。
get "/agent" $ do agent <- reqHeader "User-Agent" text agent
調べたところheaderという関数があったのでheaderを使えばよいっぽい。型はこんなかんじ。
header :: Text -> ActionM (Maybe Text)
微妙にMaybeになってるので
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty main :: IO () main = scotty 3000 $ do get "/agent" $ do agent <- header "User-Agent" case agent of Just ua -> text ua Nothing -> text "no User-Agent header"
としないと動かない。
また、ヘッダーの設定で、headerという関数じゃなくて
get "/adit" $ do status status302 header "Location" "http://www.adit.io"
setHeaderという関数という関数になってるので、こんなかんじにしないと動かない。
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty import Network.HTTP.Types main :: IO () main = scotty 3000 $ do get "/adit" $ do status status302 setHeader "Location" "http://www.adit.io"
その他Persist周りで
Database.Persist.GenericSqlがないのでDatabase.Persist.Sqlを使う。
import Database.Persist.Sql
persistがないのでpersistLowerCaseを使う
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Post title String content Text createdAt UTCTime deriving Show |]
で、runDbのところ
runDb :: SqlPersist (ResourceT IO) a -> IO a runDb query = runResourceT . withSqliteConn "dev.sqlite3" . runSqlConn $ query readPosts :: IO [Entity Post] readPosts = (runDb $ selectList [] [LimitTo 10])
こんなかんじで書いてると、Monad.Loggerが0.3.0からMonadLogger IOのインスタンスがなくなってるっぽくてこんなエラーが出る。
Main.hs:43:22: No instance for (MonadLogger IO) arising from a use of ‘selectList’ In the second argument of ‘($)’, namely ‘selectList [] [LimitTo 10]’ In the expression: (runDb $ selectList [] [LimitTo 10]) In an equation for ‘readPosts’: readPosts = (runDb $ selectList [] [LimitTo 10]) cabal: Error: some packages failed to install: scotty-todo-0.0.1 failed during the building phase. The exception was: ExitFailure 1
ぐぐってみると、NoLoggingTで包むと型が合うよとの情報があり
database - No instance for Control.Monad.Logger.MonadLogger when attempting to use persistent - Stack Overflow
こんなかんじで試して見たら型があった。
runDb :: SqlPersist (ResourceT (NoLoggingT IO)) a -> IO a runDb query = runNoLoggingT . runResourceT . withSqliteConn "dev.sqlite3" . runSqlConn $ query readPosts :: IO [Entity Post] readPosts = (runDb $ selectList [] [LimitTo 10])
なんかscottyの使い方の勉強のはずなのに半分ぐらいPersistentの使い方の勉強になってきたような。
Herokuへのデプロイは力尽きて諦めた。。。