Node.js 인터뷰 질문 39
질문: Node.js의 HTTP 모듈에 대해 설명하고, 간단한 웹 서버를 만드는 방법을 코드와 함께 설명해주세요.
답변:
Node.js의 HTTP 모듈은 HTTP 서버와 클라이언트 기능을 구현하기 위한 핵심 모듈입니다. 이 모듈을 사용하면 HTTP 서버를 생성하고 클라이언트 요청을 처리할 수 있으며, HTTP 클라이언트로서 다른 서버에 요청을 보낼 수도 있습니다.
HTTP 모듈 개요
HTTP 모듈은 Node.js의 핵심 모듈 중 하나로, 외부 패키지 설치 없이 require('http')
를 통해 사용할 수 있습니다. 주요 기능은 다음과 같습니다:
- HTTP 서버 생성 및 요청 처리
- HTTP 클라이언트 기능 제공
- 서버와 클라이언트 간의 데이터 스트리밍
- 헤더, 상태 코드, 쿠키 등 HTTP 메시지 관리
간단한 HTTP 서버 만들기
다음은 HTTP 모듈을 사용하여 간단한 웹 서버를 만드는 기본 예제입니다:
const http = require("http");
// 서버 생성
const server = http.createServer((req, res) => {
// 요청 정보 출력
console.log(`요청 방식: ${req.method}`);
console.log(`요청 URL: ${req.url}`);
console.log(`요청 헤더: ${JSON.stringify(req.headers)}`);
// 응답 헤더 설정
res.setHeader("Content-Type", "text/html; charset=utf-8");
// URL에 따른 다른 응답 처리
if (req.url === "/") {
res.statusCode = 200;
res.end("<h1>메인 페이지에 오신 것을 환영합니다!</h1>");
} else if (req.url === "/about") {
res.statusCode = 200;
res.end("<h1>소개 페이지입니다</h1>");
} else {
// 404 Not Found 응답
res.statusCode = 404;
res.end("<h1>페이지를 찾을 수 없습니다</h1>");
}
});
// 포트 3000에서 서버 실행
const PORT = 3000;
server.listen(PORT, () => {
console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다`);
});
이 코드는 다음과 같은 기능을 수행합니다:
- HTTP 모듈을 불러옵니다.
createServer
메소드로 서버를 생성합니다. 이 메소드는 요청 핸들러 함수를 인자로 받습니다.- 요청 핸들러 함수는 요청(
req
)과 응답(res
) 객체를 매개변수로 받습니다. - 서버는 요청 URL에 따라 다른 콘텐츠로 응답합니다.
listen
메소드로 특정 포트에서 서버를 시작합니다.
요청(Request) 객체
req
객체는 클라이언트로부터 받은 HTTP 요청에 대한 정보를 포함합니다. 주요 속성과 메소드는 다음과 같습니다:
req.method
: 요청 메소드(GET, POST, PUT, DELETE 등)req.url
: 요청 URLreq.headers
: 요청 헤더 객체req.httpVersion
: HTTP 버전
요청 본문 데이터를 읽는 예제:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.method === "POST") {
let body = "";
// 데이터를 청크로 받기
req.on("data", (chunk) => {
body += chunk.toString();
});
// 데이터 수신이 완료되면 처리
req.on("end", () => {
console.log("요청 본문:", body);
try {
const data = JSON.parse(body);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
message: "데이터를 성공적으로 받았습니다",
data: data,
})
);
} catch (error) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
message: "잘못된 JSON 형식입니다",
})
);
}
});
} else {
res.writeHead(405, { "Content-Type": "text/plain" });
res.end("POST 요청만 허용됩니다");
}
});
server.listen(3000, () => {
console.log("서버가 포트 3000에서 실행 중입니다");
});
응답(Response) 객체
res
객체는 클라이언트에게 보낼 HTTP 응답을 구성하는 데 사용됩니다. 주요 메소드는 다음과 같습니다:
res.writeHead(statusCode[, statusMessage][, headers])
: 응답 헤더 작성res.setHeader(name, value)
: 개별 헤더 설정res.write(chunk[, encoding][, callback])
: 응답 본문 작성res.end([data][, encoding][, callback])
: 응답 완료
다양한 형식의 응답을 보내는 예제:
const http = require("http");
const fs = require("fs");
const path = require("path");
const server = http.createServer((req, res) => {
// HTML 응답
if (req.url === "/html") {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<html><body><h1>HTML 응답입니다</h1></body></html>");
}
// JSON 응답
else if (req.url === "/json") {
const data = {
name: "Node.js",
version: process.version,
description: "JavaScript 런타임",
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(data));
}
// 파일 응답 (스트리밍)
else if (req.url === "/file") {
const filePath = path.join(__dirname, "example.txt");
// 파일이 존재하는지 확인
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("파일을 찾을 수 없습니다");
return;
}
// 파일 스트림 생성 및 응답
const fileStream = fs.createReadStream(filePath);
res.writeHead(200, { "Content-Type": "text/plain" });
// 파일 스트림을 응답에 파이프
fileStream.pipe(res);
// 오류 처리
fileStream.on("error", (error) => {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("서버 오류가 발생했습니다");
});
});
}
// 리다이렉션
else if (req.url === "/redirect") {
res.writeHead(302, { Location: "/" });
res.end();
}
// 기본 응답
else {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("기본 응답입니다");
}
});
server.listen(3000, () => {
console.log("서버가 포트 3000에서 실행 중입니다");
});
HTTP 클라이언트 사용하기
HTTP 모듈을 사용하여 다른 서버에 요청을 보내는 방법도 있습니다:
const http = require("http");
// GET 요청 예제
const getOptions = {
hostname: "example.com",
port: 80,
path: "/api/data",
method: "GET",
headers: {
"User-Agent": "Node.js HTTP Client",
},
};
const getReq = http.request(getOptions, (res) => {
console.log(`상태 코드: ${res.statusCode}`);
let data = "";
// 데이터 수신
res.on("data", (chunk) => {
data += chunk;
});
// 응답 완료
res.on("end", () => {
console.log("응답 데이터:", data);
});
});
// 오류 처리
getReq.on("error", (error) => {
console.error("요청 오류:", error.message);
});
// 요청 종료
getReq.end();
// POST 요청 예제
const postData = JSON.stringify({
name: "홍길동",
email: "hong@example.com",
});
const postOptions = {
hostname: "example.com",
port: 80,
path: "/api/users",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(postData),
},
};
const postReq = http.request(postOptions, (res) => {
console.log(`상태 코드: ${res.statusCode}`);
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.log("응답 데이터:", data);
});
});
postReq.on("error", (error) => {
console.error("요청 오류:", error.message);
});
// 요청 본문 전송
postReq.write(postData);
postReq.end();
간편한 HTTP 요청을 위한 http.get()
간단한 GET 요청을 위해 http.get()
메소드를 사용할 수 있습니다:
const http = require("http");
http
.get("http://example.com/api/data", (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(`잘못된 컨텐츠 유형: ${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(`HTTP 요청 오류: ${e.message}`);
});
HTTP 서버의 고급 기능
1. 이벤트 처리
HTTP 서버는 다양한 이벤트를 발생시킵니다:
const server = http.createServer();
// request 이벤트
server.on("request", (req, res) => {
// 요청 처리
});
// connection 이벤트
server.on("connection", (socket) => {
console.log("새 연결이 생성되었습니다");
});
// close 이벤트
server.on("close", () => {
console.log("서버가 종료되었습니다");
});
// error 이벤트
server.on("error", (error) => {
console.error("서버 오류:", error);
});
server.listen(3000);
2. 타임아웃 설정
클라이언트 연결에 타임아웃을 설정할 수 있습니다:
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end("Hello World");
});
// 서버 연결 타임아웃 설정 (밀리초)
server.timeout = 5000;
// 또는 개별 요청에 타임아웃 설정
server.on("request", (req, res) => {
req.setTimeout(5000, () => {
res.writeHead(408); // Request Timeout
res.end("요청 시간이 초과되었습니다");
});
});
server.listen(3000);
실용적인 HTTP 서버 예제: REST API 서버
간단한 사용자 관리 REST API 서버 예제:
const http = require("http");
const url = require("url");
// 메모리 데이터베이스 역할
const users = [
{ id: 1, name: "김철수", email: "kim@example.com" },
{ id: 2, name: "홍길동", email: "hong@example.com" },
];
const server = http.createServer((req, res) => {
// CORS 헤더 설정
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
// OPTIONS 요청에 대한 처리
if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}
// URL 파싱
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const trimmedPath = path.replace(/^\/+|\/+$/g, "");
// 사용자 API 경로 처리
if (trimmedPath === "api/users") {
// 모든 사용자 조회 (GET)
if (req.method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(users));
}
// 새 사용자 추가 (POST)
else if (req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
try {
const newUser = JSON.parse(body);
newUser.id = users.length + 1; // 간단한 ID 생성
users.push(newUser);
res.writeHead(201, { "Content-Type": "application/json" });
res.end(JSON.stringify(newUser));
} catch (error) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "잘못된 요청입니다" }));
}
});
} else {
res.writeHead(405, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "허용되지 않는 메소드입니다" }));
}
}
// 특정 사용자 API 경로 처리
else if (trimmedPath.match(/^api\/users\/\d+$/)) {
const id = parseInt(trimmedPath.split("/").pop());
const userIndex = users.findIndex((user) => user.id === id);
if (userIndex === -1) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "사용자를 찾을 수 없습니다" }));
return;
}
// 특정 사용자 조회 (GET)
if (req.method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(users[userIndex]));
}
// 사용자 정보 업데이트 (PUT)
else if (req.method === "PUT") {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
try {
const updatedUser = JSON.parse(body);
users[userIndex] = { ...users[userIndex], ...updatedUser, id };
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(users[userIndex]));
} catch (error) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "잘못된 요청입니다" }));
}
});
}
// 사용자 삭제 (DELETE)
else if (req.method === "DELETE") {
const deletedUser = users.splice(userIndex, 1)[0];
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(deletedUser));
} else {
res.writeHead(405, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "허용되지 않는 메소드입니다" }));
}
}
// 기타 경로에 대한 처리
else {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "리소스를 찾을 수 없습니다" }));
}
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다`);
});
HTTP 모듈의 한계와 대안
HTTP 모듈은 간단한 웹 서버를 구축하는 데 유용하지만, 복잡한 애플리케이션을 개발할 때는 다음과 같은 한계가 있습니다:
- 라우팅 기능이 제한적입니다.
- 미들웨어 지원이 없습니다.
- 오류 처리가 복잡합니다.
- 파일 업로드, 폼 처리 등 고급 기능을 직접 구현해야 합니다.
이러한 한계를 극복하기 위해 Express, Koa, Fastify와 같은 웹 프레임워크를 사용하는 것이 일반적입니다. 이러한 프레임워크는 HTTP 모듈을 기반으로 구축되었지만 더 많은 기능과 편의성을 제공합니다.
요약
- HTTP 모듈은 Node.js에서 웹 서버와 클라이언트를 구현하기 위한 핵심 모듈입니다.
- 기본 사용법:
http.createServer()
로 서버 생성- 요청(
req
)과 응답(res
) 객체를 처리하는 콜백 함수 제공 server.listen()
으로 특정 포트에서 서버 시작
- 주요 기능:
- 요청 정보 접근 (
req.method
,req.url
,req.headers
) - 응답 헤더 및 상태 코드 설정 (
res.writeHead()
,res.setHeader()
) - 응답 데이터 전송 (
res.write()
,res.end()
) - HTTP 클라이언트 기능 (
http.request()
,http.get()
)
- 요청 정보 접근 (
- 고급 사용법:
- 이벤트 기반 프로그래밍
- 파일 스트리밍
- REST API 구현
- CORS 및 보안 헤더 관리
HTTP 모듈은 Node.js 웹 개발의 기초가 되는 모듈로, 이를 이해하면 Express와 같은 상위 수준 프레임워크의 동작 원리도 더 잘 이해할 수 있습니다.