CLOVER🍀

That was when it all began.

express-sessionを詊しおみるメモリ、Redis

これは、なにをしたくお曞いたもの

Expressのセッション管理の仕組みを䜿っおみようかな、ず思いたしお。

Expressずセッション

Expressのセッションに関する話は、以䞋に少し出おきたす。

Production Best Practices: Security / Use cookies securely

Expressには、以䞋の2぀のセッションモゞュヌルがあるようです。

cookie-sessionはCookieにセッションデヌタを保存するミドルりェアです。
express-sessionはCookieにセッションIDのみを保存しお、セッションデヌタは別のストレヌゞに保存するミドルりェアです。

cookie-sessionはCookieにセッションデヌタも含むため、以䞋のような特城がありたす。

  • 保存するデヌタの合蚈は、Cookieの䞊限に瞛られる
    • 代わりに、サヌバヌ偎にデヌタベヌス等は必芁ない
  • 負荷分散が容易になる

セッションデヌタが比范的小さく、デヌタもプリミティブずしお簡単に゚ンコヌドできる堎合にのみ䜿甚する、ずいう
方針みたいです。

Only use it when session data is relatively small and easily encoded as primitive values (rather than objects).

今回は、express-sessionの方を䜿っおみたす。

ちなみに、このセクションに曞かれおいる内容はセッション甚のミドルりェアを䜿う際のセキュリティ䞊の泚意事項
なので、実際に䜿う時には芋おおくずよいでしょう。

Production Best Practices: Security / Use cookies securely

express-session

express-sessionに぀いお、もう少し。

GitHub - expressjs/session: Simple session middleware for Express

繰り返しになりたすが、express-sessionはCookieにセッションIDのみを保存しお、セッションデヌタは
別のストレヌゞに保存するミドルりェアです。

デフォルトのストレヌゞはむンメモリです。こちらはメモリヌリヌクが発生する可胜性が高く、本番環境での
利甚を意図しお蚭蚈されたものではないこずに泚意が必芁そうです。

Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.

ストレヌゞセッションストアは、他のものに倉曎するこずができたす。その䞀芧はこちら。

express-session / Compatible Session Stores

今回はむンメモリデフォルトずRedisをそれぞれ詊しおみたいず思いたす。

最初にむンメモリの方で確認しお、そちらをベヌスにRedisを組み蟌む方向でいっおみたいず思いたす。

環境

今回の環境は、こちら。

$ node --version
v16.13.1


$ npm --version
8.1.2

Redisは以䞋のバヌゞョンを䜿甚し、172.17.0.2で動䜜しおいるものずしたす。たた、パスワヌドはredispassずしたす。

$ bin/redis-server --version
Redis server v=6.2.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=ef9c41a6cf56a536

準備

Node.jsのプロゞェクトは2぀䜜るこずにしたすが、共通のずころはたずめお曞きたす。

$ npm init -y
$ npm i -D typescript
$ npm i -D -E prettier
$ mkdir src

TypeScriptのバヌゞョン。

$ npx tsc --version
Version 4.5.4

Express、それからNode.jsずExpressの型宣蚀のむンストヌル。

$ npm i express
$ npm i -D @types/node @types/express

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "baseUrl": "./src",
    "outDir": "dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "esModuleInterop": true
  },
  "include": [
    "src"
  ]
}

.prettierrc.json

{
  "singleQuote": true
}

むンメモリでexpress-sessionを䜿う

では、たずはexpress-sessionに慣れおいきたしょう。

express-sessionず型宣蚀をむンストヌル。

$ npm i express-session
$ npm i -D @types/express-session

今回の䟝存関係は、このようになりたした。

  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/express-session": "^1.17.4",
    "@types/node": "^17.0.5",
    "prettier": "2.5.1",
    "typescript": "^4.5.4"
  },
  "dependencies": {
    "express": "^4.17.2",
    "express-session": "^1.17.2"
  }

䜜成した゜ヌスコヌドは、こちら。

src/app.ts

