Node.js 인터뷰 질문 21
질문: Node.js의 HTTP 모듈에 대해 설명하고 주요 기능을 알려주세요.
답변:
Node.js의 HTTP 모듈은 HTTP 서버와 클라이언트를 구현하기 위한 코어 모듈입니다. 이 모듈은 웹 서버를 생성하거나 HTTP 요청을 보내는 데 사용되며, Node.js의 비동기 이벤트 기반 아키텍처를 활용하여 효율적인 네트워크 통신을 가능하게 합니다.
모듈 로드하기
const http = require("http");
1. HTTP 서버 생성하기
http.createServer()
메서드를 사용하여 간단한 HTTP 서버를 만들 수 있습니다.
const http = require("http");
// HTTP 서버 생성
const server = http.createServer((req, res) => {
// 요청 처리
console.log(`요청 URL: ${req.url}`);
console.log(`요청 메서드: ${req.method}`);
// 응답 헤더 설정
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
// 응답 데이터 전송 및 종료
res.end("안녕하세요, Node.js HTTP 서버입니다!\n");
});
// 서버 리스닝 시작
server.listen(3000, "127.0.0.1", () => {
console.log("서버가 http://127.0.0.1:3000 에서 실행 중입니다.");
});
2. 요청 객체 (IncomingMessage)
http.IncomingMessage
객체는 서버가 수신한 HTTP 요청이나 클라이언트가 수신한.HTTP 응답에 대한 정보를 담고 있습니다.
주요 속성과 메서드:
req.url
: 요청 URL을 포함하는 문자열req.method
: HTTP 메서드(GET, POST 등)req.headers
: 요청 헤더를 포함하는 객체req.httpVersion
: HTTP 버전req.on('data', callback)
: 요청 본문 데이터 수신 이벤트req.on('end', callback)
: 요청 본문 수신 완료 이벤트
server.on("request", (req, res) => {
// URL 파싱 (쿼리 문자열 처리)
const url = new URL(req.url, `http://${req.headers.host}`);
console.log("경로:", url.pathname);
console.log("쿼리 파라미터:", url.searchParams);
// 요청 본문 읽기 (POST 요청 등)
let body = [];
req
.on("data", (chunk) => {
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString();
console.log("요청 본문:", body);
// 요청 처리 로직
});
});
3. 응답 객체 (ServerResponse)
http.ServerResponse
객체는 HTTP 서버 요청에 대한 응답을 생성하는 데 사용됩니다.
주요 메서드:
res.statusCode
: HTTP 상태 코드 설정 (기본값: 200)res.setHeader(name, value)
: 응답 헤더 설정res.writeHead(statusCode, headers)
: 상태 코드와 다수의 헤더를 한 번에 설정res.write(data)
: 응답 본문 데이터 작성res.end([data])
: 응답 종료 (선택적으로 마지막 데이터 전송)
const server = http.createServer((req, res) => {
// JSON 응답 전송
const data = {
message: "성공",
items: [1, 2, 3],
};
res.writeHead(200, {
"Content-Type": "application/json",
"X-Custom-Header": "Custom Value",
});
res.end(JSON.stringify(data));
});
4. HTTP 클라이언트 요청 보내기
http.request()
또는 http.get()
메서드를 사용하여 HTTP 클라이언트 요청을 보낼 수 있습니다.
const http = require("http");
// GET 요청 보내기
http
.get("http://example.com", (res) => {
const { statusCode } = res;
const contentType = res.headers["content-type"];
let error;
if (statusCode !== 200) {
error = new Error(`요청 실패. 상태 코드: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error(
`잘못된 content-type. application/json이 필요합니다. 받은 타입: ${contentType}`
);
}
if (error) {
console.error(error.message);
res.resume(); // 응답 데이터 소비 (메모리 누수 방지)
return;
}
res.setEncoding("utf8");
let rawData = "";
res.on("data", (chunk) => {
rawData += chunk;
});
res.on("end", () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
} catch (e) {
console.error(`데이터 파싱 오류: ${e.message}`);
}
});
})
.on("error", (e) => {
console.error(`요청 오류: ${e.message}`);
});
복잡한 요청(POST, 헤더 설정 등):
const http = require("http");
const options = {
hostname: "example.com",
port: 80,
path: "/api/data",
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer token123",
},
};
const req = http.request(options, (res) => {
console.log(`상태 코드: ${res.statusCode}`);
res.setEncoding("utf8");
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.log("응답 본문:", data);
});
});
req.on("error", (e) => {
console.error(`요청 오류: ${e.message}`);
});
// 요청 본문 작성
const postData = JSON.stringify({
key1: "value1",
key2: "value2",
});
// 요청 전송
req.write(postData);
req.end();
5. 라우팅 구현
간단한 라우팅 시스템을 구현할 수 있습니다:
const http = require("http");
const url = require("url");
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const trimmedPath = path.replace(/^\/+|\/+$/g, "");
const method = req.method.toLowerCase();
const queryParams = parsedUrl.query;
console.log(`경로: ${trimmedPath}, 메서드: ${method}`);
// 라우팅 처리
if (method === "get" && trimmedPath === "users") {
// GET /users 처리
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ users: ["user1", "user2", "user3"] }));
} else if (method === "get" && trimmedPath === "products") {
// GET /products 처리
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ products: ["product1", "product2"] }));
} else {
// 404 처리
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "페이지를 찾을 수 없습니다." }));
}
});
server.listen(3000, () => {
console.log("서버가 3000 포트에서 실행 중입니다.");
});
6. 오류 처리
다양한 오류 상황을 처리하는 방법:
const server = http.createServer((req, res) => {
// 요청 타임아웃 설정
req.setTimeout(5000, () => {
res.writeHead(408, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "요청 시간 초과" }));
});
// ... 요청 처리 로직
});
// 서버 오류 처리
server.on("error", (err) => {
if (err.code === "EADDRINUSE") {
console.error("포트가 이미 사용 중입니다!");
} else {
console.error("서버 오류:", err);
}
});
// 클라이언트 연결 오류 처리
server.on("clientError", (err, socket) => {
console.error("클라이언트 연결 오류:", err);
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
});
7. HTTPS 서버 생성
HTTPS 서버를 만들기 위해서는 https
모듈과 SSL/TLS 인증서가 필요합니다:
const https = require("https");
const fs = require("fs");
const options = {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem"),
};
const server = https.createServer(options, (req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("안전한 HTTPS 서버입니다!\n");
});
server.listen(443, () => {
console.log("HTTPS 서버가 443 포트에서 실행 중입니다.");
});
8. 서버 최적화 및 모범 사례
- Keep-Alive 활용: 연결 재사용을 위해 Keep-Alive 기능을 활용합니다.
const server = http.createServer((req, res) => {
// Keep-Alive 활성화
if (req.headers.connection !== "close") {
res.setHeader("Connection", "keep-alive");
res.setHeader("Keep-Alive", "timeout=5, max=1000");
}
// ... 요청 처리 로직
});
- Gzip 압축: 응답 데이터 압축을 통해 전송 속도를 향상시킵니다.
const zlib = require("zlib");
server.on("request", (req, res) => {
const acceptEncoding = req.headers["accept-encoding"] || "";
if (/\bgzip\b/.test(acceptEncoding)) {
res.writeHead(200, {
"Content-Type": "text/plain",
"Content-Encoding": "gzip",
});
const output = zlib.createGzip();
output.pipe(res);
output.write("압축된 내용입니다.");
output.end();
} else {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("압축되지 않은 내용입니다.");
}
});
- 요청 제한: 너무 큰 요청 본문을 방지하기 위한 제한을 설정합니다.
server.on("request", (req, res) => {
let body = [];
let size = 0;
const MAX_SIZE = 1024 * 1024; // 1MB 제한
req.on("data", (chunk) => {
size += chunk.length;
if (size > MAX_SIZE) {
// 요청 너무 큼, 연결 종료
res.writeHead(413, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "요청이 너무 큽니다." }));
req.connection.destroy();
} else {
body.push(chunk);
}
});
});
HTTP 모듈은 Node.js에서 웹 애플리케이션과 API를 개발하는 기본적인 도구입니다. 더 복잡한 웹 애플리케이션을 위해서는 Express.js와 같은 프레임워크가 더 많은 기능과 편의성을 제공하지만, HTTP 모듈을 이해하는 것은 Node.js의 네트워크 기능을 효과적으로 활용하는 데 필수적입니다.