Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Express.jsコードリーディング

$
0
0

Express.jsのコードを読んでみたのでまとめることにしました。
普段の業務ではNode.jsもExpressも利用していないので、JavaScriptの基本的なコーディングやライブラリの内容なども合わせてまとめておこうと思います。
間違っている内容がありましたら編集リクエストをいただけますと幸いです。

Expressコードリーディング

対象のコード

今回コードリーディングする対象のコードです。
ただHello Worldするだけのコードになります。

index.js
constexpress=require('express')constapp=express()app.get('/',(req,res)=>{res.send('Hello World');})app.listen(3000);

nodeでindex.jsを起動します。
起動後curlでレスポンスを確認します。

$node index.js 
$curl localhost:3000
Hello World

expressインスタンス生成

まずはconst app = express()の処理を追っていきます。

ここで最初に呼び出されるのは、expressライブラリのルートディレクトリ上に存在するindex.jsファイルです。

index.js
module.exports=require('./lib/express');

上記はmoduleの作成のみで、処理本体はlib/express.jsファイルです

lib/express.js
/**
 * Expose `createApplication()`.
 */exports=module.exports=createApplication;/**
 * Create an express application.
 *
 * @return {Function}
 * @api public
 */functioncreateApplication(){varapp=function(req,res,next){app.handle(req,res,next);};mixin(app,EventEmitter.prototype,false);mixin(app,proto,false);// expose the prototype that will get set on requestsapp.request=Object.create(req,{app:{configurable:true,enumerable:true,writable:true,value:app}})// expose the prototype that will get set on responsesapp.response=Object.create(res,{app:{configurable:true,enumerable:true,writable:true,value:app}})app.init();returnapp;}

一つずつ処理を見ていきます。

アプリケーション作成

まずはこちらになります。

lib/express.js
varapp=function(req,res,next){app.handle(req,res,next);};

appのfunctionを生成していますが、こちらがExpressの入り口となるアプリケーション部分です。
HTTPリクエストをされるとここが実行されます。

プレーンなNode.jsだけでここを表現すると以下のような処理になります。

サンプル
consthttp=require('http')varapp=function(req,res,next){app.handle(req,res,next);};app.handle=functionhandle(req,res,callback){res.writeHead(200,{'Content-Type':'text/plain'});res.end('Hello World\n');}http.createServer(app).listen(3000);

アプリケーションメソッドをコピーしてくる(継承する)

appはEventEmitterとprotoをコピーします。

lib/express.js
varEventEmitter=require('events').EventEmitter;varmixin=require('merge-descriptors');varproto=require('./application');varreq=require('./request');varres=require('./response');中略mixin(app,EventEmitter.prototype,false);mixin(app,proto,false);// expose the prototype that will get set on requestsapp.request=Object.create(req,{app:{configurable:true,enumerable:true,writable:true,value:app}})// expose the prototype that will get set on responsesapp.response=Object.create(res,{app:{configurable:true,enumerable:true,writable:true,value:app}})

ここでコピーしているproto(application.js)がapplicationのメイン処理が入っている部分です。
具体的な処理は後々出てくるのでそこで説明します。

その他、EventEmitterライブラリやExpressプロジェクトのルートディレクトリにあるrequest.js、response.jsの内容もコピーしています。

アプリケーション初期設定

lib/express.js
app.init();

ここで読みだしているのは、lib/application.js内に設定されているinitメソッドです。
この中で行なっているのは、アプリケーションをデフォルトの設定で設定しています。

例えば、HTTPヘッダーにx-powered-byを返却するなどはこの中で設定しています。

Routingの設定

次にRouting設定です。

app.get('/',(req,res)=>{res.send('Hello World');})

の処理です。
こちらで呼び出されるapp.getlib/application.jsファイルの以下処理になります。

lib/application.js
methods.forEach(function(method){app[method]=function(path){if(method==='get'&&arguments.length===1){// app.get(setting)returnthis.set(path);}this.lazyrouter();varroute=this._router.route(path);route[method].apply(route,slice.call(arguments,1));returnthis;};});

HTTPメソッドに紐づくfunctionの作成

以下の処理でHTTPメソッド(GET,POST,PUT,PATCH,DELETEなど)と同一名称のメソッドをappクラスに作成します。

lib/application.js
varmethods=require('methods');methods.forEach(function(method){app[method]=function(path){// 中略};});

Routerクラスの移譲

次はthis.lazyrouter();の部分です。
ここは以下の処理になります。

lib/application.js
varRouter=require('./router');app.lazyrouter=functionlazyrouter(){if(!this._router){this._router=newRouter({caseSensitive:this.enabled('case sensitive routing'),strict:this.enabled('strict routing')});this._router.use(query(this.get('query parser fn')));this._router.use(middleware.init(this));}};

appクラスの_routerプロパティにRouterクラスを委譲しています。
Routerクラスはlib/router/index.jsファイルを参照しています。

this._router.useはMiddlewareの設定です。

this._router.use(query(this.get('query parser fn')));の設定は、クエリストリングをObjectに変換してくれます。

this._router.use(middleware.init(this));はMiddlewareの初期設定を行います。
X-Powered-Byヘッダーを付与するのもこちらになります。

Routeクラスの取得とLayerクラスをスタックに追加

続いては以下の処理です。
var route = this._router.route(path);

こちらは先ほど作成した_routerのrouteメソッドを利用して、RouteクラスとLayerクラスを作成します。
実際の処理はこちらです。

lib/router/index.js
varRoute=require('./route');varLayer=require('./layer');/**
 * Create a new Route for the given path.
 *
 * Each route contains a separate middleware stack and VERB handlers.
 *
 * See the Route api documentation for details on adding handlers
 * and middleware to routes.
 *
 * @param {String} path
 * @return {Route}
 * @public
 */proto.route=functionroute(path){varroute=newRoute(path);varlayer=newLayer(path,{sensitive:this.caseSensitive,strict:this.strict,end:true},route.dispatch.bind(route));layer.route=route;this.stack.push(layer);returnroute;};

RouteクラスとLayerクラスを生成します。
そしてLayerクラスのrouteプロパティにRouteクラスを移譲しています。
作成したLayerクラスはRouterクラスのstackに格納しています。

呼び出し元にはRouteクラスを返却しています。

RouteクラスにLayerクラスをセット

続いては以下の処理です。
route[method].apply(route, slice.call(arguments, 1));

こちらは先ほど作成したRouteクラスに新しいLayerクラス作成したうえでセットします。
実際の処理はこちらです。

lib/router/route.js
methods.forEach(function(method){Route.prototype[method]=function(){varhandles=flatten(slice.call(arguments));for(vari=0;i<handles.length;i++){varhandle=handles[i];if(typeofhandle!=='function'){vartype=toString.call(handle);varmsg='Route.'+method+'() requires a callback function but got a '+typethrownewError(msg);}debug('%s %o',method,this.path)varlayer=Layer('/',{},handle);layer.method=method;this.methods[method]=true;this.stack.push(layer);}returnthis;};});

ここではLayerクラスを作成してかつ、指定したメソッドをLayerクラスにセットします。
そして作成したLayerクラスをRouteクラスのstackに追加しています。

httpのlisten開始

最後に以下の構文でhttpをport3000で開始します。
app.listen(3000);

実際の処理はこちらです。

lib/application.js
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

中身については、アプリケーション作成の部分で説明した内容と同じなので省略します。

リクエストを受け付けた時の挙動

最後にリクエストを受け付けた時の挙動についてです。
このような順番で処理されます。

app->app.handle->router.handle->Layer.handle->Route.dispatch->Layer.hanldle->Express利用者が設定したfunction

ここでRouter、Layer、Routeクラスの関係性について図にまとめてみました。

Routing.png

Expressで利用しているNode.jsの構文やライブラリについて

上記まででExpressの処理の流れをざっと確認してきましたが、実際にコードリーディングするにあたり、普段見慣れないNode.jsの構文やライブラリがいつかあると思います。
そこでそれぞれの構文がどういうものなのかをまとめます。

mixin

参考:merge-descriptors
まずはmixinからです。
Expressでは、以下のように利用しています。

lib/express.js
varapp=function(req,res,next){app.handle(req,res,next);};mixin(app,EventEmitter.prototype,false);mixin(app,proto,false);

mixinがどういうものか見ていきます。

サンプル
varmixin=require('merge-descriptors');varapp=function(){};varapplication=function(){}application.handle=function(){return'Hello mixin';}mixin(app,application,false);console.log(app.handle());// Hello mixin

このようにappクラスにapplicationクラスのメソッドを継承させています。
これがmixin(merge-descriptors)の利用方法になります。

EventEmitter

参考:GitHub->node->Events

続いてはEventEmitterです。
EventEmitterを利用することでイベント駆動型の設計が可能になります。

サンプル
varmixin=require('merge-descriptors');varEventEmitter=require('events').EventEmitter;varapp=function(){};mixin(app,EventEmitter.prototype,false);app.init=function(){app.on('mount',()=>{console.log('emit!');});console.log('init')}app.send=function(){app.emit('mount');}app.init();// initapp.send();// emit!

上記のように、on.('イベント名')で事前に処理を受け付けるタイミングを作成してemit('イベント名')を実行する際に、onの中に指定したfunctionが実行されます。

Object.create

続いてはObject.createについてです。
Expressでは以下のように利用されています。

Expressでの利用
// expose the prototype that will get set on requestsapp.request=Object.create(req,{app:{configurable:true,enumerable:true,writable:true,value:app}})

こちらもmixinと同じようにメソッドを継承します。

サンプル
varapp=function(){};varreq=function(){};req.headers=function(){return'req!';}app.request=Object.create(req);console.log(app.request.headers());//  req!

apply&call&bind

applyとcallとbindについてです。
Express.jsでは様々な場所でこのAPIが利用されています。

サンプル
route[method].apply(route,slice.call(arguments,1));varargs=slice.call(arguments,1);

こちらは様々な記事で解説されているため、詳細は割愛しますが、簡単に試して見ます。

サンプル
varapp=function(){}app.say=function(name,age){returnname+''+age+'歳です。';}console.log(app.say('Taro',16));// Taroは16歳です。console.log(app.say.apply(app,['Taro',16]));// Taroは16歳です。console.log(app.say.call(app,'Taro',16));// Taroは16歳です。varsay=app.say.bind(app,'Taro',16);console.log(say());

参考

Expressガイド

他にもExpressの解説をされている方がいらっしゃいましたので紹介します。
エンジニアの教養、フレームワーク解剖学【Express編】


Viewing all articles
Browse latest Browse all 8691

Trending Articles