Node.js의
net모듈(TCP 레벨)만 사용하여 Express와 유사한 API를 제공하는 웹 애플리케이션 서버(WAS)를 직접 구현한 프로젝트입니다. TCP 소켓 수신부터 HTTP 메시지 파싱, 라우팅, 응답 작성까지 전 과정을 라이브러리 없이 구현했습니다.
다음과 같이 Node.js 의 net 모듈만으로 Express 와 같은 인터페이스를 제공하는 WAS 를 구현하는 것을 목표로 했습니다.
const net = require('net');
const server = net.createServer(socket => {
socket.on('data', data => {
const request = data.toString();
if (request.includes('GET')) {
socket.write('HTTP/1.1 200 OK\r\n');
socket.write('Content-Type: text/plain\r\n');
socket.write('\r\n');
socket.write('Hello, World!');
socket.end();
}
});
});
server.listen(3000, () => {
console.log('HTTP server running on port 3000');
});
// app.js
const app = createApp();
app.get('/hello/:name', (req, res) => {
res.json({ msg: `Hi, ${req.params.name}!` });
});
app.listen(3000, '127.0.0.1', () => {
console.log('listening on <http://127.0.0.1:3000>');
});
프레임워크 사용자가 흔히 접하는 app.get(), app.post(), res.json()과 같은 추상화를 가장 밑바닥부터 직접 쌓아 올리는 것이 관건이었습니다.
가장 먼저 전체 구조를 Architecture.md에 설계했습니다.
하나의 요청이 처리되는 흐름을 "TCP 수신 → HTTP 파싱 → 라우팅 → 응답 작성" 으로 나누고, 각 단계를 독립된 모듈로 분리하여 책임과 역할을 명확히 구분했습니다.
BE/
├─ core/
│ ├─ HttpServer.js // net.Server 생성, 연결마다 HttpConnection 인스턴스 생성
│ ├─ HttpConnection.js // 소켓 1개의 수명·상태 관리, data 이벤트 수신
│ ├─ HttpParser.js // 상태 머신으로 바이트 스트림 → HTTP 메시지 파싱
│ └─ app.js // 라우트 테이블 등록, 핸들러 디스패치, app 파사드 반환
├─ http/
│ ├─ Request.js // 요청 객체(method, path, query, headers, body)
│ └─ Response.js // 응답 객체(status, setHeader, send, json, end)
├─ routing/
│ ├─ pattern.js // 경로 패턴을 정규식으로 변환, :param 키 추출
│ └─ match.js // 라우팅 테이블 순회, 매칭 및 params 바인딩
└─ server.js // 테스트용 라우트 모음
이때 요청 처리의 데이터 흐름은 다음과 같습니다.
클라이언트 (TCP)
│
▼
net.Server ── 연결 수립 ──▶ HttpConnection (소켓 1:1)
│ socket.on('data')
▼
HttpParser.parseChunk() ← 바이트 스트림을 완성된 메시지로
│
▼
Request / Response 생성
│
▼
app.handle() ─▶ match() ─▶ 라우트 핸들러 실행
HttpServer예제코드의 net.createServer()를 감싸, 클라이언트 연결이 수립될 때마다 전달받는 net.Socket 인스턴스를 HttpConnection으로 넘겨 각 연결을 관리하도록 했습니다.