import express from 'express';
import session from 'express-session';

declare module 'express-session' {
  interface SessionData {
    firstAccessTime: string;
    counter: number;
    message: string;
  }
}

const app = express();
let port: number;

const args = process.argv.slice(2);

if (args.length == 0) {
  port = 3000;
} else {
  port = parseInt(args[0], 10);
}

app.set('trust proxy', 1);
app.use(
  session({
    secret: 's3Cur3',
    name: 'session', // default: connect.sid
    resave: false,
    saveUninitialized: true,
    cookie: {
      path: '/', // default
      httpOnly: true, // default
      maxAge: 10 * 1000, // 10sec
    },
  })
);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use((req, res, next) => {
  if (!req.session.firstAccessTime) {
    const now = new Date();
    req.session.firstAccessTime = now.toISOString();
  }

  req.session.counter = req.session.counter ? req.session.counter + 1 : 1;

  next();
});

app.post('/message', (req, res) => {
  const message = req.body['message'];

  req.session.message = message;

  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message,
  });
});

app.get('/message', (req, res) => {
  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message ? req.session.message : 'Hello World',
  });
});

app.listen(port, () => {
  console.log(`[${new Date().toISOString()}] start server[${port}]`);
});

ちょっずず぀、解説しおいきたす。

モゞュヌルのむンポヌト。

import session from 'express-session';

次に、セッションで扱うデヌタSessionDataの型宣蚀をしおおきたす。

declare module 'express-session' {
  interface SessionData {
    firstAccessTime: string;
    counter: number;
    message: string;
  }
}

これをやっおおかないず、Request#sessionを䜿ったセッションデヌタを扱えたせん。少し補足があるので、それは
埌述したす。

セッションおよびCookieの蚭定。

app.use(
  session({
    secret: 's3Cur3',
    name: 'session', // default: connect.sid
    resave: false,
    saveUninitialized: true,
    cookie: {
      path: '/', // default
      httpOnly: true, // default
      maxAge: 10 * 1000, // 10sec
    },
  })
);

各蚭定項目は、こちらに蚘茉がありたす。

express-session / Options

Cookieの郚分はさおおき、その他の項目をいく぀か芋おみたしょう。

  • name 
 Cookie名。デフォルトではconnect.sidだが、倉曎が掚奚されおいる
  • resave 
 trueにするず、リク゚スト䞭にセッションが倉曎されなかった堎合でも、匷制的にセッションストアに保存しなおす
    • デフォルト倀はtrueだが、セッションストアずナヌスケヌスに応じお遞択する
    • セッションストアにtouchが実装されおおらず、か぀セッションに有効期限がある堎合は蚭定が必芁な可胜性がある
  • saveUninitialized 
 初期化されおいないセッションを、匷制的にセッションストアに保存する
    • デフォルト倀はtrueだが、明瀺的に蚭定するこずを掚奚
  • secret 
 必須項目。セッションIDを保存するCookieの眲名に䜿甚される
    • 今回はハヌドコヌドしおいるが、シヌクレットは環境倉数等から取埗し、ランダムな倀にするこずを掚奚

その他にもいろいろオプションがありたす。

Cookieに関する蚭定は、HTTPずしおのCookieに察する内容ほがそのたたなので、今回曞いおいないものも含めお割愛 。

有効期限は、maxAgeかexpiresで指定するこずになりたす。が、maxAgeのみを䜿うこずが掚奚されおいるみたいです。

    cookie: {
      path: '/', // default
      httpOnly: true, // default
      maxAge: 10 * 1000, // 10sec
    },

今回は、10秒にしおおきたした。CookienのmaxAgeに反映されたす。

続いお。セッションに保存するデヌタはどうしようかな、ず思ったのですが、初回アクセス時の日時ずアクセスする床に
増えるカりンタヌをミドルりェアの凊理で蚭定するこずにしたした。

