koa入門(ミドルウェアの書き方)

Koaのmiddleware作成方法

さて、先日 koa入門 という入門記事を書いてみました。今回はそれにともなってKoaのミドルウェアの作り方を中心に説明します。

また、 koa-livereload というKoaのミドルウェアも公開してみました。それについても説明します。

Expressのミドルウェアの書き方:

Expressのミドルウェアの書き方をまずはおさらい。

app.use(function (req, res, next){
  // requestとresponseとnextを持つ関数を受け取る。
  
  // 適当に/_chkに来たら、OKを返すミドルウェアを作る。
  if (req.url === "/_chk") {
    console.log("OK");
    res.send(200, "OK");
  }
  
  // next()を呼び出すと次のミドルウェアを呼ぶ。
  next();
});

Koaのミドルウェアの書き方:

基本的には同様ですが、requestとresponseの取得方法が異なります。

// generator function (function *)をcallbackとして受け取る。
app.use(function *(next) {
  // nextだけを受け取る
  // this がコンテキスト
  // this.requestでrequestを取得できる。
  if (this.request.url === "/_chk") {
     console.log("OK");
     // this.responseでresponseを取得できる。
     this.response.status = 200;
     // ちなみにthis.bodyはthis.response.bodyのalias
     this.body = "OK";
  }
  // yield nextで次のmiddlewareを呼ぶ
  yield next;
});

ポイントになるのは以下の点ですね。

  1. app.useにgenerator function( function * ) を渡す必要がある。
  2. this.requestでリクエスト、this.responseでレスポンスをもらうことができる。
  3. yield nextで次のmiddlewareを呼び出すことができる。

Koa middleware Best Practice

Koaのミドルウェアベストプラクティスが存在します。

ミドルウェアを書くなら一度は目を通しておいたほうが良いです。

ちなみにかずぽんさんも翻訳してくれているので、ここではポイントだけ書きます。

ミドルウェアに引数を渡すときはgenerator functionを返す関数を作る
//引数を取る関数を作る
function responseTime(prepend, append) {
  return function *(next){
    var start = new Date;
    yield next;
    var ms = new Date - start;
    // ここで引数を使う
    console.log('%s - %s %s', prepend,  ms, append);
  };
}

// 引数に対して値を渡す
app.use(responseTime("response time", "ms"));
yield nextで次のミドルウェアを呼び出す
function responseTime(prepend, append) {
  return function *(next){
    var start = new Date;
    // ここで次のミドルウェアを呼び出す
    yield next;
    var ms = new Date - start;
    console.log('%s - %s %s', prepend,  ms, append);
  };
}

app.use(responseTime("response time", "ms"));
app.use(function *(next){
  // yield nextが呼ばれるとここが呼ばれる。
  this.body = 'Hello World';
  // yield nextを呼び出した時に次のミドルウェアが存在しない場合は何もしないfunctionが実行されるため、エラーにはならない。
  yield next;
});

yield nextを呼び出した時に次のミドルウェアが存在しなくてもその時はnoopの関数が呼ばれるためエラーにはなりません。

coのフローモデルで非同期処理を実現する

koaのミドルウェアを書くときの一番肝の部分ですね。いわゆる従来のコールバックじゃなくてTJ作成のcoを使って記述したほうがcallback地獄にならずに済みます。

coをベースとしたライブラリはたくさんあるので、今後koaでライブラリを選定するときはcoベースのものを使うといいかもしれません。

試しにfsモジュールとfsをラップしたco-fsというモジュールを使った時の違いを記述します。


fs(express)の場合:

var fs = require('fs');

app.use(function(req, res, next){
  fs.readdir('.', function(err, paths) {
    fs.readFile('./' + paths[0], 'utf8', function(err, file) {
      if (err) return;
      res.send(200, file);
    });
  });
});

co-fs(koa)の場合:

var fs = require('co-fs');

app.use(function *(next){
  var paths = yield fs.readdir('.');
  var file = yield fs.readFile('./'+paths[0], 'utf8');
  this.body = file;
});

co-fsの方がcallbackが少なくなっている点、またcallbackで受けとるはずのpathsやfileなどの結果となる値を返り値として受け取っていることがわかると思います。

thunkifyとcoというモジュールでこれを実現しているので、coベースのライブラリの書き方も抑えておくといいと思います。

koa-livereloadを作ってみた。

実際にミドルウェアを作ってみようと思ってkoa-livereloadを作ってみました。

koa-livereloadはconnect-livereloadのkoaでも使えるようにしたもので、koaからserveされるHTMLの中にlivereload用のスクリプトを埋め込んでくれるミドルウェアです。

grunt-contrib-watchやgrunt-contrib-livereloadを使って変更時にブラウザをライブリロードするための代物。

install
npm install koa-livereload --save-dev
How to use
var koa = require('koa');
var route = require('koa-route');
var serve = require('koa-static');
var views = require('co-views');
// ミドルウェアを読み込む
var livereload = require('koa-livereload');
var app = koa();

var render = views(__dirname + '/views', { map : {html : 'jade'}});

// livereloadを読み込む
app.use(livereload());

// GET /views => render template engine
app.use(route.get('/views', function *(next) {
  this.body = yield render('index.jade', {name: "koa"});
}));

app.listen(3000);

こんな風にした上で、

Gruntfile.jsに以下のように書く。

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      options: {
        livereload: true,
      },
      test: {
        files: ['**/*.js'],
      },
    },
  });
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('default', ['watch']);
};

gruntでlivereloadサーバーを起動しておく。

$ grunt

そうすると、scriptタグにlivereload.jsを勝手に入れてくれる機能です。

この辺りのexamplesを参考にしてくれると詳細な使い方がわかると思います。