Help us understand the problem. What is going on with this article?

Yesodにserversession-backend-redisを組み込んで動かしてみたのでメモしておく

学習がてら、HaskellのWebフレームワークであるYesodと、セッション管理をサポートするライブラリであるserversessionを組み合わせて動かしてみました。

今回は、stackのtemplateであるyesodweb/simpleを利用して、redissessionというプロジェクト名にしました。

stack new redissession yesodweb/simple

今回は、SessionストレージにRedisを使いたかったので、serversessionというライブラリを使いました。
(このライブラリは長いことメンテナンスされていないようですが、今回はHaskellでのセッション周りの実装例を見てみることが目的なので、気にせず進みます。)

まずは、package.yamlにserversessionパッケージの情報を追記します。
フロントエンドとしてYesod、バックエンドとしてRedisを使用するので、serversession-frontend-yesodとserversession-backend-redisも追記します。
また、serversesion-backend-redisはhedisというパッケージに依存しているので、それも追記します。

package.yaml
dependencies:
(中略)
- serversession == 1.0.1
- serversession-frontend-yesod == 1.0
- serversession-backend-redis == 1.0.3
- hedis < 0.11

serversession-frontend-yesodとserversession-backend-redisは、今回使用したStackageのresolverのlts-16.20に含まれていなかったため、stack.yamlにも追記します。
また、このままではパッケージ間のバージョン依存関係の不整合によりエラーが発生してしまったので、"allow-newer: true"を設定して、バージョン依存関係の不整合を無視します。
今回はまず動かしてみることが目的なので、これでよしとします。

stack.yaml
extra-deps:
- serversession-frontend-yesod-1.0
- serversession-backend-redis-1.0.3

allow-newer: true
src/Foundation.hs
import ...()

-- 以下を追記
import Web.ServerSession.Backend.Redis (RedisStorage(..))
import Web.ServerSession.Frontend.Yesod (simpleBackend, setCookieName, setSecureCookies)
import qualified Database.Redis as Redis

(中略)

data App = App 
    { appSettings    :: AppSettings
    , appStatic      :: Static -- ^ Settings for static file serving.
    , appHttpManager :: Manager
    , appLogger      :: Logger
    , appConn        :: Redis.Connection --追加
    }
src/Foundation.hs
instance Yesod App where

(中略)

  makeSessionBackend :: App -> IO (Maybe SessionBackend)
  makeSessionBackend = simpleBackend opts . createStorage
    where createStorage appl = RedisStorage (appConn appl) idleTimeoutSec absoluteTimeoutSec
          idleTimeoutSec     = Just $ 60*10
          absoluteTimeoutSec = Just $ 60*30
          opts               = setCookieName "REDISSESSION_ID"
                             . setSecureCookies False
src/Application.hs
import qualified Database.Redis as Redis

makeFoundation :: AppSettings -> IO App 
makeFoundation appSettings = do
    -- Some basic initializations: HTTP connection manager, logger, and static
    -- subsite.
    appHttpManager <- getGlobalManager
    appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger
    appStatic <-
        (if appMutableStatic appSettings then staticDevel else static)
        (appStaticDir appSettings)
    appConn <- Redis.connect Redis.defaultConnectInfo  -- ここを追記                                                                                                                                                                                       

    -- Return the foundation
    return App {..}
src/Handler/Home.hs
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE QuasiQuotes #-} --追加

import Web.ServerSession.Frontend.Yesod (forceInvalidate, ForceInvalidate(..))
import Import
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3)
import Text.Julius (RawJS (..))

import Web.ServerSession.Frontend.Yesod (forceInvalidate, ForceInvalidate(..)) 
src/Handler/Home.hs
getHomeR :: Handler Html
getHomeR = do
    sess <- getSession
    forceInvalidate CurrentSessionId -- これを呼ぶとセッションIDが再採番される
    defaultLayout
        [whamlet|
            <form method=post>
                <input type=text name=key>
                <input type=text name=val>
                <input type=submit>
            <h1>#{show sess}
        |]  

postHomeR :: Handler ()
postHomeR = do
    (key, mval) <- runInputPost $ (,) <$> ireq textField "key" <*> iopt textField "val"
    case mval of
        Nothing -> deleteSession key 
        Just val -> setSession key val 
    liftIO $ print (key, mval)
    redirect HomeR

動かしてみます。

Redisを立ち上げておきます。

$ redis-server

Webサーバを起動します

$ stack exec -- yesod devel

Webサーバが立ち上がったので、Webブラウザで、localhost:3000にアクセスします。

スクリーンショット 2021-01-06 23.37.17.png

Set-Cookieヘッダーに、「REDISSESSION_ID」フィールドがあります。うまく動いていそうです。
次に、Sessionストレージにデータを保存してみます。
Keyは「hoge」、Valueは「fuga」を指定します。

スクリーンショット 2021-01-06 23.37.33.png

Session情報が画面に表示されました。ちゃんと動いていそうです。
(「forceInvalidate CurrentSessionId」を呼んだので、SetCookieヘッダーの「REDISSESSION_ID」の値が変わっていますが、このタイミングでは本来必要ありません。)

スクリーンショット 2021-01-06 23.38.03.png

redis-cliでも、Redisに格納されているデータを確認しておきます。

スクリーンショット 2021-01-06 22.52.08.png

ブラウザで入力したデータが、ちゃんと保存されています。

masaki_shoji
ソフトウェアエンジニア。株式会社G・B・S所属。 仕事では主にAndroid開発を担当する事が多いですが、システム全般に興味があります。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away