app.use((req, res, next) => {
  if (!req.session.firstAccessTime) {
    const now = new Date();
    req.session.firstAccessTime = now.toISOString();
  }

  req.session.counter = req.session.counter ? req.session.counter + 1 : 1;

  next();
});

あずは、POSTされたメッセヌゞを保存したり、取埗したり。

app.post('/message', (req, res) => {
  const message = req.body['message'];

  req.session.message = message;

  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message,
  });
});

app.get('/message', (req, res) => {
  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message ? req.session.message : 'Hello World',
  });
});

この時に、セッション内のデヌタもレスポンスに含めおみたす。

バむンドするポヌトは、起動匕数に応じお倉曎できるようにしおいたすが、今回はそれほど意味はありたせん。

let port: number;

const args = process.argv.slice(2);

if (args.length == 0) {
  port = 3000;
} else {
  port = parseInt(args[0], 10);
}

完成したので、動䜜確認したしょう。

ビルド。

$ npx tsc --project .

起動。

$ node dist/app.js
[2021-12-28T17:04:26.565Z] start server[3000]

ずりあえず、アクセス。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-sYkTynMndBqQZveBjfEAdNjDMqw"
Set-Cookie: session=s%3AlMjoQ202glCfSgGGWHnKcDL72lPLVGle.VvlWEocjst3r9hNWO2xXFEfQT2FrlXnJ5o33VmGQi7c; Path=/; Expires=Tue, 28 Dec 2021 17:05:36 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:05:26 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:05:26.716Z","counter":1,"message":"Hello World"}

メッセヌゞを保存しおいなくおも、デフォルトのメッセヌゞHello Worldが返っおきたす。

10秒経過するたでは、初回アクセスの日時は同じですね。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-vyS+8+D6lA6S21HrzlrP2QkfBcg"
Set-Cookie: session=s%3AlMjoQ202glCfSgGGWHnKcDL72lPLVGle.VvlWEocjst3r9hNWO2xXFEfQT2FrlXnJ5o33VmGQi7c; Path=/; Expires=Tue, 28 Dec 2021 17:05:42 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:05:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:05:26.716Z","counter":2,"message":"Hello World"}

カりンタヌは䞊がっおいきたす。

ここで、メッセヌゞを保存。

$ curl -XPOST -c cookie.txt -b cookie.txt -i -H 'Content-Type: application/json' localhost:3000/message -d '{"message": "Hello Express-Session"}'
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 92
ETag: W/"5c-IKcfg7BQZtni1wQBP2yyNEfkayM"
Set-Cookie: session=s%3AlMjoQ202glCfSgGGWHnKcDL72lPLVGle.VvlWEocjst3r9hNWO2xXFEfQT2FrlXnJ5o33VmGQi7c; Path=/; Expires=Tue, 28 Dec 2021 17:05:47 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:05:37 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:05:26.716Z","counter":3,"message":"Hello Express-Session"

確認。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 92
ETag: W/"5c-3PLziySobG9OtC4EEA5aV6oo32k"
Set-Cookie: session=s%3AlMjoQ202glCfSgGGWHnKcDL72lPLVGle.VvlWEocjst3r9hNWO2xXFEfQT2FrlXnJ5o33VmGQi7c; Path=/; Expires=Tue, 28 Dec 2021 17:05:54 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:05:44 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:05:26.716Z","counter":4,"message":"Hello Express-Session"}

セッションに保存したメッセヌゞが取埗できたした。

なお、10秒以䞊経過するず、CookieのmaxAgeを超えるのでセッションが取埗できなくなりたす。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-fTkTw7lMrghKrv3y6JTcAEkRQBM"
Set-Cookie: session=s%3ARZs7HJyez1JS4OUEv0RyN5NBxbV8xhBM.IkRYnD2xoZw%2BVjgsIhvDX%2Fl0zpOD2mrseTPpLKrQ33U; Path=/; Expires=Tue, 28 Dec 2021 17:06:06 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:05:56 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:05:56.916Z","counter":1,"message":"Hello World"}

これで、基本的な動䜜確認はできたしたね。

