Express에서는 [요청에 관한 처리를 하는 함수]를 미들웨어라고 합니다. 미들웨어라고 할 때 상황에 따라 라우트 핸들러도 포함해서 미들웨어라고 하기도 하고 제외 하기도 합니다. 각각의 상황에 맞게 이해하면 됩니다.
미들웨어의 정확한 정의는 OS[운영체제]와 응용 프로그램 사이에서 동작하는 프로그램을 의미합니다.
함수 또한 작은 프로그램이라고 볼 수 있으니 Express에서 말하는 미들웨어 또한 미들웨어의 정의에 부합합니다.
우리는 Express에서 app.use(express.json()); 라는 코드를 추가해,
express의 json이라는 메소드는 어떤 미들웨어(함수)를 리턴하는 걸까요? 한번 코드를 파헤쳐봅시다.
그럼 한번 express의 맨처음 코드인 아래코드가 실행될때 참조되는 파일인 index.js 파일을 찾아가봅시다.
const express = require(‘express’);
index.js 파일을 로컬 스토리지의 express 디렉토리[폴더]에서도 찾을 수 있지만,
Express 는 오픈소스 프로젝트이기에 github에서 직접 들어가서 확인할 수 있습니다.
https://github.com/expressjs/express
위 사진에서 볼 수 있듯이.
index.js 파일은 lib디렉토리 안에 있는 express라는 파일에서 모듈을 가져오는군요.
그럼 해당 파일로 올라가봅시다. lib/express.js 파일을 열어서 그 상단 부분을 보면
express.js 파일에서는
createApplication라는 함수가 선언되어있고
createApplication 함수는 실행될 때 => app이라는 함수를 리턴한다는 것을 알 수 있습니다.
그러니까 결국 아래 코드에서 저희가 보았던 app 객체는 함수였던 것이죠.
const express = require('express');
const app = express();세요
그리고 express.js 파일에서 조금만 더 아래로 내려가보면 [70~80번째줄]
(바로 이전에 확인한 것처럼 exports는 createApplication의 함수를 가리킵니다)
이런 식으로 createApplication 함수에 json이라는 프로퍼티를 생성하고(만들고) 있습니다.
그렇기때문에 저희가 아래 코드를 처음에 입력한 순간부터 바로 json()을 쓸 수 있엇던 것 입니다.
const express = require(‘express’);
app.use(express.json());
그렇다면 지금 json 프로퍼티에 설정된 bodyParser.json이라는 건 무엇이고 어디에서 온 것 일까요?
express 패키지가 의존하고 있는 => 다른 패키지에 있는 객체인데요. 약간 어지럽죠..?
이따가 다시 한번 정리하겠습니다.
잠깐 express 패키지의 package.json 파일을 살펴보면,
express의 package.json파일을 보면 의존성관리 항목에 body-parser가 있다.
Express의 packge.json에 있는 의존성[dependencies] 패키지들 중에 body-parser라는 패키지가 있다는 걸 알 수 있습니다.
이 패키지를 봐야 json 함수의 정체를 온전히 파악할 수 있습니다. 타고들어가고 또 타고들어가서 또 타고들어가는...
그러고나서 npm 사이트에 들어가서 body-parser를 찾아보면 아래와 같은 화면이 나오는데.
위 내용을 정리하자면.
서버로 들어오는 요청을 라우트 핸들러에 도달하기 전에
리퀘스트의 바디를 파싱(parsing)해주는 기능을 가진 패키지라는 설명입니다.
라우트 핸들러란?
라우트 핸들러[Route Handler]란 특정 path에 지정된 메소드를 리퀘스트 처리해주는 함수를 의미합니다. 어떤 기능을 하냐면 => 지정된 경로에 맞춰서 리퀘스트를 처리해주거나 보통은 컨트롤러로 옮겨주는 역할을 수행하죠.
app.get('/api/members', async (req, res) => {
const { team } = req.query;
if (team) {
const teamMembers = await Member.findAll({ where: { team } });
res.send(teamMembers);
} else {
const members = await Member.findAll();
res.send(members);
}
});
"여기서는 async로 시작되는 함수가 라우트 핸들러인 거죠."
그러니까 body-parser 패키지는 요청이 라우트 핸들러에 의해 처리되기 전에
먼저=> HTTP 응답의 바디에 관한 처리하는 기능을 갖고 있는 패키지입니다.
또한 body-parser에는 JSON 형식의 데이터를 파싱하는 기능도 있음을 알 수 있습니다.
자그럼 GitHub 링크를 클릭해서 body-parser 패키지에서 필요한걸 찾아옵시다.
마찬가지로 body-parser 패키지도 오픈소스입니다
이번에도 index.js 파일을 열고. 코드 중간을 보면 아래와 같은
일단 exports가 가리키는 객체가 설정되어있고
그 아래에 Object.defineProperty로 시작되는 코드는 exports 객체에 json이라는 프로퍼티를 설정하는 코드입니다.
지금 defineProperty 메소드의 세 번째 매개변수로 들어온 객체는 json이라는 프로퍼티 자체가 가질 속성들인데요.
여기서 get이라는 부분은 나중에 이 프로퍼티에 접근할 때 실행될 getter 함수를 의미합니다.
Recap!
express.json은
bodyParser.json을 의미하고
createParserGetter('json')의 리턴값을 가리킵니다!
자 근데 createParserGetter의 리턴값은 무엇을 가르킬까요? ㅎㅎ...
createParserGetter 함수도 같은 파일 안에 들어있는데요.
createParserGetter 함수는 => get 함수를 리턴하네요.
이 get이라는 함수는 다시 loadParser 함수의 리턴 결과를 리턴합니다.
아래 코드를 보면서 loadParser함수도 한번 확인해봅시다.
Swith case 조건문에서는 문자열 'json'을 발견하면
=> 또다른 파일에서 가져온 parser를 parsers 객체에 설정! 그후
=> parser 배열을 리턴합니다.
그러므로 parser 에는
=>lib/types/json.js 파일에서 가져온
=>parser들이 들어간 배열이 됩니다.
슬슬한계가 오고 있겠지만 정말 곧 끝입니다!
거의 다 왔어요!
아래 코드에 보이는 ./lib/types/json 파일로 가보겠습니다.
자, 이 파일은 json이라는 함수를 리턴하고 있습니다. 그리고 json 함수는 결국 jsonParser라는 함수를 리턴합니다.
Velog에서는 접은글 기능을 사용할 수 없네요..
코드가 너무 길기 때문에 건너뛰시려면 스크롤을 많이 내리셔야합니다.
function json (options) {
var opts = options || {}
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var inflate = opts.inflate !== false
var reviver = opts.reviver
var strict = opts.strict !== false
var type = opts.type || 'application/json'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
if (body.length === 0) {
// special-case empty json body, as it's a common client-side mistake
// TODO: maybe make this configurable or part of "strict" option
return {}
}
if (strict) {
var first = firstchar(body)
if (first !== '{' && first !== '[') {
debug('strict violation')
throw createStrictSyntaxError(body, first)
}
}
try {
debug('parse json')
return JSON.parse(body, reviver)
} catch (e) {
throw normalizeJsonSyntaxError(e, {
message: e.message,
stack: e.stack
})
}
}
return function jsonParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// assert charset per RFC 7159 sec 8.1
var charset = getCharset(req) || 'utf-8'
if (charset.slice(0, 4) !== 'utf-') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
Express 프레임워크에서 우리가 쓰는 express.json()
이것은 지금 보이는 jsonParser라고 하는 함수를 리턴하는 부분이었던 겁니다.
app.use(express.json());
좀 더 구체적으로 설명하자면.
맨 처음 우리가 추상적으로 썻던 위의 코드는
app.use(jsonParser);
사실 jsonParser를 json()으로 표현한 것이고.
jsonParser 함수의 파라미터는 req, res, next 라는 3개의 파라미터가 있고.
마지막 next 파라미터로 넘어오는 함수를 마지막에 호출함으로서.function jsonParser (req, res, next) { ... ... next(); }
HTTP 요청 / 응답 을 다음 미들웨어나 라우트 핸들러로 넘기는 것. 이라고 이해하면 되겠습니다.
jsonParse에 대한 더 구체적인 흐름은 위에 첨부한 json function 코드를 참고하시고.
parse와 통신 과정에서의 정확한 원리는 IETF 국제 인터넷 통신 표준 협회의 HTTP 통신에 관한 문서에서 parse와 패킷에 대한 부분을 확인해주세요.
[저 협회에서 HTTP, HTTPS, HTTP/3 , WEB3.0 등의 iso 프로토콜 표준을 만든답니다.]
https://www.rfc-editor.org/rfc/rfc9110.html
긴글 읽으시느라 정말... 수고하셨습니다!