모든 Node 웹서버 앱은 웹서버 객체를 만들어야하고 이 때 createServer를 사용합니다.
코드를 보면 http를 require로 가져오고 const server 변수에다가 http모듈의 createServer함수를 담는 것으로 보입니다.
const http = require('http');
const server = http.createServer((request, response) => {
// 여기서 작업이 진행됩니다!
});
우선 다음의 순서대로 코드를 확인하는 것이 좋겠습니다.
/ Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayPrototypeSlice,
ArrayPrototypeSort,
ObjectDefineProperty,
} = primordials;
const httpAgent = require('_http_agent');
const { ClientRequest } = require('_http_client');
const { methods } = require('_http_common');
const { IncomingMessage } = require('_http_incoming');
const {
validateHeaderName,
validateHeaderValue,
OutgoingMessage
} = require('_http_outgoing');
const {
_connectionListener,
STATUS_CODES,
Server,
ServerResponse
} = require('_http_server');
let maxHeaderSize;
/**
* Returns a new instance of `http.Server`.
* @param {{
* IncomingMessage?: IncomingMessage;
* ServerResponse?: ServerResponse;
* insecureHTTPParser?: boolean;
* maxHeaderSize?: number;
* }} [opts]
* @param {Function} [requestListener]
* @returns {Server}
*/
function createServer(opts, requestListener) {
return new Server(opts, requestListener);
}
/**
* @typedef {object} HTTPRequestOptions
* @property {httpAgent.Agent | boolean} [agent]
* @property {string} [auth]
* @property {Function} [createConnection]
* @property {number} [defaultPort]
* @property {number} [family]
* @property {object} [headers]
* @property {number} [hints]
* @property {string} [host]
* @property {string} [hostname]
* @property {boolean} [insecureHTTPParser]
* @property {string} [localAddress]
* @property {number} [localPort]
* @property {Function} [lookup]
* @property {number} [maxHeaderSize]
* @property {string} [method]
* @property {string} [path]
* @property {number} [port]
* @property {string} [protocol]
* @property {boolean} [setHost]
* @property {string} [socketPath]
* @property {number} [timeout]
* @property {AbortSignal} [signal]
*/
/**
* Makes an HTTP request.
* @param {string | URL} url
* @param {HTTPRequestOptions} [options]
* @param {Function} [cb]
* @returns {ClientRequest}
*/
function request(url, options, cb) {
return new ClientRequest(url, options, cb);
}
/**
* Makes a `GET` HTTP request.
* @param {string | URL} url
* @param {HTTPRequestOptions} [options]
* @param {Function} [cb]
* @returns {ClientRequest}
*/
function get(url, options, cb) {
const req = request(url, options, cb);
req.end();
return req;
}
module.exports = {
_connectionListener,
METHODS: ArrayPrototypeSort(ArrayPrototypeSlice(methods)),
STATUS_CODES,
Agent: httpAgent.Agent,
ClientRequest,
IncomingMessage,
OutgoingMessage,
Server,
ServerResponse,
createServer,
validateHeaderName,
validateHeaderValue,
get,
request
};
ObjectDefineProperty(module.exports, 'maxHeaderSize', {
configurable: true,
enumerable: true,
get() {
if (maxHeaderSize === undefined) {
const { getOptionValue } = require('internal/options');
maxHeaderSize = getOptionValue('--max-http-header-size');
}
return maxHeaderSize;
}
});
ObjectDefineProperty(module.exports, 'globalAgent', {
configurable: true,
enumerable: true,
get() {
return httpAgent.globalAgent;
},
set(value) {
httpAgent.globalAgent = value;
}
});
그 중 위 코드에 있는 Function createServer가 보이시죠 ? 해당 코드의 내용입니다.
opts, requestListener를 인자로 받고 받은 인자와 함께 Server 객체를 return(반환)해주고 있습니다.
function createServer(opts, requestListener) {
return new Server(opts, requestListener);
}
여기서는 function createServer의 라이브러리 내부 내용을 보면 다음과 같습니다.
살펴보면 Request와 Response를 인자로 받고 서버를 생성해서 리턴해주는 것으로 보입니다.
* @since v0.1.13
function createServer<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
>(requestListener?: RequestListener<Request, Response>): Server<Request, Response>;
function createServer<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
>(
options: ServerOptions<Request, Response>,
requestListener?: RequestListener<Request, Response>,
): Server<Request, Response>;
그렇다면 여기서 반환해주는 Server객체는 무엇일까요?
createServer가 반환한 Server 객체는 EventEmitter이고 EventEmitter란 개발자가 실제로 이벤트를 만들고 이벤트를 발생시킬 수 있는 기술로 알고계시면 됩니다.
EventEmitter 객체는 다음과 같은 메소드를 가지고 있습니다.
emitter.addListener(event, listener): 이벤트를 생성하는 메소드입니다. on() 메소드와 같습니다.
emitter.on(event, listener): 이벤트를 생성하는 메소드입니다. addListener()과 동일합니다.
emitter.once(event, listener): 이벤트를 한 번만 연결한 후 제거합니다.
emitter.removelistener(event, listener):
특정 이벤트의 핸들러를 제거합니다.
이 메소드를 이용해 리스너를 삭제하면 리스너 배열의 인덱스가 갱신되니 주의해야 합니다.
emitter.removeAllListeners([event]): 모든 이벤트 핸들러를 제거합니다.
emitter.setMaxListeners(n):
n으로 한 이벤트에 최대허용 개수를 지정합니다.
node.js는 기본값으로 한 이벤트에 10개의 이벤트 핸들러를 작성할 수 있는데,
11개 이상을 사용하고 싶다면 n값을 넘겨주면 됩니다.
n값으로 0을 넘겨주면 연결 개수 제한이 사라집니다.
emitter.emit(eventName[, ...args]): 이벤트를 발생시킵니다.
아래 코드를 통해 Server 객체가 EventEmitter가 맞는지 확인할 수 있다.
class Server<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse
> extends NetServer {
constructor(requestListener?: RequestListener<Request, Response>);
constructor(
options: ServerOptions<Request, Response>,
requestListener?: RequestListener<Request, Response>
);
setTimeout(msecs?: number, callback?: () => void): this;
setTimeout(callback: () => void): this;
maxHeadersCount: number | null;
maxRequestsPerSocket: number | null;
timeout: number;
headersTimeout: number;
keepAliveTimeout: number;
requestTimeout: number;
closeAllConnections(): void;
closeIdleConnections(): void;
addListener(event: string, listener: (...args: any[]) => void): this;
addListener(event: "close", listener: () => void): this;
addListener(event: "connection", listener: (socket: Socket) => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
addListener(event: "listening", listener: () => void): this;
addListener(
event: "checkContinue",
listener: RequestListener<Request, Response>
): this;
addListener(
event: "checkExpectation",
listener: RequestListener<Request, Response>
): this;
addListener(
event: "clientError",
listener: (err: Error, socket: stream.Duplex) => void
): this;
addListener(
event: "connect",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
addListener(
event: "request",
listener: RequestListener<Request, Response>
): this;
addListener(
event: "upgrade",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
emit(event: string, ...args: any[]): boolean;
emit(event: "close"): boolean;
emit(event: "connection", socket: Socket): boolean;
emit(event: "error", err: Error): boolean;
emit(event: "listening"): boolean;
emit(
event: "checkContinue",
req: InstanceType<Request>,
res: InstanceType<Response> & { req: InstanceType<Request> }
): boolean;
emit(
event: "checkExpectation",
req: InstanceType<Request>,
res: InstanceType<Response> & { req: InstanceType<Request> }
): boolean;
emit(event: "clientError", err: Error, socket: stream.Duplex): boolean;
emit(
event: "connect",
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
): boolean;
emit(
event: "request",
req: InstanceType<Request>,
res: InstanceType<Response> & { req: InstanceType<Request> }
): boolean;
emit(
event: "upgrade",
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
): boolean;
on(event: string, listener: (...args: any[]) => void): this;
on(event: "close", listener: () => void): this;
on(event: "connection", listener: (socket: Socket) => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "listening", listener: () => void): this;
on(
event: "checkContinue",
listener: RequestListener<Request, Response>
): this;
on(
event: "checkExpectation",
listener: RequestListener<Request, Response>
): this;
on(
event: "clientError",
listener: (err: Error, socket: stream.Duplex) => void
): this;
on(
event: "connect",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
on(event: "request", listener: RequestListener<Request, Response>): this;
on(
event: "upgrade",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
once(event: string, listener: (...args: any[]) => void): this;
once(event: "close", listener: () => void): this;
once(event: "connection", listener: (socket: Socket) => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: "listening", listener: () => void): this;
once(
event: "checkContinue",
listener: RequestListener<Request, Response>
): this;
once(
event: "checkExpectation",
listener: RequestListener<Request, Response>
): this;
once(
event: "clientError",
listener: (err: Error, socket: stream.Duplex) => void
): this;
once(
event: "connect",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
once(event: "request", listener: RequestListener<Request, Response>): this;
once(
event: "upgrade",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
prependListener(event: string, listener: (...args: any[]) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(
event: "connection",
listener: (socket: Socket) => void
): this;
prependListener(event: "error", listener: (err: Error) => void): this;
prependListener(event: "listening", listener: () => void): this;
prependListener(
event: "checkContinue",
listener: RequestListener<Request, Response>
): this;
prependListener(
event: "checkExpectation",
listener: RequestListener<Request, Response>
): this;
prependListener(
event: "clientError",
listener: (err: Error, socket: stream.Duplex) => void
): this;
prependListener(
event: "connect",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
prependListener(
event: "request",
listener: RequestListener<Request, Response>
): this;
prependListener(
event: "upgrade",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(
event: "connection",
listener: (socket: Socket) => void
): this;
prependOnceListener(event: "error", listener: (err: Error) => void): this;
prependOnceListener(event: "listening", listener: () => void): this;
prependOnceListener(
event: "checkContinue",
listener: RequestListener<Request, Response>
): this;
prependOnceListener(
event: "checkExpectation",
listener: RequestListener<Request, Response>
): this;
prependOnceListener(
event: "clientError",
listener: (err: Error, socket: stream.Duplex) => void
): this;
prependOnceListener(
event: "connect",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
prependOnceListener(
event: "request",
listener: RequestListener<Request, Response>
): this;
prependOnceListener(
event: "upgrade",
listener: (
req: InstanceType<Request>,
socket: stream.Duplex,
head: Buffer
) => void
): this;
}
코드를 전체적으로 살펴보니 addListener와 emit, on, once등이 있습니다. 이것을 보니 위의 createServer로 만든 server객체는 EventEmitter 객체가 맞는 것 같습니다.
그럼 위의 전체적인 큰 그림을 살펴봤구요
method와 url, headers, requerst, response같은 전문 용어를 알아보겠습니다.
용어에 대한 설명은 다음과 같습니다.
request객체는 ReadableStream , EventEmitter이다 (request객체는 일반적으로 우리가 네이버로그인 요청을 할 때 버튼클릭을 하거나 어떤 요청을 할 때 그 요청 정보를 담고 있는 정보라고 생각하시면 됩니다.)
response객체는 WritableStream , EventEmitter이다 (서버란 즉 서빙, 서비스를 하는 대상입니다. 위에 있는 request로 요청을 받은 정보에 대해 그것을 읽은 뒤 다시 응답해주는 것을 말합니다.)
method : HTTP 메소드 / 동사 (GET, POST ,PUT)
(직관적으로 GET은 가져와서 조회하는 것이고 POST는 무언가를 만드는 것입니다. 지금 블로그에 글을 쓰는 것을 포스팅이라고 하듯이요, PUT도 글자 그대로 넣는 것입니다. )
url : 전체 URL에서 서버, 프로토콜, 포트를 제외한 것, 세번째 슬래시 이후의 나머지 전부
headers : request에 있는 headers 전용객체에는 캐시설정, 데이터 타입설정 등
그리고 위에서 알게된 내용으로 사용자가 서버에 보낸 데이터를 다시 보내는 서버를 만들어보았습니다.
코드는 다음과 같습니다.
const http = require('http');
http.createServer((request, response) => {
const { headers, method, url } = request;
let body = [];
request.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
// 여기서부터 새로운 부분입니다.
response.on('error', (err) => {
console.error(err);
});
response.statusCode = 200;
response.setHeader('Content-Type', 'application/json');
// 주의: 위 두 줄은 다음 한 줄로 대체할 수도 있습니다.
// response.writeHead(200, {'Content-Type': 'application/json'})
const responseBody = { headers, method, url, body };
response.write(JSON.stringify(responseBody));
response.end();
// 주의: 위 두 줄은 다음 한 줄로 대체할 수도 있습니다.
// response.end(JSON.stringify(responseBody))
// 새로운 부분이 끝났습니다.
});
}).listen(8080);
그렇다면 위에서 배운 HTTP 트랜잭션 내용을 통해 Mini Node Server를 구현한다면 어떻게 나타낼 수 있을까요??
소문자로 요청이 오면 소문자로 응답을 하고 대문자로 요청이 오면 대문자로 응답을 하는 서버 소스입니다.
코드는 다음과 같습니다.
const http = require("http");
const PORT = 4999;
const ip = "localhost";
const server = http.createServer((request, response) => {
// console.log(
// `http request method is ${request.method}, url is ${request.url}`
// );
console.log(request);
const { method, url, headers } = request;
//request 객체는 IncomingMessage의 인스턴스입니다.
// >> request(요청)에서 메서드와 url, 헤더스를 가져온것 같다.
let body = [];
request
.on("data", (chunk) => {
//리퀘스트에 요청이 왔을 때 바디에 push를 이용해서 chunk를 삽입한다.
body.push(chunk);
})
.on("end", () => {
//end 요청이 끝날 때 버퍼를 바디에 합쳐서 문자열로 바꿔준다.
body = Buffer.concat(body).toString();
console.log("body", body);
if (method === "POST" && url === "/upper") {
response.writeHead(200, defaultCorsHeader);
response.end(body.toUpperCase());
// response.statusCode = 200;
// response.setHeader("Content-Type", "application/json");
// response.setHeader("X-Powered-By", "bacon");
// response.userAgent;
} else if (request.method === "POST" && request.url === "/lower") {
response.writeHead(200, defaultCorsHeader);
response.end(body.toLowerCase());
response.statusCode = 201;
} else if (request.method === "OPTIONS") {
response.writeHead(200, defaultCorsHeader);
response.end();
} else {
response.writeHead(404, defaultCorsHeader);
response.end("잘못된 요청");
}
});
});
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
const defaultCorsHeader = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Accept",
"Access-Control-Max-Age": 10,
};
위의 미니노드 서버 구조는 다음과 같습니다.
그리고 코드 흐름과 설명은 다음 내용을 보시면 알 수 있습니다.
http.js
https://github.com/nodejs/node/blob/v18.0.0/lib/http.js
HTTP 트랜잭션 해부
참조 : 링크
Node JS - EventEmitters (0) | 2023.01.04 |
---|---|
nvm & node.js (0) | 2023.01.01 |
Javascript 런타임에 대하여 (0) | 2022.12.23 |