Slide 1

Slide 1 text

HaskellͷWeb Application FrameworkΛࢼͯ͠ΈΔ 2015-11-07 LTۦಈ։ൃ20

Slide 2

Slide 2 text

ͻΉΒ ͱ΋ͻ͜ ͦΖͦΖScalaॳ৺ऀΛଔۀ͠ͳ͚Ε͹

Slide 3

Slide 3 text

10݄ͷπΠʔτ https://twitter.com/eielh/status/654319476620464129

Slide 4

Slide 4 text

10݄ͷπΠʔτ https://twitter.com/eielh/status/654319476620464129 神無月

Slide 5

Slide 5 text

Haskellͷ Web Application Framework

Slide 6

Slide 6 text

3強 • Yesod • Snap • Happstack IUUQTXJLJIBTLFMMPSH8FC'SBNFXPSLT

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

3強 • Yesod (wai & warp) • Snap • Happstack IUUQTXJLJIBTLFMMPSH8FC'SBNFXPSLT

Slide 11

Slide 11 text

wai + warp Ͱ͍ͬͯΈΑ͏

Slide 12

Slide 12 text

IUUQRJJUBDPNFJFMIJUFNTCGDGEFFEF

Slide 13

Slide 13 text

IUUQRJJUBDPNFJFMIJUFNTCGDGEFFEF waiの最小サンプルは試した

Slide 14

Slide 14 text

IUUQRJJUBDPNFJFMIJUFNTFBFFFCBBE

Slide 15

Slide 15 text

IUUQRJJUBDPNFJFMIJUFNTFBFFFCBBE herokuにもデプロイできた

Slide 16

Slide 16 text

よーし、Web APIをつくるぞー

Slide 17

Slide 17 text

wai ϕʔεͷϑϨʔϜϫʔΫ

Slide 18

Slide 18 text

wai ベースのフレームワーク • scotty • Spock • yesod • MFlow

Slide 19

Slide 19 text

೉қ౓ ܕܕܕʜ 4UBCJMJUZ TDPUUZ ௿ ௿ FYQFSJNFOUBM 4QPDL த த FYQFSJNFOUBM :FTPE ߴ ߴ 4UBCMF .'MPX ࢼ͞ͳ͔ͬͨ 8FC"1*ʹ͸޲͔ͳͦ͏ͩͬͨͷͰ

Slide 20

Slide 20 text

΍ͬͯΈΑ͏

Slide 21

Slide 21 text

Web API͢Δͷʹ͍ͨ͜͠ͱ •ΤϯυϙΠϯτͷߏங •JSONͷੜ੒ •JSONͷಡऔ •HTTPϔομͷಡऔ •HTTP΁ομͷઃఆ

Slide 22

Slide 22 text

લ४උ ଍͠ࢉͷͰ͖Δ७ਮͳWeb APIΛͭ͘Δ ଍͠ࢉΛ͢ΔυϝΠϯϩδοΫΛ༻ҙ

Slide 23

Slide 23 text