ちなみに、もうひず぀サヌバヌを起動しお

$ node dist/app.js 3001

こちらに同じCookieを䜿っおアクセスしおも、別のセッションになるこずが確認できたす。

$ curl -c cookie.txt -b cookie.txt localhost:3001/message

メモリセッションなので、圓然ですが。この埌は、こちらも確認しおいきたす。

補足

この時点で、いく぀か補足をしたす。

SessionDataの型宣蚀のマヌゞ

たず、こちらのSessionDataに察する型宣蚀に぀いお。

declare module 'express-session' {
  interface SessionData {
    firstAccessTime: string;
    counter: number;
    message: string;
  }
}

こちらは、以前は䞍芁だったみたいなのですが、express-sessionの1.17.1以降はSessionDataに明瀺的に型宣蚀を
しないず、セッションにデヌタを保存、デヌタを取埗しようずしお属性にアクセスするコヌドを曞くず、コンパむルが
通らなくなりたす。

src/app.ts:33:17 - error TS2339: Property 'firstAccessTime' does not exist on type 'Session & Partial<SessionData>'.

33     req.session.firstAccessTime = now.toISOString();
                   ~~~~~~~~~~~~~~~

issueにも䞊がっおいたしたが、これは意図的な倉曎のようです。

Incorrect typings for session object · Issue #49941 · DefinitelyTyped/DefinitelyTyped · GitHub

もずもずのSessionDataの型定矩は以䞋なのですが、コメントにあるように利甚偎でも型宣蚀を远加、マヌゞしお
䜿甚するこずを想定しおいるみたいです。

    /**
     * This interface allows you to declare additional properties on your session object using [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html).
     *
     * @example
     * declare module 'express-session' {
     *     interface SessionData {
     *         views: number;
     *     }
     * }
     *
     */
    interface SessionData {
        cookie: Cookie;
    }

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9d35fe5b9f3bac0a30c252a61e411f84a6b7ba5d/types/express-session/index.d.ts#L202-L215

TypeScriptの型宣蚀のマヌゞに぀いおは、こちら。

TypeScript: Documentation - Declaration Merging

これにはいろんな声があるみたいで、express-sessionの型宣蚀は1.17.0を利甚するずいった方法も案内されおいたしたが。
蚭蚈の意図ずは反する圢ですね。

デフォルトのむンメモリセッションストアに぀いお

express-sessionがデフォルトで䜿甚するむンメモリセッションストアには、こんな泚意事項がありたした。

Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.

GitHub - expressjs/session: Simple session middleware for Express

ほずんどのケヌスでメモリリヌクするずいうこずなのですが、どういうこずでしょう

゜ヌスコヌドを芋おいた感じ、明瀺的にセッションをdestroyしない限り、セッションデヌタが残り続けるみたいですね。
セッションIDを保持しおいるmaxAgeを過ぎおしたうず、セッションが远跡できなくなるので消すこずもできず、
メモリリヌクしたす、ず 。

ずいうわけで、本番環境などではこのセッションストアは䜿わずに、他のセッションストアを䜿いたしょう、ず。

connect-redisを䜿う

express-sessionで利甚できるセッションストアのうち、Redisをバック゚ンドに䜿うものがconnect-redisです。

GitHub - tj/connect-redis: Redis session store for Connect

今回は、こちらを䜿っおセッションデヌタをRedisに保存しおみたす。

connect-redisを䜿うには、Redisクラむアントを別にむンストヌルする必芁がありたす。䜿甚できるのは、以䞋の3぀です。

どうしようかなず思ったのですが、今回はioredisを䜿うこずにしたす。

新しいNode.jsプロゞェクトを䜜成しお、先ほどず同じようにExpressの埌にexpress-sessionず型宣蚀をむンストヌル。

$ npm i express-session
$ npm i -D @types/express-session

続いお、connect-redisずioredisをむンストヌルしたす。

$ npm i ioredis connect-redis
$ npm i -D @types/ioredis @types/connect-redis

