"프레임워크는 Node의 HTTP 계약을 없애지 않아. 라우팅, 본문 파싱, 오류 처리 같은 반복 작업을 그 위에 얹어 줄 뿐이야."
가장 작은 서버
import { createServer } from 'node:http';
const server = createServer((req, res) => {
// req는 읽기 스트림이고 res는 쓰기 스트림이야
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(`hi, you asked for ${req.url}\n`);
});
server.listen(3000, () => {
console.log('http://localhost:3000');
});
createServer에 넘긴 함수는 요청마다 실행돼. req는 http.IncomingMessage, res는 http.ServerResponse이고 둘 다 스트림 계약을 따라. 이 정도면 동작하는 서버지만, 운영 서버가 되려면 라우팅과 입력 제한, 시간 제한, 오류 처리, 종료 절차를 더 갖춰야 해.
요청에서 읽을 것
req.method에는 HTTP 메서드가, req.url에는 경로와 질의 문자열이 들어 있어. 헤더는 소문자 키를 쓰는 req.headers에서 읽어. 요청 본문은 한 번에 완성된 객체가 아니라 읽기 스트림으로 도착하므로 for await (const chunk of req)처럼 소비해야 해.
경로와 질의 문자열을 나눌 때는 문자열을 직접 자르기보다 new URL(req.url, base)를 사용해. 라우팅만 필요하다면 신뢰할 수 있는 고정 base로도 충분해. 외부에서 받은 Host 헤더를 절대 URL이나 보안 판단에 사용할 때는 먼저 검증해야 해.
응답은 헤더를 정한 뒤 끝낸다
res.statusCode, res.setHeader(), res.writeHead()로 상태와 헤더를 정하고, res.write()로 본문을 여러 번 보낼 수 있어. 마지막에는 반드시 res.end()를 호출해야 해. 헤더가 전송된 뒤에는 상태나 헤더를 바꿀 수 없고, 응답을 끝내지 않으면 클라이언트는 계속 기다려.
큰 응답을 쓸 때 res.write()가 false를 반환하면 내부 버퍼가 찼다는 뜻이야. 그때는 drain을 기다려야 해. 프레임워크가 편의를 제공하더라도 이 스트림 계약 자체는 그대로 남아 있어.
요청 본문과 입력 한도
node:stream/consumers의 text(), json(), buffer()는 작은 요청 본문을 간단히 읽게 해 줘.import { createServer } from 'node:http';
import { json } from 'node:stream/consumers';
createServer(async (req, res) => {
try {
const body = await json(req);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 42, ...body }));
} catch {
res.writeHead(400).end('bad request');
}
}).listen(3000);다만 이 도우미는 스트림 전체를 소비해. 신뢰할 수 없는 요청에는 바이트 수 한도를 먼저 적용해야 메모리 고갈을 막을 수 있어. 크기 제한이 기본인 프레임워크를 쓰거나, 청크를 읽는 동안 누적 크기를 검사해.오류, 시간 제한, 서버 수명주기
요청 처리 함수의 예외가 자동으로 안전한 500 응답이 되지는 않아. 비동기 처리에는 try/catch를 두고, 요청과 응답 스트림의 오류도 처리해야 해. 느린 클라이언트가 연결을 오래 붙잡지 못하도록 헤더와 요청 시간 제한을 정하고, 값은 서비스의 실제 트래픽 특성에 맞춰야 해.
종료할 때는 새 연결을 받는 일을 멈춘 뒤 진행 중인 요청이 끝날 시간을 줘. server.close()가 그 출발점이고, 종료 신호를 두 번 처리하지 않는 보호 장치와 최종 시간 제한도 필요해. 이 수명주기는 뒤의 graceful shutdown 레슨에서 더 자세히 다뤄.
클라이언트와 HTTPS
node:http에는 request와 get도 있지만, 일반적인 외부 HTTP 요청에는 내장 fetch가 더 간결해. 소켓, 에이전트, 저수준 스트림을 직접 제어해야 할 때는 node:http가 여전히 필요해. node:https는 같은 서버 모양에 TLS를 더하며, 운영에서는 역방향 프록시나 로드 밸런서가 TLS를 끝내는 구성도 흔해. 어느 층이 인증서와 암호화를 맡는지는 배포 구조에서 명시해야 해.