本来やりたいことにすぐに進めずに、express周りを色々調べてます。
以前ちょっとやったexpress-sessionのセッションストレージとしてRedisを使うやつ、前回はHerokuだったのだけど、今回はローカルのMacにインストールしてみる。
目次
Redisのインストール
MacならHomebrewでかんたん。
$ brew install redis
これでサーバもクライアントも一緒に入る。
Homebrewでのサービスの制御&Redisサーバの起動
Homebrewでインストールしたサービスの起動はbrew services
で操作します。
$ brew services --help Usage: brew services [subcommand] Manage background services with macOS' launchctl(1) daemon manager. If sudo is passed, operate on /Library/LaunchDaemons (started at boot). Otherwise, operate on ~/Library/LaunchAgents (started at login). [sudo] brew services [list]: List all managed services for the current user (or root). [sudo] brew services info (formula|--all): List all managed services for the current user (or root). [sudo] brew services run (formula|--all): Run the service formula without registering to launch at login (or boot). [sudo] brew services start (formula|--all): Start the service formula immediately and register it to launch at login (or boot). [sudo] brew services stop (formula|--all): Stop the service formula immediately and unregister it from launching at login (or boot). [sudo] brew services restart (formula|--all): Stop (if necessary) and start the service formula immediately and register it to launch at login (or boot). [sudo] brew services cleanup: Remove all unused services. --file Use the plist file from this location to start or run the service. --all Run subcommand on all services. --json Output as JSON. -d, --debug Display any debugging information. -q, --quiet Make some output more quiet. -v, --verbose Make some output more verbose. -h, --help Show this message.
何もつけずに実行すると、現在のサービスが表示されます。Redisがサービスとして登録されているのがわかる。ちなみに、うちの場合だとすでにPostgreSQLとunboundがもとから入っていて、PostgreSQLがサービスとして起動している。
$ brew services Name Status User File postgresql started kun432 ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist redis none unbound none
サーバの起動・停止はbrew services start/stop
で行う。systemctlでいうところのenable/disableも兼ねているみたい。
$ brew services start redis ==> Successfully started `redis` (label: homebrew.mxcl.redis)
プロセスが上がっている。
$ ps auxw | grep redis kun432 61300 0.0 0.0 34158584 3668 ?? S 5:00PM 0:00.03 /usr/local/opt/redis/bin/redis-server 127.0.0.1:6379
redis-cliでつないで見る。当然ながらまだ何もkeyはない。
$ redis-cli 127.0.0.1:6379> keys * (empty array)
express-sessionから使ってみる
ではexpress-sessionから使ってみる。以下で使用したコードをほぼそのまま使う。
npmパッケージの準備。node-redisは古いみたいなので、ioredisにしました。
$ npm init
$ npm install --save express express-session ioredis connect-redis
コードはこんな感じ。
const express = require('express'); const session = require("express-session"); const redis = require("ioredis"); const RedisStore = require("connect-redis")(session); const app = express(); app.set('port', (process.env.PORT || 3000)); app.use( session({ secret: 'secret', cookie: { maxAge: 3600 * 1000 }, resave: false, saveUninitialized: false, store: new RedisStore({ url: process.env.REDIS_URL, client: redis.createClient({ url: process.env.REDIS_URL }) }) }), ); app.get('/', function(request, response) { let session = request.session; console.log(JSON.stringify(session)); if (!!session.count) { session.count += 1; } else { session.count = 1; } response.send(`Hello World! count: ${session.count}\n`); }); app.listen(app.get('port'), function() { console.log("Node app is running at localhost:" + app.get('port')); });
RedisのURLは環境変数で指定するようにしてるので以下。
$ export REDIS_URL="redis://localhost:6379"
では起動。
$ node app.js
ブラウザからhttp://localhost:3000
にアクセスしてカウントアップされればOK。
redis-cliでもセッション情報が保持されているのがわかりますね。
$ redis-cli 127.0.0.1:6379> keys * 1) "sess:EZxHrgAzRqDBYVm-BWVozBomRoryhyzY" 127.0.0.1:6379> type sess:EZxHrgAzRqDBYVm-BWVozBomRoryhyzY string 127.0.0.1:6379> get sess:EZxHrgAzRqDBYVm-BWVozBomRoryhyzY "{\"cookie\":{\"originalMaxAge\":3600000,\"expires\":\"2022-02-12T09:59:33.666Z\",\"httpOnly\":true,\"path\":\"/\"},\"count\":19}"
おまけ
これ。
この記事から数年経ってて現在のHerokuではちゃんと設定されている。
timeout
The
timeout
setting sets the number of seconds Redis waits before killing idle connections. A value of zero means that connections are not closed. The default value is 300 seconds (5 minutes). You can change this value using the CLI:
maxmemory-policy
The maxmemory-policy setting sets the key eviction policy used when an instance reaches its storage limit. Available policies for key eviction include:
- noeviction returns errors when the memory limit is reached.
- allkeys-lru removes less recently used keys first.
- volatile-lru removes less recently used keys first that have an expiry set.
- allkeys-random evicts random keys.
- volatile-random evicts random keys that have an expiry set.
- volatile-ttl evicts keys with an expiry set and a short TTL.
- volatile-lfu evicts using approximated LFU among the keys with an expire set.
- allkeys-lfu evicts any key using approximated LFU.
- Heroku Redis doesn’t support tuning lfu-log-factor or lfu-decay-time
By default, this setting is set to
noeviction
. You can change this value using the CLI:
Homebrewでローカルに入れた場合、もちろんこのあたりの設定は行われていません。
$ cat /usr/local/etc/redis.conf (...snip...) # Close the connection after a client is idle for N seconds (0 to disable) timeout 0 (...snip...) # The default is: # # maxmemory-policy noeviction (...snip...)
Hobby DevなRedisインスタンスはこんな感じのスペックっぽい。
- メモリ 25MB
- 接続数 20
Herokuにあわせて、かつ、タイムアウトなど設定をいじるならこんな感じにしておけば良さそう。
timeout 60 maxclients 20 maxmemory 26214400 maxmemory-policy allkeys-lru
restartして反映しておく。
$ brew services restart redis
あと、Redisのキーは、express-sessionでcookie.maxAgeを設定しておけば、勝手に消える。
(...snip...) app.use( session({ secret: 'secret', cookie: { maxAge: 3600 * 1000 }, (...snip...)
cookie.maxAgeが着れるまでにアクセスするとTTLが更新される。以下のように、cookie.maxAgeを60秒で設定して、60秒立つ前にアクセスすると、更新されているのがわかる。
127.0.0.1:6379> keys * 1) "sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT" 127.0.0.1:6379> ttl sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT (integer) 45 127.0.0.1:6379> ttl sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT (integer) 44 127.0.0.1:6379> ttl sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT (integer) 44 127.0.0.1:6379> ttl sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT (integer) 43 〜ここで一度アクセスする〜 127.0.0.1:6379> ttl sess:hANI7uJn5gR0C8wgdA6b_ClUnQtuYyHT (integer) 58
この挙動を無効にするにはdisableTouch: true
にすれば良いらしいけど、セッション継続してるのに強制的に消す必要は一般的にはそれほどない気がする。あと、あくまでもこれはセッションの内容が「更新」される場合のみで、参照するだけでは更新されないみたい。
ttl
If the session cookie has aexpires
date,connect-redis
will use it as the TTL.
Otherwise, it will expire the session using thettl
option (default:86400
seconds or one day).
Note: The TTL is reset every time a user interacts with the server. You can disable this behavior in some instances by usingdisableTouch
.
Note:express-session
does not updateexpires
until the end of the request life cycle. Callingsession.save()
manually beforehand will have the previous value.
その場合、以下にある通り、touchメソッドを使って意図的に更新するか、
disableTouch
Disables re-saving and resetting the TTL when usingtouch
(default:false
)
The express-session package uses touch to signal to the store that the user has interacted with the session but hasn't changed anything in its data. Typically, this helps keep the users session alive if session changes are infrequent but you may want to disable it to cut down the extra calls or to prevent users from keeping sessions open too long. Also consider enabling if you store a lot of data on the session.
もしくは、express-sessionのオプションでresave:true
にする必要があるみたい。
(...snip...) app.use( session({ secret: 'secret', cookie: { maxAge: 3600 * 1000 }, resave: true, saveUninitialized: false, (...snip...)