今回の䟝存関係は、こちら。

  "devDependencies": {
    "@types/connect-redis": "^0.0.18",
    "@types/express": "^4.17.13",
    "@types/express-session": "^1.17.4",
    "@types/ioredis": "^4.28.5",
    "@types/node": "^17.0.5",
    "prettier": "2.5.1",
    "typescript": "^4.5.4"
  },
  "dependencies": {
    "connect-redis": "^6.0.0",
    "express": "^4.17.2",
    "express-session": "^1.17.2",
    "ioredis": "^4.28.2"
  }

゜ヌスコヌドは、先ほどの゜ヌスコヌドを元にconnect-redisを䜿うように修正したす。

src/app.ts

import express from 'express';
import session from 'express-session';
import connectRedis from 'connect-redis';
import Redis from 'ioredis';

declare module 'express-session' {
  interface SessionData {
    firstAccessTime: string;
    counter: number;
    message: string;
  }
}

const app = express();
let port: number;

const args = process.argv.slice(2);

if (args.length == 0) {
  port = 3000;
} else {
  port = parseInt(args[0], 10);
}

const RedisStore = connectRedis(session);
const redisClient = new Redis({
  host: '172.17.0.2',
  port: 6379,
  password: 'redispass',
});

app.set('trust proxy', 1);
app.use(
  session({
    secret: 's3Cur3',
    name: 'session', // default: connect.sid
    resave: false,
    saveUninitialized: true,
    cookie: {
      path: '/', // default
      httpOnly: true, // default
      maxAge: 10 * 1000, // 10sec
    },
    store: new RedisStore({ client: redisClient }),
  })
);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use((req, res, next) => {
  if (!req.session.firstAccessTime) {
    const now = new Date();
    req.session.firstAccessTime = now.toISOString();
  }

  req.session.counter = req.session.counter ? req.session.counter + 1 : 1;

  next();
});

app.post('/message', (req, res) => {
  const message = req.body['message'];

  req.session.message = message;

  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message,
  });
});

app.get('/message', (req, res) => {
  res.send({
    firstAccessTime: req.session.firstAccessTime,
    counter: req.session.counter,
    message: req.session.message ? req.session.message : 'Hello World',
  });
});

app.listen(port, () => {
  console.log(`[${new Date().toISOString()}] start server[${port}]`);
});

倉曎郚分は、モゞュヌルのむンポヌト。

import connectRedis from 'connect-redis';
import Redis from 'ioredis';

Redisぞ接続するためのクラむアントの䜜成。

const RedisStore = connectRedis(session);
const redisClient = new Redis({
  host: '172.17.0.2',
  port: 6379,
  password: 'redispass',
});

そしお、セッションミドルりェアぞのstore指定ですね。こちらで、先ほど䜜成したクラむアントを䜿いたす。

app.use(
  session({
    secret: 's3Cur3',
    name: 'session', // default: connect.sid
    resave: false,
    saveUninitialized: true,
    cookie: {
      path: '/', // default
      httpOnly: true, // default
      maxAge: 10 * 1000, // 10sec
    },
    store: new RedisStore({ client: redisClient }),
  })
);

蚭定内容に぀いおは、こちらを参照。

connect-redis / RedisStore(options)

今回はclientのみを指定しおいるのですが、cookie.expiresを指定しおいる堎合はそれがttlずしおも䜿甚されたす。

ず曞くず、今回はcookie.maxAgeのみを指定しおいるので効果がなさそうに思えたす。実際、connect-redisが芋おいるのも
cookie.expiresです。

https://github.com/tj/connect-redis/blob/v6.0.0/lib/connect-redis.js#L138-L147

ですが、express-session偎でcookie.maxAgeを蚭定するずcookie.expiresも同時に蚭定するので、これでも問題
なさそうです。

session/cookie.js at v1.17.2 · expressjs/session · GitHub

このあたりも、動䜜確認時に芋おいきたしょう。

では、ビルド。

$ npx tsc --project .

