kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

expressのミドルウェアをほんの少しだけ理解する

f:id:kun432:20220213014606p:plain

引き続き、ちゃんと理解できていないexpress周りをコツコツいじってます。今回はミドルウェアについて。

目次

expressのミドルウェアについて

公式もサンプロコードがあって、十分わかりやすいけど

ここが図にまとまっていてとてもイメージしやすかった。

ということで早速ロガー的なものを書いてみる。ベースは前回のexpress-session+Redisのものを使う。

const express = require('express');

const session = require("express-session");
const redis = require("ioredis");
const RedisStore = require("connect-redis")(session);

const logger = function (req, res, next) {
  console.log(JSON.stringify(req.session));
  next();
}

const app = express();

app.set('port', (process.env.PORT || 3000));

app.use(
    session({
        secret: 'secret',
        cookie: { maxAge: 60 * 1000 },
        resave: false,
        saveUninitialized: false,
        store: new RedisStore({
            url: process.env.REDIS_URL,
            client: redis.createClient({
                url: process.env.REDIS_URL
            })
        })
    }),
);

app.use(logger);

app.get('/', function(request, response) {
  let session = request.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'));
});

追加したのはここ。

const logger = function (req, res, next) {
  console.log(JSON.stringify(req.session));
  next();
}

セッション情報を記録するような関数loggerを作成した。そして以下でミドルウェアとして使うことを宣言。

app.use(logger);

元のコードではapp.get('/')のメインの処理で書いてたのをミドルウェアとして外出ししたような感じ。

実行

$ node index.js
Node app is running at localhost:3000

アクセスしてみるとコンソールにこういう感じで表示される。

{"cookie":{"originalMaxAge":60000,"expires":"2022-02-12T17:41:46.901Z","httpOnly":true,"path":"/"}}
{"cookie":{"originalMaxAge":60000,"expires":"2022-02-12T17:41:46.911Z","httpOnly":true,"path":"/"},"count":1}
{"cookie":{"originalMaxAge":60000,"expires":"2022-02-12T17:41:47.889Z","httpOnly":true,"path":"/"},"count":2}
{"cookie":{"originalMaxAge":60000,"expires":"2022-02-12T17:41:49.003Z","httpOnly":true,"path":"/"},"count":3}

ちなみに、express-sessionもミドルウェアとして使っていて、順序としてはこうなっている。

app.use(
    session({
(...snip...)
});
app.use(logger);

これを逆にしてみる。

app.use(logger);
app.use(
    session({
(...snip...)
});

起動し直して再度アクセスしてみるとこうなる。

$ node index.js
Node app is running at localhost:3000
undefined
undefined
undefined
undefined

ミドルウェアはapp.useで呼び出した順番で処理される。今回のロガーではセッション情報が必要になるので、その処理を担っているexpress-sessionよりもあとに呼び出さないと何も処理されないということ。

最初に作った関数をもう一度見てみる。

const logger = function (req, res, next) {
  console.log(JSON.stringify(req.session));
  next();
}

ミドルウェア関数の場合はnextという引数を追加して、処理の最後にnext()することで、次のミドルウェアに順にチェーン的に処理されるらしい。

こういう書き方もできる。

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: 60 * 1000 },
        resave: false,
        saveUninitialized: false,
        store: new RedisStore({
            url: process.env.REDIS_URL,
            client: redis.createClient({
                url: process.env.REDIS_URL
            })
        })
    }),
);

app.get('/', function(req, res, next) {
  console.log(JSON.stringify(req.session));
  next();
}, function(req, res) {
  let session = req.session;
  if (!!session.count) {
    session.count += 1;
  } else {
    session.count = 1;
  }

  res.send(`Hello World! count: ${session.count}\n`);
});

app.listen(app.get('port'), function() {
  console.log("Node app is running at localhost:" + app.get('port'));
});

こちらの場合は、ミドルウェア関数を作ってapp.useするのではなく、app.getでコールバック関数を直接間に挟む感じ。

おまけ

ask-sdkでいうところのrequest/response interceptor的なものをミドルウェアとして用意しておくと便利だと思って、ざっと調べてみた感じ、リクエストは自分でベタに書くなり、bunyan/log4js使って書くなりすれば良いけど、レスポンスの場合は一工夫いるみたい。