- [node] http 모듈 커스텀하기 2편2023년 12월 24일 01시 47분 22초에 업로드 된 글입니다.작성자: @kimyu0218
아직 부족한 게 많은 패키지에요. 핵심만 구현했으니 귀엽게(?) 봐주세요.
http 모듈에는 어떤 게 들어있을까?
http 모듈을 위한 코드를 작성하기에 앞서 http 모듈에 들어있는 객체들을 살펴보자.
http 모듈은 HTTP 프로토콜을 다루기 위한 핵심 모듈이다.
nodejs - http documentation(사실 캡처해서 가져오려고 했으나 너무 많아서 포기했다. 위 사이트 들어가서 확인해주세요)
내가 구현할 객체는 일부 상수와 HTTP 메시지 부분이다. 메시지 객체를 구현하기 위해서는 메서드와 상태코드 정보를 넘겨줘야 하기 때문에 `METHODS`, `STATUS_CODES`를 구현하기로 결정했다.- http.METHODS
- http.STATUS_CODES
- http.IncomingMessage
- http.OutgoingMessage
http를 위한 상수 정의하기
메시지를 구현하기 전에 메시지에 필요한 상수부터 정의해보도록 하자.
http.METHODS
`http.METHODS`는 HTTP 요청 메서드에 대한 상수를 담고 있는 배열이다. http 모듈에서는 `string[]` 자료형을 이용하고 있으나 좀 더 상수처럼 관리하기 위해 다음과 같이 작성해줬다.
export const METHODS: Record<HttpMethods, HttpMethods> = { GET: 'GET', HEAD: 'HEAD', PUT: 'PUT', POST: 'POST', PATCH: 'PATCH', DELETE: 'DELETE', TRACE: 'TRACE', OPTIONS: 'OPTIONS', };
http.STATUS_CODES
`http.STATUS_CODES`는 HTTP 응답에 사용되는 상태코드의 집합이다. 상태코드가 너무 많은 관계로 자주 사용되는 상태코드에 대해서만 선언하기로 결정했다.
export const STATUS_CODES: Record<number, string> = { 100: 'Continue', 101: 'Switching Protocols', 200: 'OK', 201: 'Created', 204: 'No Content', 301: 'Moved Permanently', 302: 'Found', 304: 'Not Modified', 400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 409: 'Conflict', 429: 'Too Many Requests', 500: 'Internal Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', };
http 메시지 정의하기
1편에서 살펴봤듯이 http request와 response는 start-line에 들어있는 내용만 다를 뿐 뼈대는 동일하다. 즉, start-line, header, message-body로 구성되어 있다.
HttpMessage 클래스
request와 response를 작성하기 전에 뼈대를 작성해보자. 뼈대 클래스를 상속하여 request, response를 구현할 것이다.
export default class HttpMessage { private startLine?: string; private version?: string; private header: Map<string, string> = new Map(); private messageBody: any; getStartLine(): string { return this.startLine ?? null; } getVersion(): string { return this.version ?? null; } getHeader(key: string): string { return this.header.get(key) ?? null; } getHeaders(): Map<string, string> { return this.header; } getMessageBody(): any { return this.messageBody; } setStartLine(startLine: string): this { this.startLine = startLine; return this; } setVersion(version: string): this { this.version = version; return this; } setHeader(key: string, value: string): this { this.header.set(key, value); return this; } setMessageBody(body: any): this { this.messageBody = body; return this; } }
공통 부분인 start-line, header, message-body를 프로퍼티로 선언하고 getter와 setter를 작성해줬다. (`return this`로 메서드 체이닝을 구현하기 위해 `get`, `set` 키워드를 사용하지 않았다.)
HttpResponse 클래스
이제 앞에서 작성한 `HttpMessage`를 상속하여 `HttpRequest`를 정의할 것이다. `HttpResponse`는 서버가 클라이언트에게 전달하는 메시지이므로 상태코드를 수정하고 메시지를 전달할 수 있어야 한다.
export class HttpResponse extends HttpMessage { private readonly socket: Socket; private statusCode: number = 200; private statusMessage: string = STATUS_CODES[200]; constructor(socket: Socket, version: string) { super(); this.setVersion(version); this.socket = socket; } send(): void { this.socket.write(this.getMessage()); this.socket.end(); } throwError(statusCode: number, message?: string): HttpResponse { this.statusCode = statusCode; this.statusMessage = STATUS_CODES[statusCode]; this.setMessageBody(message ? message : this.statusMessage); this.setHeader('Content-Type', `${CONTENT_TYPE.HTML}; charset=utf-8`); return this; } getMessage(): string { this.setStartLine(this.makeStatusLine()); const contentType: string = this.getHeader('Content-Type'); const messageBody: any = contentType === CONTENT_TYPE.JSON ? JSON.stringify(this.getMessageBody()) : this.getMessageBody(); return new StringBuilder(this.getStartLine()) // status-line .add(CRLF) .add(this.makeHeader()) // header .add(CRLF) .add(messageBody) // message-body .build(); } setStatusCode(statusCode: number): HttpResponse { this.statusCode = statusCode; this.statusMessage = STATUS_CODES[statusCode]; return this; } setStatusMessage(message: string): HttpMessage { this.statusMessage = message; return this; } private makeStatusLine(): string { return new StringBuilder(this.getVersion()) // HTTP-Version .add(SP) .add(this.statusCode.toString()) // Status-Code .add(SP) .add(this.statusMessage) // Reason-Phrase .build(); } private makeHeader(): string { const header: Map<string, string> = this.getHeaders(); return Object.entries(header).reduce( (acc: string, [key, value]: string[]) => acc + `${key}:${value}${CRLF}`, '', ); } }
`HttpResponse` 객체를 생성할 때 생성자의 파라미터로 소켓을 넘겨준다. 이를 통해 send 메서드 호출 시 클라이언트에게 응답을 전달할 수 있다.
(socket.write(), socket.end()에 관한 내용은 net 모듈을 살펴보시는 걸 추천합니다.)
메시지를 전달할 때, HTTP 프로토콜의 양식을 지켜야하므로 1편을 참고하여 `getMessage()`, `makeStatusLine()`을 작성한다.
아직 `HttpRequest`가 나오지 않았지만 이번 포스트는 여기서 마무리하겠다. 요청은 클라이언트가 보내는 메시지이기 때문에 파싱이 필요한데, 파서 코드가 꽤 길어 별도의 글로 작성하는 게 좋다고 판단했다.
모든 코드를 포스트에 담을 수 없다는 점 양해부탁드립니다. 제가 작성한 코드는 깃허브에서 확인하실 수 있습니다.
참고자료'backend > 내가 만든 패키지' 카테고리의 다른 글
[node] http 모듈 커스텀하기 3편 (1) 2023.12.28 [node] http 모듈 커스텀하기 1편 (1) 2023.12.23 [node] Swagger 데코레이터 어디까지 커스텀 해봤니? (0) 2023.12.23 다음글이 없습니다.이전글이 없습니다.댓글