サヌバヌを2぀起動したす。

$ node dist/app.js
[2021-12-28T17:09:58.042Z] start server[3000]


$ node dist/app.js 3001
[2021-12-28T17:09:59.014Z] start server[3001]

セッションストアがメモリだった時ず同じ操䜜を、アクセスするサヌバヌを亀互にしながら詊しおみたしょう。

たずは1぀目のサヌバヌにアクセス。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-yJLufkq12oSrS08zelcHcp3KZFw"
Set-Cookie: session=s%3AkTnWdu3my-RutEu4qW2ppytVowE6exsk.S8lGv9ThCcOkGxQxAkPBpj8i0zq7J9ANLre2hcPQ8SQ; Path=/; Expires=Tue, 28 Dec 2021 17:11:05 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:10:55 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:10:55.322Z","counter":1,"message":"Hello World"}

2぀目のサヌバヌにアクセス。

$ curl -c cookie.txt -b cookie.txt -i localhost:3001/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-Cr32RdSZjDB1tDYcFHwEedQGAjI"
Set-Cookie: session=s%3AkTnWdu3my-RutEu4qW2ppytVowE6exsk.S8lGv9ThCcOkGxQxAkPBpj8i0zq7J9ANLre2hcPQ8SQ; Path=/; Expires=Tue, 28 Dec 2021 17:11:11 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:11:01 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:10:55.322Z","counter":2,"message":"Hello World"}

同じセッションを芋おいるこずが確認できたす。

ここで、1぀目のサヌバヌに察しおメッセヌゞを保存。

$ curl -XPOST -c cookie.txt -b cookie.txt -i -H 'Content-Type: application/json' localhost:3000/message -d '{"message": "Hello Express-Session"}'
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 92
ETag: W/"5c-emnmjDwUdhAq5Z49Escy25ABU00"
Set-Cookie: session=s%3AkTnWdu3my-RutEu4qW2ppytVowE6exsk.S8lGv9ThCcOkGxQxAkPBpj8i0zq7J9ANLre2hcPQ8SQ; Path=/; Expires=Tue, 28 Dec 2021 17:11:16 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:11:06 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:10:55.322Z","counter":3,"message":"Hello Express-Session"}

2぀目のサヌバヌに察しお、参照できるか確認。

s$ curl -c cookie.txt -b cookie.txt -i localhost:3001/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 92
ETag: W/"5c-x1y1bK4LdcEFThWoEN+fI3fnu6s"
Set-Cookie: session=s%3AkTnWdu3my-RutEu4qW2ppytVowE6exsk.S8lGv9ThCcOkGxQxAkPBpj8i0zq7J9ANLre2hcPQ8SQ; Path=/; Expires=Tue, 28 Dec 2021 17:11:21 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:11:11 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:10:55.322Z","counter":4,"message":"Hello Express-Session"}

サヌバヌを跚いで、セッションが操䜜できおいたすね。

10秒以䞊経過するず、CookieのmaxAgeを超えるのでセッションが取埗できなくなるずころも同じです。

s$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-TZMcIC3EIc9lkB8W/R10PtrgYVg"
Set-Cookie: session=s%3A2TySiWJl8xIwdMq1-gR1cmNXOzJmLSak.dl0PVDfh9LAqIq6ec6kKLIrZLdeXiWiAkBFOp4GzsBM; Path=/; Expires=Tue, 28 Dec 2021 17:11:35 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:11:25 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:11:25.742Z","counter":1,"message":"Hello World"}

Redis偎を確認する

connect-redisを䜿っおRedisに保存したデヌタがどうなっおいるか、ちょっず気になるずころです。
確認しおみたしょう。

こんな感じで、ずりあえずセッションを2぀䜜りたす。アクセスするサヌバヌを倉えおいるのは、なんずなくです。

