Skip to content

Latest commit

 

History

History
202 lines (123 loc) · 10.1 KB

File metadata and controls

202 lines (123 loc) · 10.1 KB

Connect

この文章はConnect70 3.4.0を元に書かれています。

ConnectはNode.jsで動くHTTPサーバフレームワークです。 _middleware_という拡張する仕組みを持っていて、Connectが持つ機能自体はとても少ないです。

この章ではConnectの_middleware_の仕組みについて見て行きましょう。

どう書ける?

Connectを使った簡単なEchoサーバを書いてみましょう。 Echoサーバとは、送られてきたリクエストの内容をそのままレスポンスとして返すサーバのことです。

import, connect-echo-example.js

このEchoサーバに対して、以下のようなリクエストBodyを送信すると、レスポンスとして同じ値が返ってきます。

{
    "key": "value"
}

app.use(middleware) という形で、_middleware_と呼ばれる関数にはrequestresponseといったオブジェクトが渡されます。 このrequestresponseを_middleware_で処理することでログを取ったり、任意のレスポンスを返したり出来るようになっています。

Echoサーバでは req.pipe(res); という形でリクエストをそのままレスポンスとして流す事で実現されています。

middlewareをモジュールとして実装

もう少し_middleware_をプラグインらしくモジュールとして実装したものを見てみます。

次のconnect-example.jsは、あらゆるリクエストに対して、 "response text"というレスポンスを"X-Content-Type-Options"ヘッダを付けて返すだけのものです。

それぞれの処理を_middleware_としてファイルを分けて実装し、app.use(middleware)で処理を追加しています。

import nosniff.js

import hello.js

import errorHandler.js

import connect-example.js

基本的にどの_middleware_もapp.use(middleware)という形で拡張でき、 モジュールとして実装すれば再利用もしやすい形となっています。

Note _middleware_となる関数の引数が4つであると、それはエラーハンドリングの_middleware_とするという、Connect独自のルールがあります。

どういう仕組み

Connectの_middleware_がどのような仕組みで動いているのかを見ていきます。

appに登録した_middleware_は、リクエスト時に呼び出されています。 そのため、appのどこかに利用する_middleware_を保持していることは推測できると思います。

Connectではapp.stackに_middleware_を配列として保持しています。 次のようにしてapp.stackの中身を表示してみると、_middleware_が登録順で保持されていることがわかります。

import connect-trace-example.js

Connectが登録された_middleware_をどう処理するかというと、 サーバがリクエストを受け取った時に、それぞれ順番に呼び出しています。

上記の例だと以下の順番で_middleware_が呼び出されることになります。

  • nosniff
  • hello
  • errorHandler

エラーハンドリングの_middleware_は処理中にエラーが起きた時のみ呼ばれます。

そのため、通常は nosniff.js -> hello.js の順で呼び出されます。

import nosniff.js

nosniff.jsは、HTTPヘッダを設定し終わったらnext()を呼び出していて、 このnext()が次の_middleware_へ行くという意味になります。

次に、hello.jsを見てみると、next()がないことがわかります。

import hello.js

next()がないということはhello.jsがこの連続する_middleware_の最後となっていることがわかります。 仮に、これより先に_middleware_が登録されていたとしても無視されます。

つまり、処理的には以下のようにstackを先頭から一個づつ取り出して、処理していくという方法が取られています。

Connectの行っている処理を抽象的なコードで書くと以下のような形となっています。

let req = "...",
    res = "...";
function next(){
    let middleware = app.stack.shift();
    // nextが呼ばれれば次のmiddleware
    middleware(req, res, next);
}
next();// 初回

このような_middleware_を繋げた形を_middleware stack_と呼ぶことがあります。

_middleware stack_で構成されるHTTPサーバとして、PythonのWSGI MiddlewareやRubyのRackなどがあります。 ConnectはRackと同じくuseで_middleware_を指定することからも分かりますが、 Rackを参考にして実装されています。

次は、先ほど抽象的なコードとなっていたものを具体的な実装にしながら見ていきます。

実装してみよう

JunctionというConnectライクな_middleware_をサポートしたものを作成してみます。

Junctionは、use(middleware)process(value, (error, result) => { });を持っているシンプルなクラスです。

import junction.js

実装を見てみると、useで_middleware_を登録して、processで登録した_middleware_を順番に実行していきます。 そのため、Junction自体は渡されたデータは何も処理せずに、_middleware_との中継のみをしています。

登録する_middleware_はConnectと同じで、処理をしたらnextを呼んで、次の_middleware_が処理するというのを繰り返しています。

使い方はConnectと引数の違いはありますが、ほぼ同じような形で利用できます。

import junction-example.js

どういう用途に向いている?

ConnectやJunctionの実装を見てみると分かりますが、このアーキテクチャでは機能の詳細は_middleware_で実装できます。 そのため、本体の実装は_middleware_に提供するインタフェースの決定、エラーハンドリングの手段の提供するだけでとても小さいものとなっています。

今回は紹介していませんが、Connectにはルーティングに関する機能があります。 しかし、この機能も「与えられたパスにマッチした場合のみに反応する_middleware_を登録する」という単純なものです。

app.use("/foo", function fooMiddleware(req, res, next) {
    // req.url starts with "/foo"
    next();
});

このアーキテクチャは、入力があり出力がある場合にコアとなる部分は小さく実装できることが分かります。

そのため、ConnectやRackなどのHTTPサーバでは「リクエストに対してレスポンスを返す」というのが決まっているので、 このアーキテクチャは適しています。

どういう用途に向いていない?

このアーキテクチャでは機能の詳細が_middleware_で実装できます。 その中で多くの機能を_middleware_で実装していくと、_middleware_間に依存関係が生じることがあります。

これにより、use(middleware) で登録する順番が変わるだけで挙動が変わる事があります。 _middleware_は柔軟ですが、_middleware_間で起きる前提の解決を利用者が行う必要があります。

そのため、プラグイン同士の強い独立性や明確な依存関係を扱いたい場合には不向きといえるでしょう。

これらを解消するためにコアはそのままにして、最初から幾つかの_middleware stack_を作ったものが提供されるケースもあります。

エコシステム

Connect自体の機能は少ないため、その分_middleware_が多くあるのが特徴的です。

また、それぞれの_middleware_が小さな単機能であり、それを組み合わせて使うように作られているケースが多いです。

これは、_middleware_が層となっていてそれを重ねていく作り、つまり_middleware stack_の形を取ることが多いからであるとも言えます。

pylons_as_onion

ミドルウェアでラップするプロセスは、概念的にたまねぎの中の層と同様の構造をもたらします。 WSGI ミドルウェアより引用

この仕組みを使っているもの

  • Express
    • Connectと_middleware_の互換性がある
    • 元々はConnectを利用していたが4.0.0で自前の実装に変更
  • wooorm/retext
    • useでプラグインを登録していくテキスト処理ライブラリ

まとめ

ここではConnectのプラグインアーキテクチャについて学びました。

  • Connectは_middleware_を使ったHTTPサーバライブラリである
  • Connect自体は機能は少ない
  • 複数の_middleware_を組わせてアプリケーションを作ることができる

参考資料