Skip to content

Connectの仕組み #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions ja/connect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Echoサーバとは、送られてきたリクエストの内容をそのまま
```

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

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

Expand All @@ -47,4 +47,69 @@ Echoサーバでは `req.pipe(res);` という形でリクエストをそのま
基本的にどの_middleware_も`app.use(middleware)`という形で拡張でき、
モジュールとして実装すれば再利用もしやすい形となっています。

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

## どういう仕組み

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

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

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

[import connect-trace-example.js](../../src/connect/connect-trace-example.js)

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

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

- errorHandler
- nosniff
- hello

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

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

[import nosniff.js](../../src/connect/nosniff.js)

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

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

[import hello.js](../../src/connect/hello.js)

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

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

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

```js
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を参考にして実装されています。

- [Ruby - Rack解説 - Rackの構造とRack DSL - Qiita](http://qiita.com/higuma/items/838f4f58bc4a0645950a#2-5 "Ruby - Rack解説 - Rackの構造とRack DSL - Qiita")

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

## 実装してみよう
2 changes: 2 additions & 0 deletions src/connect/connect-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import assert from "assert";
import connect from "connect";
import http from "http";
import fetch from "node-fetch";

const responseText = "response text";
let app = connect();
// add Error handling
Expand All @@ -14,6 +15,7 @@ app.use(errorHandler());
app.use(nosniff());
// respond to all requests
app.use(hello(responseText));

//create node.js http server and listen on port
let server = http.createServer(app).listen(3000, request);

Expand Down
22 changes: 22 additions & 0 deletions src/connect/connect-trace-example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use strict";
import errorHandler from "./errorHandler";
import hello from "./hello";
import nosniff from "./nosniff";
import connect from "connect";

const responseText = "response text";
let app = connect();
// add Error handling
app.use(errorHandler());
// add "X-Content-Type-Options" to response
app.use(nosniff());
// respond to all requests
app.use(hello(responseText));

// print middleware list
app.stack.map(({handle}) => console.log(handle));
/* =>
[Function: errorHandling]
[Function: nosniff]
[Function: hello]
*/
4 changes: 2 additions & 2 deletions src/connect/errorHandler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
export default function errorHandler() {
return function (err, req, res, next) {
export default function () {
return function errorHandling(err, req, res, next) {
console.error(err.stack);
res.status(500).send(err.message);
next();
Expand Down
2 changes: 1 addition & 1 deletion src/connect/hello.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
export default function (text) {
return function (req, res) {
return function hello(req, res) {
res.end(text);
};
}
2 changes: 1 addition & 1 deletion src/connect/nosniff.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function setHeaders(res, headers) {
});
}
export default function () {
return function (req, res, next) {
return function nosniff(req, res, next) {
setHeaders(res, {
"X-Content-Type-Options": "nosniff"
});
Expand Down