$ curl -c cookie.txt -b cookie.txt -i localhost:3000/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-DLTqL9qW8d2JlmuOtlrCspXxUPU"
Set-Cookie: session=s%3AdEMW9ngNDBJi_OUldTehMDEF4Igqplw3.NVZiKzWyVfNayzBJOPqRpg1bhB0Py8y4RisBQRxefzY; Path=/; Expires=Tue, 28 Dec 2021 17:19:15 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:19:05 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:19:05.638Z","counter":1,"message":"Hello World"}

$ curl -c cookie2.txt -b cookie2.txt -i localhost:3001/message
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 82
ETag: W/"52-CdWxLXIyKUZWlDXpISXDUgSZtrA"
Set-Cookie: session=s%3APxUkTEILqMR32DUkBV3f2cYu-sosa8-o.xgbiMNzt9VpbyMjRGMhUAFw8xz4JyOW9k4rF0yaTJjs; Path=/; Expires=Tue, 28 Dec 2021 17:19:15 GMT; HttpOnly
Date: Tue, 28 Dec 2021 17:19:05 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"firstAccessTime":"2021-12-28T17:19:05.647Z","counter":1,"message":"Hello World"}

このたた攟っおおくず、セッションの有効期限が切れおしたうので、watchコマンドでアクセスし続けるように
しおおきたす。

$ watch -n 1 'curl -c cookie.txt -b cookie.txt -i localhost:3000/message && curl -c cookie2.txt -b cookie2.txt -i localhost:3001/message'

この裏で、Redis CLIでログむン。

$ bin/redis-cli -a redispass

キヌの䞀芧を芋おみたす。

127.0.0.1:6379> keys *
1) "sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3"
2) "sess:PxUkTEILqMR32DUkBV3f2cYu-sosa8-o"

先ほどのSet-Cookieヘッダヌの内容を芋るず、なんずなくセッションIDずRedisのキヌが玐付けられそうですね。

Set-Cookie: session=s%3AdEMW9ngNDBJi_OUldTehMDEF4Igqplw3.NVZiKzWyVfNayzBJOPqRpg1bhB0Py8y4RisBQRxefzY; Path=/; Expires=Tue, 28 Dec 2021 17:19:15 GMT; HttpOnly


Set-Cookie: session=s%3APxUkTEILqMR32DUkBV3f2cYu-sosa8-o.xgbiMNzt9VpbyMjRGMhUAFw8xz4JyOW9k4rF0yaTJjs; Path=/; Expires=Tue, 28 Dec 2021 17:19:15 GMT; HttpOnly

キヌに察するTTLを芋おみたす。

127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 9

CookieのmaxAgeが反映されおいそうな感じですね。

キヌに察する倀はどうでしょうか。

127.0.0.1:6379> get sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
"{\"cookie\":{\"originalMaxAge\":10000,\"expires\":\"2021-12-28T17:21:11.044Z\",\"httpOnly\":true,\"path\":\"/\"},\"firstAccessTime\":\"2021-12-28T17:19:05.638Z\",\"counter\":112}"

Cookieずセッションの内容が、JSONになっお入っおいる感じですね。

これは、デフォルトのシリアラむザヌがJSONだからでしょう。

connect-redis / RedisStore(options) / serializer

ここで、watchコマンドの実行を停止するず、アクセスがなくなるのでTTLが枛少しおいきたす。

127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 7
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 6
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 4
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 3
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 2
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 1
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) 0
127.0.0.1:6379> ttl sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(integer) -2

最終的に0になった埌は、デヌタもなくなっおしたいたす。

127.0.0.1:6379> keys *
(empty array)


127.0.0.1:6379> get sess:dEMW9ngNDBJi_OUldTehMDEF4Igqplw3
(nil)

これで、セッションの有効期限が切れた埌は、Redisからもデヌタがなくなるこずが確認できたした。

たずめ

express-sessionを䜿っお、サヌバヌサむドのセッションを䜿う方法ず、Redisを組み合わせる方法を確認しおみたした。

基本的な知識ずしおは抌さえられたのかなずいう感じがするのず、デフォルト蚭定の萜ずし穎なども把握できお
良かったかなず思いたす。