express.js
Table of Contents
1 router
1.1 index.js
1.1.1 init & proto
뭔가 기능을 사용하기 편하도록 미리 저장해놓는 초기화 코드가 있다.
또한 아래에 있는 Route, Layer 모듈을 가져온다.
var Route = require('./route');
var Layer = require('./layer');
var methods = require('methods');
var mixin = require('utils-merge');
var debug = require('debug')('express:router');
var deprecate = require('depd')('express');
var flatten = require('array-flatten');
var parseUrl = require('parseurl');
var setPrototypeOf = require('setprototypeof')
var objectRegExp = /^\[object (\S+)\]$/;
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
또한 proto는 Router 를 주어진 options 을 이용하여 초기화 한다.
/**
* Initialize a new `Router` with the given `options`.
* @param {Object} [options]
* @return {Router} which is a callable function
* @public
*/
var proto = module.exports = function(options) {
var opts = options || {}
function router(req, res, next) {
router.handle(req, res, next);
}
// mixin Router class functions
// `router` 함수의 프로토타입을 `proto`로 설정하여 `proto`의 메서드를 상속받는다.
setPrototypeOf(router, proto)
router.params = {}; // 파라미터에 저장할 객체.
router._params = []; // 내부적으로 사용할 파라미터 배열.
router.caseSensitive = opts.caseSensitive; // 경로의 대소문자 구분 여부.
router.mergeParams = opts.mergeParams; // 부모 라우터의 파라미터와 병합 여부.
router.strict = opts.strict; // 경로의 끝에 슬래시가 있어야 하는지 여부.
router.stack = [] // 미들웨어와 라우트를 저장할 배열.
return router;
};
1.1.2 param
- 파라미터 플레이스홀더와 콜백 매핑:
- 주어진 파라미터 플레이스 홀더(parameter placeholder) `name` 을 특정 콜백함수와 매핑함.
- 파라미터 매핑(parameter mapping)의 용도:
- 경로에 있는 플레이스홀더 파라미터에 대해 사전 조건(pre-condition)을 제공하는데 사용됨.
- 예를들어, `_:userid_` 파라미터는 데이터베이스에서 사용자 정보를 자동으로 로드할 수 있다.
- 콜백함수의 서명:
- 미들웨어와 같은 시그니처를 가지지만, placeholder 값이 추가로 전달된다.
- next() 함수 호출:
- 미들웨어와 마찬가지로 `next()` 함수를 호출하여 다음 라우트 또는 파라미터 함수로 이어진다.
- 요청이 중단되지 않도록 요청에 응답하거나 `next()` 를 호출해야 한다.
proto.param = function param(name, fn) {
// param logic
if (typeof name === 'function') {
// 경고메시지: 함수형 파라미터 사용은 권장되지 않음.
deprecate('router.param(fn): Refactor to use path params');
// 함수형 파라미터를 `_params` 배열에 추가.
this._params.push(name);
return;
}
// apply param functions
var params = this._params;
var len = params.length;
var ret;
// 파라미터 이름 전처리
if (name[0] === ':') {
deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.slice(1)) + ', fn) instead')
// 이름에서 콜론 제거: 파라미터 이름이 콜론으로 시작하면 제거
// 권장 사항: 콜론 없이 사용하자
name = name.slice(1)
}
// 기존 파라미터 함수 적용
for (var i = 0; i < len; ++i) {
// _params 배열의 각 함수에 대해 파라미터 함수 적용(apply)
// 적용 후 리턴(ret)값이 있으면 fn을 업데이트
if (ret = params[i](name, fn)) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
// 유효성검사 : `fn` 이 함수가 아니면 오류 발생
if ('function' !== typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
// 파라미터 미들웨어 추가
// `params` 객체에 파라미터 이름으로 미들웨어 추가
(this.params[name] = this.params[name] || []).push(fn);
return this;
};
- 사용 사례
사용자 정보 로드
// user_id 가 경로에 있을 때마다 미들웨어 실행 app.param('user_id', function(req, res, next, id){ // 유저 로드 로직 // id를 이용해 유저 정보를 로드하고 `req.user` 에 저장. User.find(id, function(err, user){ if (err) { return next(err); } else if (!user) { return next(new Error('failed to load user')); } req.user = user; next(); }); });로그 기록: 특정 파라미터가 요청에 포함될 때마다 로그 기록
app.param('order_id', function(req, res, next, id) { console.log('Order ID:', id); next(); });파라미터 유효성 검사
app.param('productId', function(req, res, next id) { if (!isValidProductId(id)) { return res.status(400).send('Invalid product Id'); } })
1.1.3 handle
- 요청 미들웨어 및 라우트를 순차적으로 처리
- 각 레이어와 경로를 비교하여 적절한 라우트로 디스패치
- 오류발생시 다음 미들웨어로 오류 전달
- 자동으로 OPTIONS 응답 생성
- 경로 파라미터를 병합하고 처리
proto.handle = function handle(req, res, out) {
// `self` : 현재 `Router` 인스턴스 참조.
var self = this;
debug('dispatching %s %s', req.method, req.url);
// idx : 현재 스택의 인덱스
// protohost : 프로토콜과 호스트 부분 추출
// removed : 제거된 경로
// slashAdded : 패스에 슬래시 추가 여부
// sync : 동기 호출 횟수.
// paramcalled : 파라미터 호출 상태.
var idx = 0;
var protohost = getProtohost(req.url) || ''
var removed = '';
var slashAdded = false;
var sync = 0
var paramcalled = {};
// store options for OPTIONS request
// only used if OPTIONS request
var options = [];
// middleware and routes
var stack = self.stack;
// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
var done = restore(out, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
// 옵션 요청 처리
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}
// setup basic req values
// `req.baseUrl`은 현재 라우터의 baseUrl을 설정.
// 중첩된 라우터에서 상위 라우터의 기본 URL을 유지하는데 사용됨.
// `parentUrl` = 상위 라우터의 `baseUrl`
req.baseUrl = parentUrl;
// `req.originalUrl`은 초기 요청 URL을 저장한다.
// `req.originalUrl`이 이미 설정되어 있지 않으면, `req.url` 값을 사용한다.
// 원래 요청 URL을 추적하여 요청 흐름 중 변경되지 않도록 한다.
req.originalUrl = req.originalUrl || req.url;
next();
function next() {...}
function trim_prefix(layer, layerError, layerPath, path) {...}
};
- next()
next는 Express.js의 요청 처리 흐름을 제어하는 핵심 함수이다. 이 함수는 미들웨어 스택에서 다음 미들웨어나 라우트로 제어를 전달한다.function next(err) { // 1. 오류 처리: var layerError = err === 'route' ? null : err; // remove added slash 추가된 슬래시 제거 if (slashAdded) { req.url = req.url.slice(1); slashAdded = false; } // restore altered req.url // `removed` 길이가 0이 아니면 원래 `baseUrl`, `url` 로 변경. // `removed` 는 `trim_prefix` 에서 변경을 수행함. if (removed.length !== 0) { req.baseUrl = parentUrl; req.url = protohost + removed + req.url.slice(protohost.length); removed = ''; } // signal to exit router // error 타입이 'router' 면 멈춘다. if (layerError === 'router') { setImmediate(done, null); return; } // no more matching layers // 미들웨어 스택의 끝 if (idx >= stack.length) { setImmediate(done, layerError); return; } // max sync stack // 동기 스택 최대 한도 if (++sync > 100) { return setImmediate(next, err); } // get pathname of request // 경로명 가져오기 : getPathname을 이용하여 요청 경로명 추출. var path = getPathname(req); if (path == null) { return done(layerError); } // find next matching layer var layer; var match; var route; // 다음 매칭 레이어 찾기 // `match` 가 `true` 될 때까지 stack을 순회 while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; if (typeof match !== 'boolean') { layerError = layerError || match; } if (match !== true) { continue; } if (!route) { continue; } if (layerError) { match = false; continue; } var method = req.method; var has_method = route._handles_method(method); if (!has_method && method === 'OPTIONS') { appendMethods(options, route._options()); } if (!has_method && method !== 'HEAD') { match = false; } } if (match !== true) { return done(layerError); } if (route) { req.route = route; } req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params; var layerPath = layer.path; self.process_params(layer, paramcalled, req, res, function (err) { if (err) { next(layerError || err); } else if (route) { layer.handle_request(req, res, next); } else { trim_prefix(layer, layerError, layerPath, path); } sync = 0; }); }- matchLayer
레이어에 path를 매칭함.
/** * Match path to a layer. * * @param {Layer} layer * @param {string} path * @private */ function matchLayer(layer, path) { try { return layer.match(path); } catch (err) { return err; } } - trimprefix
layerPath와req.url을 비교 및 수정하고, 요청을 해당 레이어로 전달한다.- protohost는 요청 URL에서 프로토콜과 호스트 부분을 추출한다. 예시 : URL `http://example.com/path/to/resource` -> protohost : `http://example.com`
- path와 layerPath 차이
- path : 현재요청의 경로명.
예시 : 요청 URL이
/user/123인경우path는/user/123용도 : 요청 경로를 기준으로 레이어와 매칭 여부를 확인 - layerPath : 미들웨어 또는 라우트가 설정된 경로.
예시 : 미들웨어가
/user에 설정된 경우layerPath는/user. 용도 : 요청 경로의 일부분이 해당 레이어의 경로와 일치하는지 확인.
- path : 현재요청의 경로명.
예시 : 요청 URL이
function trim_prefix(layer, layerError, layerPath, path) { // 결로 길이 확인 if (layerPath.length !== 0) { // Validate path is a prefix match // layerPath가 path의 접두사인지 확인. if (layerPath !== path.slice(0, layerPath.length)) { next(layerError) return } // Validate path breaks on a path separator // 뒤따르는 문자가 '/', '.' 인지 확인. var c = path[layerPath.length] if (c && c !== '/' && c !== '.') return next(layerError) // Trim off the part of the url that matches the route // middleware (.use stuff) needs to have the path stripped debug('trim prefix (%s) from url %s', layerPath, req.url); removed = layerPath; req.url = protohost + req.url.slice(protohost.length + removed.length) // protohost가 없고, req.url의 첫문자가 '/' 가 아니면 슬래시를 추가함. // Ensure leading slash if (!protohost && req.url[0] !== '/') { req.url = '/' + req.url; slashAdded = true; } // Setup base URL (no trailing slash) // parentUrl : 상위 라우터의 기본 URL // removed : 현재 레이어의 경로 접두사 // 목적 `req.baseUrl` 을 상위URL과 현재 레이어의 경로로 설정하되, `removed` 의 마지막 문자가 `/` 이면 이를 제거하여 설정. req.baseUrl = parentUrl + (removed[removed.length - 1] === '/' ? removed.substring(0, removed.length - 1) : removed); } debug('%s %s : %s', layer.name, layerPath, req.originalUrl); // layerError가 있으면, handle_error, 그렇지 않으면 handle_reqest if (layerError) { layer.handle_error(layerError, req, res, next); } else { layer.handle_request(req, res, next); } }
- matchLayer
- processparams
요청 파라미터를 처리하고, 필요한 경우 파라미터 콜백을 비동기적으로 실행한다. processparam은
param에 등록한 콜백을 실행하는 녀석이다.param함수는 아래처럼 생겼다.app.param('userId', function(req, res, next, id) { // 비동기 데이터베이스 조회 User.findById(id, function(err, user) { if (err) { return next(err); } req.user = user; next(); }); });이렇게 넣으면,
route parameter에 콜백 트리거를 추가할 수 있는데, 이것을 실제로 실행하는 함수가 아래 구현이다.Route parameters는 아래 코드를 보면 설명이 쉽다.Route path: /users/:userId/books/:bookId Request URL: http://localhost:3000/users/34/books/8989 req.params: {"userId": "34", "bookId": "8989"} Route path: /flights/:from-:to Request URL: http://localhost:3000/flights/LAX-SFO req.params: { "from": "LAX", "to": "SFO" } Route path: /plantae/:genus.:species Request URL: http://localhost:3000/plantae/Prunus.persica req.params: { "genus": "Prunus", "species": "persica" } Route path: /user/:userId(\d+) Request URL: http://localhost:3000/user/42 req.params: {"userId": "42"}이런식으로 매핑되는 값이 존재할 때, 콜백을 수행할 수 있는 듯 하다. 즉, 여기서 userId를 얻으면?
user프로퍼티에 user 정보를 가져올 수 있다는 것이다.proto.process_params = function process_params(layer, called, req, res, done) { // this는 Router객체를 말함. var params = this.params; // captured parameters from the layer, keys, and values var keys = layer.keys; // fast track // key 없으면 끝? if (!keys || keys.length === 0) { return done(); } var i = 0; var name; var paramIndex = 0; var key; var paramVal; var paramCallbacks; var paramCalled; // process params in order // param callbacks can be async function param(err) { if (err) { return done(err); } if (i >= keys.length) { return done(); } paramIndex = 0; key = keys[i++]; name = key.name; paramVal = req.params[name]; // Router.params[name] 에 해당 이름에 관련된 콜백들을 가져온다. paramCallbacks = params[name]; paramCalled = called[name]; if (paramVal === undefined || !paramCallbacks) { return param(); } // param previously called with same value or error occurred if (paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== 'route'))) { //restore value req.params[name] = paramCalled.value; // next param return param(paramCalled.error); } called[name] = paramCalled = { error: null, match: paramVal, value:paramVal } paramCallback() } // single param callbacks function paramCallback(err) { var fn = paramCallbacks[paramIndex++]; // store updated value paramCalled.value = req.params[key.name]; if (err) { // store error paramCalled.error = err; param(err); return; } if (!fn) return param(); try { fn (req, res, paramCallback, paramVal, key.name); } catch(e) { paramCallback(e); } } param(); }
1.1.4 use
1.1.5 route
1.1.6 appendMethods
1.1.7 getPathname
1.1.8 getProtohost
1.1.9
1.2 layer.js
1.2.1 Layer
Layer 모듈은 Express.js에서 라우트와 미들웨어의 경로 매칭 및 처리를 담당하는 중요한 구성 요소. 이 모듈은 경로, 옵션, 그리고 핸들러 함수를 받아 각 요청에 대해 적절한 처리를 수행.
- path : 요청이 매칭될 경로
- name : 함수 이름. 없으면
'<anonymous>' - params : 경로 매칭 옵션
- fn : 실제 실행될 핸들러 함수
- faststar : 모든 경로와 일치하는 정규식인지 확인.
- fastslash : 루트 경로와 일치하는지 확인.
fast_star , fast_slack 는 최적화를 위한 것들인듯.
var pathRegexp = require('path-to-regexp');
var debug = require('debug')('express:router:layer');
module.exports = Layer;
function Layer(path, options, fn) {
if (!(this instanceof Layer)) {
return new Layer(path, options, fn);
}
debug('new %o', path)
var opts = options || {};
this.handle = fn;
this.name = fn.name || '<anonymous>';
this.params = undefined;
this.path = undefined;
this.regexp = pathRegexp(path, this.keys = [], opts);
// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
}
1.2.2 handlererorr
단순 에러 핸들링인데, fn.length !== 4 체크로 인자의 개수를 확인하고 에러를 전파하는 듯?
여기 Layer에서 함수는 무조건 4개의 인자가 필요한가보다. 에러를 핸들링하는 함수는 err 인자가 필수이기때문에 4개의 인자가 필요하다.
그리고 예외를 던지면 그것도 next로 보낸다.
/**
* Handle the error for the layer.
*
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
var fn = this.handle;
if (fn.length !== 4) {
// not a standard error handler
return next(error);
}
try {
fn(error, req, res, next);
} catch (err) {
next(err);
}
};
1.2.3 handlerequest
여기는 일반 요청을 핸들링하는 레이어이다. 이곳은 오히려 함수의 인자가 3개를 초과하면 안된다.
/**
* Handle the request for the layer.
*
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
1.2.4 match
아마 Layer에서 가장 중요한 함수일 듯.
- 루트 경로 검사 (fastslash)
- 모든 경로 검사 (faststar)
- 정규 표현식 매칭 (this.regexp.exec(path))
- 특이한점은 this.regexp 는 일반적인 정규식 매칭함수가 아니다.
path-to-regexp라는 모듈을 사용한다. [github link]
- 특이한점은 this.regexp 는 일반적인 정규식 매칭함수가 아니다.
- 매칭 실패 처리
- 매칭된 값 저장
- 파라미터 추출 및 디코딩
- 정규표현식배칭을 이용한
match로 for-loop를 돌면서 파라미터를 추출하고 디코딩한다.
- 정규표현식배칭을 이용한
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
Layer.prototype.match = function match(path) {
var match
if (path != null) {
// fast path non-ending match for / (any path matches)
if (this.regexp.fast_slash) {
this.params = {}
this.path = ''
return true
}
// fast path for * (everything matched in a param)
if (this.regexp.fast_star) {
this.params = {'0': decode_param(path)}
this.path = path
return true
}
// match the path
match = this.regexp.exec(path)
}
if (!match) {
this.params = undefined;
this.path = undefined;
return false;
}
// store values
this.params = {};
this.path = match[0]
var keys = this.keys;
var params = this.params;
for (var i = 1; i < match.length; i++) {
var key = keys[i - 1];
var prop = key.name;
var val = decode_param(match[i])
if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
params[prop] = val;
}
}
return true;
};
- path-to-regexp
pathToRegexp 함수
- 목적: 문자열 경로를 정규 표현식으로 변환. (즉, express에서 사용하는 pathsmㄴ pathToRegexp를 기반으로 만들면된다.)
- 인자:
- path: 문자열, 문자열 배열, 또는 정규 표현식.
- keys: 경로에서 추출된 키를 저장할 배열.
- options: 옵션 객체.
- 옵션:
- sensitive: 대소문자 구분 여부.
- strict: 선택적 종결 구분자 허용 여부.
- end: 문자열 끝까지 매칭 여부.
- start: 문자열 시작부터 매칭 여부.
- delimiter: 세그먼트의 기본 구분자.
- endsWith: 종료 문자 리스트.
- encode: 삽입 전 문자열을 인코딩하는 함수.
- prefixes: 파싱 시 자동으로 접두사로 고려할 문자 리스트.
- 주의 사항
- 생성된 정규 표현식은 경로, 호스트 이름 등 정렬된 데이터를 대상으로 하며, 쿼리 문자열, URL 조각, JSON 등 무작위 순서의 데이터는 처리하지 못함.
- 쿼리 문자열 포함 경로를 사용할 경우, 물음표(?)를 이스케이프 처리해야 파라미터가 선택 사항으로 간주되지 않음.
- 사용예시 :
const keys = []; const regexp = pathToRegexp("/foo/:bar", keys); // 결과 // regexp = /^\/foo(?:\/([^\/#\?]+?))[\/#\?]?$/i // keys = [{ name: 'bar', prefix: '/', suffix: '', pattern: '[^\\/#\\?]+?', modifier: '' }]
1.2.5 decodeparam
decodeparam 함수는 URI 인코딩된 파라미터 값을 디코딩한다. 이 함수는 Express.js의 Layer 모듈에서 사용되며, 주어진 문자열을 디코딩하여 파라미터로 반환.
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @private
*/
function decode_param(val) {
if (typeof val !== 'string' || val.length === 0) {
return val;
}
try {
return decodeURIComponent(val);
} catch (err) {
if (err instanceof URIError) {
err.message = 'Failed to decode param \'' + val + '\'';
err.status = err.statusCode = 400;
}
throw err;
}
}
1.3 router.js
1.3.1 Route
Route 객체는 path, stack, method를 가진다.
/**
* Initialize `Route` with the given `path`,
*
* @param {String} path
* @public
*/
function Route(path) {
this.path = path;
this.stack = [];
debug('new %o', path)
// route handlers for various http methods
this.methods = {};
}
1.3.2 _handlesmethod
이름처럼 METHOD를 핸들링하는 듯.
작성한 method를 모두 toLowerCase()로 일반화시킨다.
주요코드는 Boolean(this.methods[name]) 이다. 해당 method 를 핸들링할 수 있는지 알려준다.
/**
* Determine if the route handles a given method.
* @private
*/
Route.prototype._handles_method = function _handles_method(method) {
if (this.methods._all) {
return true;
}
// normalize name
var name = typeof method === 'string'
? method.toLowerCase()
: method
if (name === 'head' && !this.methods['head']) {
name = 'get';
}
return Boolean(this.methods[name]);
};
1.3.3 options
현재 지원하는 method를 array로 리턴하는 듯?
head 는 없으면 넣어준다.
/**
* @return {Array} supported HTTP methods
* @private
*/
Route.prototype._options = function _options() {
var methods = Object.keys(this.methods);
// append automatic head
if (this.methods.get && !this.methods.head) {
methods.push('head');
}
for (var i = 0; i < methods.length; i++) {
// make upper case
methods[i] = methods[i].toUpperCase();
}
return methods;
};
1.3.4 dispatch & next
req에 맞는 녀석을 dispatch 해본다.
Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack;
var sync = 0
if (stack.length === 0) {
return done();
}
var method = typeof req.method === 'string'
? req.method.toLowerCase()
: req.method
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = this;
next();
function next(err) {
// signal to exit route
if (err && err === 'route') {
return done();
}
// signal to exit router
if (err && err === 'router') {
return done(err)
}
// max sync stack
if (++sync > 100) {
return setImmediate(next, err)
}
var layer = stack[idx++]
// end of layers
if (!layer) {
return done(err)
}
if (layer.method && layer.method !== method) {
next(err)
} else if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
// Reset sync counter after each layer
sync = 0
}
};
코드에 대해 좀 더 자세히 뜯어봐야한다.
- 초기화 부분
var idx = 0 var stack = this.stack var sync = 0
- idx : tracking the current position in the stack.
- stack : References the stack of middleware and handlers associated with the route.
- sync : Tracks the number of synchronous calls to prevent stack overflows.
- Empty Stack Check
stack이 비어있으면
done()을 호출하여 요청처리를 종료한다. (이것이 성공인지는 아직 확인필요) - Normalize HTTP Method
method를 일괄 소문자로 변경.
head가 method면get으로 변경.var method = typeof req.method === 'string' ? req.method.toLowerCase() : req.method if (method === 'head' && !this.methods['head']) { method = 'get'; } - Set Route Reference
나중에 핸들러에서 참조할 수 있도록 현재 경로를
req.route에 할당한다.req.route = this;
1.3.5 next function
- Error Handling
err가 route 면 건너뛰라는 신호(현재 라우트를 종료하라)err가 router 면 전체 라우터를 종료하라는 신호
// signal to exit route if (err && err === 'route') { return done(); } // signal to exit router if (err && err === 'router') { return done(err) } - Synchronous call Limitation
call stack 초과를 방지하고자 limit을 설정하고 제한에 도달하면
setimmediate을 사용하여 방지한다.// max sync stack if (++sync > 100) { return setImmediate(next, err) } - Layer Execution
stack 에서 다음 layer를 가져온다. 더 이상 layer가 없으면 건너뛴다.
var layer = stack[idx++] // end of layers if (!layer) { return done(err) } - Matching
현재 메서드와 일치하지 않으면
next호출하여 현재 레이어를 건너뛴다.오류가 있으면,
layer.handler_error를 호출한다.오류가 없으면,
layer.handle_request를 호출한다.if (layer.method && layer.method !== method) { next(err) } else if (err) { layer.handle_error(err, req, res, next); } else { layer.handle_request(req, res, next); }
1.3.6 all function
주석의 설명처럼. .all 을 호출하면 모든 verbs 에서 핸들링하는 듯.
.all 함수의 인자가 함수가 아니면 예외를 던진다.
/**
* Add a handler for all HTTP verbs to this route.
*
* Behaves just like middleware and can respond or call `next`
* to continue processing.
*
* You can use multiple `.all` call to add multiple handlers.
*
* function check_something(req, res, next){
* next();
* };
*
* function validate_user(req, res, next){
* next();
* };
*
* route
* .all(validate_user)
* .all(check_something)
* .get(function(req, res, next){
* res.send('hello world');
* });
*
* @param {function} handler
* @return {Route} for chaining
* @api public
*/
Route.prototype.all = function all() {
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (typeof handle !== 'function') {
var type = toString.call(handle);
var msg = 'Route.all() requires a callback function but got a ' + type
throw new TypeError(msg);
}
var layer = Layer('/', {}, handle);
layer.method = undefined;
this.methods._all = true;
this.stack.push(layer);
}
return this;
};
1.3.7 methods.forEach
get, post, put, delete 등의 메서드를 Route 객체에 추가한다.
Route.prototype[method]를 보면 각 메서드이름을 프로퍼티로 함수를 주입니다.- 인자가 함수가 아니면 가차없이 Error를 던진다.
- 루프 Layer를 만들고, stack에 push한다.
- Layer 객체는 각 미들웨어 또는 핸들러를 캡슐화하여 특정 경로와 메서드에 연결한다. 요청이 들어올 때 해당 경로와 메서드에 맞는 핸들러를 실행합니다.
- stack 배열은 라우트에 연결된 모든 미들웨어와 핸들러를 순차적으로 저장한다. 요청이 처리될 때 이 배열을 순회하며 각 핸들러를 실행한다.
methods.forEach(function(method){
Route.prototype[method] = function(){
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (typeof handle !== 'function') {
var type = toString.call(handle);
var msg = 'Route.' + method + '() requires a callback function but got a ' + type
throw new Error(msg);
}
debug('%s %o', method, this.path)
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
- method library source code
methods 라이브러리는 단순한 한 페이지 소스코드를 갖고 있다.
최슨 Node.js 에서 지원하는 method들과 이전 버전을 위한 상수 배열을 갖고 있다.
var http = require('http') module.exports = getCurrentNodeMethods() || getBasicNodeMethods() /** * Get the current Node.js methods. * @private */ function getCurrentNodeMethods () { return http.METHODS && http.METHODS.map(function lowerCaseMethod (method) { return method.toLowerCase() }) } /** * Get the "basic" Node.js methods, a snapshot from Node.js 0.10. * @private */ function getBasicNodeMethods () { return [ 'get', 'post', 'put', 'head', 'delete', 'options', 'trace', 'copy', 'lock', 'mkcol', 'move', 'purge', 'propfind', 'proppatch', 'unlock', 'report', 'mkactivity', 'checkout', 'merge', 'm-search', 'notify', 'subscribe', 'unsubscribe', 'patch', 'search', 'connect' ] }