{-# LANGUAGE DeriveGeneric #-} module Add where import Prelude hiding (add) import GHC.Generics (Generic) import Data.Aeson hiding (Result) data Result = Result { result :: Integer } deriving (Generic, Show) data Request = Request { x :: Integer, y :: Integer } deriving (Generic, Show) instance ToJSON Result instance FromJSON Result instance ToJSON Request instance FromJSON Request add :: Integer -> Integer -> Result add x y = Result $ x + y

Slide 24

Slide 24 text

࣮ߦྫ >>>> add 1 2 Result { result = 3}

Slide 25

Slide 25 text

࣮ߦྫ >>>> add 1 2 Result { result = 3} Result構造体メンバのresultに3がはいってる感じ

Slide 26

Slide 26 text

Ϩεϙϯε༻ͷܕ data Result = Result { result :: Integer } deriving (Generic, Show) instance ToJSON Result instance FromJSON Result

Slide 27

Slide 27 text

POST ϦΫΤετ༻ͷܕ data Request = Request { x :: Integer, y :: Integer } deriving (Generic, Show) instance ToJSON Request instance FromJSON Request

Slide 28

Slide 28 text

υϝΠϯϩδοΫ add :: Integer -> Integer -> Result add x y = Result $ x + y

Slide 29

Slide 29 text

scottyͷ৔߹

Slide 30

Slide 30 text

{-# LANGUAGE OverloadedStrings #-} module Scotty where import Web.Scotty import Data.Aeson hiding (json) import Data.Maybe import Add (add, Request(..)) import Util (getPort) scottySample = getPort >>= flip scotty route where route = do get "/add/:x/:y" $ do addSampleHeader x <- fmap read . param $ "x" y <- fmap read . param $ "y" json $ add x y post "/add" $ do addSampleHeader Just (Request x y) <- fmap decode body json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample

Slide 31

Slide 31 text

$ curl -i http://localhost:3000/add/1/2 HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 16:58:44 GMT Server: Warp/3.1.3.1 X-SAMPLE: sample Content-Type: application/json; charset=utf-8 {"result":3}

Slide 32

Slide 32 text

$ curl -i http://localhost:3000/add -d '{ "x": 1, "y": 2} ' \ -H "Content-type: application/json" -H "X-SAMPLE: hoge” HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 16:59:44 GMT Server: Warp/3.1.3.1 X-SAMPLE: hoge Content-Type: application/json; charset=utf-8 {"result":3}

Slide 33

Slide 33 text

route = do get "/add/:x/:y" $ do addSampleHeader x <- fmap read . param $ "x" y <- fmap read . param $ "y" json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample

Slide 34

Slide 34 text

route = do get "/add/:x/:y" $ do addSampleHeader x <- fmap read . param $ "x" y <- fmap read . param $ "y" json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample /add/:x/:y から param key がつらい

Slide 35

Slide 35 text

Spockͷ৔߹

Slide 36

Slide 36 text

{-# LANGUAGE OverloadedStrings #-} module Spock where import Web.Spock import Data.Aeson hiding (json) import Data.Maybe import Add (add, Request(..)) import Util (getPort) spockSample = do port <- getPort runSpock port $ spockT id $ do get ("add" var var) $ \x y -> do addSampleHeader json $ add x y post "add" $ do addSampleHeader Just (Request x y) <- jsonBody json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample

Slide 37

Slide 37 text

$ curl -i http://localhost:3000/add/1/2 HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 17:09:18 GMT Server: Warp/3.1.3.1 X-SAMPLE: sample Content-Type: application/json; charset=utf-8 {"result":3}%

Slide 38

Slide 38 text

$ curl -i http://localhost:3000/add/1/2 HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 17:09:18 GMT Server: Warp/3.1.3.1 X-SAMPLE: sample Content-Type: application/json; charset=utf-8 {"result":3}% Spock とかわりない

Slide 39

Slide 39 text

$ curl -i http://localhost:3000/add -d '{ "x": 1, "y": 2} ' \ -H "Content-type: application/json" -H "X-SAMPLE: hoge" HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 17:10:40 GMT Server: Warp/3.1.3.1 X-SAMPLE: hoge Content-Type: application/json; charset=utf-8 {"result":3}

Slide 40

Slide 40 text

$ curl -i http://localhost:3000/add -d '{ "x": 1, "y": 2} ' \ -H "Content-type: application/json" -H "X-SAMPLE: hoge" HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Fri, 06 Nov 2015 17:10:40 GMT Server: Warp/3.1.3.1 X-SAMPLE: hoge Content-Type: application/json; charset=utf-8 {"result":3} とくに変化ないけど Content-type必須に より安全になったかも

Slide 41

Slide 41 text

get ("add" var var) $ \x y -> do addSampleHeader json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample

Slide 42

Slide 42 text

get ("add" var var) $ \x y -> do addSampleHeader json $ add x y addSampleHeader = do headerSample <- header "X-SAMPLE" setHeader "X-SAMPLE" . fromMaybe "sample" $ headerSample パラメータ取得に文字列不要。 型変換も不要。

Slide 43

Slide 43 text

Yesodͷ৔߹

Slide 44

Slide 44 text

{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} module YesodSample where import Yesod (mkYesod, parseRoutes, Yesod(..), selectRep, provideJson, parseJsonBody, warp, lookupHeader, addHeader, renderRoute) import Add (add, Request(..)) import Data.Aeson.Types (Result(..)) import Data.Maybe (fromMaybe) import Data.Text.Encoding (decodeUtf8) data App = App mkYesod "App" [parseRoutes| /add/#Integer/#Integer Add1R GET /add Add2R POST |] instance Yesod App getAdd1R x y = do addSampleHeader selectRep . provideJson $ add x y postAdd2R = do addSampleHeader Success (Request x y) <- parseJsonBody selectRep . provideJson $ add x y yesod :: IO () yesod = warp 3000 App addSampleHeader = do headerSample <- lookupHeader "X-SAMPLE" addHeader "X-SAMPLE" . fromMaybe "sample" $ fmap decodeUtf8 headerSample

Slide 45

Slide 45 text

$ url -i http://localhost:3000/add/1/2 HTTP/1.1 200 OK Date: Fri, 06 Nov 2015 17:15:46 GMT Server: Warp/3.1.3.1 + Yesod/1.4.15.1 (core) Content-Length: 12 Content-Type: application/json; charset=utf-8 Set-Cookie: _SESSION=jjHIHspMlgfwUvPIXiMVzkdx6kn65DVAwX3fiVERujNy FSG9aort5UZ6KyKOW0J7sqlOOwXd6HBp0NeLvYHstBE4qcS25 Xa37WZcXYOh/WWJ1Iv7SD8xCT9QE9hK/98z20/IHXiHg2s=; Path=/; Expires=Fri, 06-Nov-2015 19:15:38 GMT; HttpOnly Vary: Accept, Accept-Language X-SAMPLE: sample {"result":3}

Slide 46

Slide 46 text

$ url -i http://localhost:3000/add/1/2 HTTP/1.1 200 OK Date: Fri, 06 Nov 2015 17:15:46 GMT Server: Warp/3.1.3.1 + Yesod/1.4.15.1 (core) Content-Length: 12 Content-Type: application/json; charset=utf-8 Set-Cookie: _SESSION=jjHIHspMlgfwUvPIXiMVzkdx6kn65DVAwX3fiVERujNy FSG9aort5UZ6KyKOW0J7sqlOOwXd6HBp0NeLvYHstBE4qcS25 Xa37WZcXYOh/WWJ1Iv7SD8xCT9QE9hK/98z20/IHXiHg2s=; Path=/; Expires=Fri, 06-Nov-2015 19:15:38 GMT; HttpOnly Vary: Accept, Accept-Language X-SAMPLE: sample {"result":3} Cookieまでついてきた

Slide 47

Slide 47 text

mkYesod "App" [parseRoutes| /add/#Integer/#Integer Add1R GET |] getAdd1R x y = do addSampleHeader selectRep . provideJson $ add x y addSampleHeader = do headerSample <- lookupHeader "X-SAMPLE" addHeader "X-SAMPLE" . fromMaybe "sample" $ fmap decodeUtf8 headerSample

Slide 48

Slide 48 text

mkYesod "App" [parseRoutes| /add/#Integer/#Integer Add1R GET |] getAdd1R x y = do addSampleHeader selectRep . provideJson $ add x y addSampleHeader = do headerSample <- lookupHeader "X-SAMPLE" addHeader "X-SAMPLE" . fromMaybe "sample" $ fmap decodeUtf8 headerSample 型まで指定できるエンドポイント 黒魔術満載?

Slide 49

Slide 49 text

·ͱΊ

Slide 50

Slide 50 text

·ͱΊ Yesod ͍͢͝ɻͰ΋ɺ೉͍͠ɻ scotty΍SpockͳΒ௅ઓ͠΍͍͢ɻ Haskellͩͱهड़͕γϯϓϧͳؾ͕͢Δɻ IUUQTHJUIVCDPNFJFMIBTLFMMXFCGSBNFXPSLTBNQMF