728x90
반응형
1. HTTP 모듈 소개
Node.js의 http 모듈은 HTTP 서버와 클라이언트를 생성하기 위한 내장 모듈입니다. 별도의 패키지 설치 없이 웹 서버를 구축할 수 있습니다.
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('서버가 http://localhost:3000 에서 실행 중');
});
2. 서버 생성 방법
2.1 createServer 사용
const http = require('http');
// 방법 1: 콜백 함수 전달
const server1 = http.createServer((req, res) => {
res.end('Hello');
});
// 방법 2: request 이벤트 리스너
const server2 = http.createServer();
server2.on('request', (req, res) => {
res.end('Hello');
});
server1.listen(3000);
2.2 서버 옵션
const http = require('http');
const server = http.createServer({
maxHeaderSize: 8192, // 헤더 최대 크기
keepAlive: true, // Keep-Alive 활성화
keepAliveInitialDelay: 0, // Keep-Alive 초기 지연
requestTimeout: 300000, // 요청 타임아웃 (5분)
headersTimeout: 60000, // 헤더 타임아웃 (1분)
connectionsCheckingInterval: 30000 // 연결 체크 간격
}, (req, res) => {
res.end('Hello');
});
3. 요청(Request) 처리
3.1 요청 정보 읽기
const http = require('http');
const server = http.createServer((req, res) => {
console.log('메서드:', req.method); // GET, POST, PUT, DELETE 등
console.log('URL:', req.url); // /path?query=value
console.log('HTTP 버전:', req.httpVersion); // 1.1
console.log('헤더:', req.headers); // { host: 'localhost', ... }
// 특정 헤더 읽기
console.log('Host:', req.headers.host);
console.log('User-Agent:', req.headers['user-agent']);
res.end('Request received');
});
server.listen(3000);
3.2 URL 파싱
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
// URL 파싱
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
console.log('경로:', parsedUrl.pathname); // /users
console.log('쿼리 문자열:', parsedUrl.search); // ?id=123
console.log('쿼리 파라미터:', parsedUrl.searchParams.get('id')); // 123
res.end('URL parsed');
});
server.listen(3000);
3.3 요청 본문 읽기
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);
// JSON 파싱
try {
const data = JSON.parse(body);
console.log('파싱된 데이터:', data);
} catch (e) {
console.log('JSON이 아닌 데이터');
}
res.end('Body received');
});
} else {
res.end('Send POST request');
}
});
server.listen(3000);
3.4 Promise 기반 본문 읽기
const http = require('http');
function getRequestBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
req.on('error', reject);
});
}
const server = http.createServer(async (req, res) => {
if (req.method === 'POST') {
const body = await getRequestBody(req);
const data = JSON.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ received: data }));
} else {
res.end('Hello');
}
});
server.listen(3000);
반응형
4. 응답(Response) 처리
4.1 상태 코드와 헤더 설정
const http = require('http');
const server = http.createServer((req, res) => {
// 상태 코드와 헤더 한 번에 설정
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-cache',
'X-Custom-Header': 'value'
});
res.end('<h1>안녕하세요</h1>');
});
// 또는 개별 설정
const server2 = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.setHeader('X-Powered-By', 'Node.js');
res.end(JSON.stringify({ message: 'Hello' }));
});
server.listen(3000);
4.2 다양한 응답 타입
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const path = req.url;
if (path === '/text') {
// 텍스트 응답
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('일반 텍스트');
}
else if (path === '/html') {
// HTML 응답
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>HTML 페이지</h1>');
}
else if (path === '/json') {
// JSON 응답
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name: '홍길동', age: 30 }));
}
else if (path === '/image') {
// 이미지 응답
res.writeHead(200, { 'Content-Type': 'image/png' });
fs.createReadStream('image.png').pipe(res);
}
else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
4.3 청크 응답
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked'
});
// 여러 번에 걸쳐 응답
res.write('첫 번째 청크\n');
setTimeout(() => {
res.write('두 번째 청크\n');
}, 1000);
setTimeout(() => {
res.write('세 번째 청크\n');
res.end(); // 응답 종료
}, 2000);
});
server.listen(3000);
5. 라우팅
5.1 기본 라우팅
const http = require('http');
const server = http.createServer((req, res) => {
const method = req.method;
const url = new URL(req.url, `http://${req.headers.host}`);
const path = url.pathname;
// 라우팅
if (method === 'GET' && path === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Home</h1>');
}
else if (method === 'GET' && path === '/about') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>About</h1>');
}
else if (method === 'GET' && path.startsWith('/users/')) {
const userId = path.split('/')[2];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ userId }));
}
else if (method === 'POST' && path === '/users') {
// POST 처리
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'User created' }));
}
else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
5.2 라우터 클래스
const http = require('http');
class Router {
constructor() {
this.routes = [];
}
add(method, path, handler) {
this.routes.push({
method: method.toUpperCase(),
path: new RegExp(`^${path.replace(/:\w+/g, '([^/]+)')}$`),
handler,
paramNames: (path.match(/:\w+/g) || []).map(p => p.slice(1))
});
}
get(path, handler) { this.add('GET', path, handler); }
post(path, handler) { this.add('POST', path, handler); }
put(path, handler) { this.add('PUT', path, handler); }
delete(path, handler) { this.add('DELETE', path, handler); }
handle(req, res) {
const url = new URL(req.url, `http://${req.headers.host}`);
for (const route of this.routes) {
if (route.method !== req.method) continue;
const match = url.pathname.match(route.path);
if (match) {
req.params = {};
route.paramNames.forEach((name, i) => {
req.params[name] = match[i + 1];
});
req.query = Object.fromEntries(url.searchParams);
return route.handler(req, res);
}
}
res.writeHead(404);
res.end('Not Found');
}
}
// 사용
const router = new Router();
router.get('/', (req, res) => {
res.end('Home');
});
router.get('/users/:id', (req, res) => {
res.end(`User ID: ${req.params.id}`);
});
router.post('/users', (req, res) => {
res.writeHead(201);
res.end('Created');
});
const server = http.createServer((req, res) => router.handle(req, res));
server.listen(3000);
6. 정적 파일 서빙
const http = require('http');
const fs = require('fs');
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon'
};
const server = http.createServer((req, res) => {
// URL에서 파일 경로 추출
let filePath = path.join(__dirname, 'public', req.url);
// 디렉토리면 index.html 추가
if (req.url === '/') {
filePath = path.join(__dirname, 'public', 'index.html');
}
// 파일 확장자로 MIME 타입 결정
const ext = path.extname(filePath);
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
// 파일 읽기
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404);
res.end('File Not Found');
} else {
res.writeHead(500);
res.end('Server Error');
}
return;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
});
});
server.listen(3000);
7. 서버 이벤트
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello');
});
// 서버 이벤트
server.on('listening', () => {
console.log('서버 시작됨');
});
server.on('connection', (socket) => {
console.log('새 연결:', socket.remoteAddress);
});
server.on('close', () => {
console.log('서버 종료됨');
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error('포트가 이미 사용 중입니다');
} else {
console.error('서버 오류:', err);
}
});
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.listen(3000);
8. 서버 제어
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello');
});
// 서버 시작
server.listen(3000, '0.0.0.0', () => {
console.log('서버 주소:', server.address());
// { address: '0.0.0.0', family: 'IPv4', port: 3000 }
});
// 서버 종료 (기존 연결은 유지)
setTimeout(() => {
server.close(() => {
console.log('서버 종료됨');
});
}, 60000);
// 새 연결 거부, 기존 연결 즉시 종료
process.on('SIGTERM', () => {
server.close(() => {
process.exit(0);
});
});
9. 실전 예시: 간단한 API 서버
const http = require('http');
// 데이터 저장소
const users = new Map();
let nextId = 1;
// 요청 본문 파싱
async function parseBody(req) {
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks).toString();
return body ? JSON.parse(body) : null;
}
// JSON 응답
function jsonResponse(res, statusCode, data) {
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
// 라우트 핸들러
const handlers = {
'GET /users': (req, res) => {
jsonResponse(res, 200, Array.from(users.values()));
},
'GET /users/:id': (req, res, params) => {
const user = users.get(parseInt(params.id));
if (user) {
jsonResponse(res, 200, user);
} else {
jsonResponse(res, 404, { error: 'User not found' });
}
},
'POST /users': async (req, res) => {
const body = await parseBody(req);
const user = { id: nextId++, ...body };
users.set(user.id, user);
jsonResponse(res, 201, user);
},
'PUT /users/:id': async (req, res, params) => {
const id = parseInt(params.id);
if (!users.has(id)) {
return jsonResponse(res, 404, { error: 'User not found' });
}
const body = await parseBody(req);
const user = { id, ...body };
users.set(id, user);
jsonResponse(res, 200, user);
},
'DELETE /users/:id': (req, res, params) => {
const id = parseInt(params.id);
if (users.delete(id)) {
jsonResponse(res, 204, null);
} else {
jsonResponse(res, 404, { error: 'User not found' });
}
}
};
// 라우팅
function route(req, res) {
const url = new URL(req.url, `http://${req.headers.host}`);
for (const [pattern, handler] of Object.entries(handlers)) {
const [method, path] = pattern.split(' ');
if (method !== req.method) continue;
const regex = new RegExp(`^${path.replace(/:(\w+)/g, '(?<$1>[^/]+)')}$`);
const match = url.pathname.match(regex);
if (match) {
return handler(req, res, match.groups || {});
}
}
jsonResponse(res, 404, { error: 'Not found' });
}
const server = http.createServer(route);
server.listen(3000, () => {
console.log('API 서버 실행 중: http://localhost:3000');
});
결론
Node.js의 http 모듈로 HTTP 서버를 직접 구현할 수 있습니다. createServer로 서버를 생성하고, 요청 객체(req)에서 메서드, URL, 헤더, 본문을 읽고, 응답 객체(res)로 상태 코드, 헤더, 본문을 전송합니다. 실제 프로덕션에서는 Express.js 같은 프레임워크를 사용하지만, 기본 http 모듈의 동작 원리를 이해하면 문제 해결과 최적화에 도움이 됩니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js의 파일 스트림 처리 (0) | 2026.03.07 |
|---|---|
| Node.js의 디렉토리 생성 및 삭제 (0) | 2026.03.07 |
| Node.js의 파일 삭제 및 이동 (0) | 2026.03.02 |
| Node.js의 파일 읽기와 쓰기 (0) | 2026.02.28 |
| Node.js의 이벤트 에미터(EventEmitter) 사용법 (0) | 2026.02.25 |