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' ] }