1. HTTP 모듈: 웹 서버 구축의 시작
HTTP 모듈은 Node.js에서 웹 서버와 클라이언트 간의 통신을 담당하는 핵심 모듈입니다. HTTP 프로토콜을 기반으로 요청과 응답을 처리하는 기능을 제공하여, 웹 서버를 쉽게 구축하고 클라이언트와 통신할 수 있도록 지원합니다.
1.1 HTTP 모듈 개요
HTTP 모듈은 Node.js에 내장된 모듈 중 하나로, HTTP 프로토콜을 사용하여 웹 서버를 구축하고 클라이언트와 데이터를 주고받을 수 있는 기능을 제공합니다. 이를 통해 개발자는 간편하게 웹 서버를 구축하고, 클라이언트의 요청을 처리하고 응답을 전송할 수 있습니다.
1.2 핵심 개념: 요청과 응답
- 요청 (Request): 클라이언트가 서버에 보내는 데이터입니다. 예를 들어, 사용자가 웹 브라우저에 URL을 입력하면 해당 주소로 GET 요청이 전송됩니다.
- 응답 (Response): 서버가 클라이언트에게 보내는 데이터입니다. HTML 페이지, JSON 데이터 등 다양한 형태로 제공될 수 있습니다.
1.3 간단한 웹 서버 구축
HTTP 모듈을 사용하여 간단한 웹 서버를 구축하는 예제를 살펴봅니다.
const http = require('http'); // HTTP 모듈 불러오기
// 서버 생성
const server = http.createServer((req, res) => {
res.statusCode = 200; // 상태 코드 설정 (200: 성공)
res.setHeader('Content-Type', 'text/plain'); // 응답 헤더 설정
res.end('안녕하세요! Node.js 웹 서버입니다.\n'); // 응답 내용 전송
});
// 포트 3000에서 요청 대기
server.listen(3000, () => {
console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});
위 코드를 실행하면 http://localhost:3000/
에 접속했을 때 "안녕하세요! Node.js 웹 서버입니다."라는 메시지를 확인할 수 있습니다.
1.4 요청 및 응답 객체 상세 분석
- 요청 객체 (req):
req.method
: 요청 방식 (GET, POST 등)을 나타냅니다.req.url
: 요청된 URL 경로를 포함합니다.req.headers
: 요청 헤더 정보를 포함합니다.
- 응답 객체 (res):
res.statusCode
: 응답 상태 코드를 설정합니다.res.setHeader(name, value)
: 응답 헤더를 설정합니다.res.writeHead(statusCode[, statusMessage][, headers])
: 상태 코드와 헤더 정보를 한 번에 설정합니다.res.write(chunk[, encoding][, callback])
: 응답 데이터를 전송합니다. (여러 번 호출 가능)res.end([data][, encoding])
: 응답 데이터를 전송하고 연결을 종료합니다.
1.5 다양한 요청 처리 예제
예제 1: URL 경로에 따른 응답 처리
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/hello') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('안녕하세요!');
} else if (req.method === 'GET' && req.url === '/about') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Node.js로 만든 웹 서버입니다.');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('페이지를 찾을 수 없습니다.');
}
});
server.listen(3000, () => {
console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});
예제 2: JSON 데이터 응답
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/api/user') {
const user = {
name: 'John Doe',
age: 30,
city: 'New York'
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user)); // JSON 객체를 문자열로 변환하여 응답
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});
예제 3: HTML 응답
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Hello, World!</h1><p>This is a simple Node.js web server.</p>');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});
예제 4: 요청 헤더 확인
const http = require('http');
const server = http.createServer((req, res) => {
const userAgent = req.headers['user-agent']; // User-Agent 헤더 읽기
console.log('User-Agent:', userAgent);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Request headers logged.');
});
server.listen(3000, () => {
console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});
2. 파일 시스템 모듈: 데이터 관리의 핵심
파일 시스템 모듈(fs)은 Node.js 애플리케이션이 로컬 파일 시스템과 상호작용하도록 돕는 핵심 모듈입니다. 파일 및 디렉토리 생성, 읽기, 쓰기, 삭제 등 파일 시스템 관련 작업을 수행하는 데 필수적인 기능을 제공합니다.
2.1 파일 시스템 모듈 소개
파일 시스템 모듈은 Node.js 애플리케이션이 로컬 파일 시스템과 상호작용할 수 있도록 다양한 기능을 제공합니다. 이를 통해 개발자는 다음과 같은 작업을 쉽게 수행할 수 있습니다.
- 파일 읽기 및 쓰기
- 디렉토리 생성 및 삭제
- 파일 메타데이터 조회
2.2 파일 읽기 및 쓰기
1) 비동기 파일 읽기 (fs.readFile)
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('파일 읽기 오류:', err);
return;
}
console.log('파일 내용:', data); // example.txt의 내용 출력
});
2) 파일 쓰기 (fs.writeFile)
const fs = require('fs');
const content = 'Node.js 파일 시스템 모듈 사용 예제';
fs.writeFile('output.txt', content, (err) => {
if (err) {
console.error('파일 쓰기 오류:', err);
return;
}
console.log('파일 쓰기 완료!');
});
3) 파일에 데이터 추가 (fs.appendFile)
const fs = require('fs');
const dataToAppend = '\n추가할 내용입니다.';
fs.appendFile('example.txt', dataToAppend, 'utf8', (err) => {
if (err) {
console.error('데이터 추가 오류:', err);
return;
}
console.log('데이터 추가 완료!');
});
2.3 디렉토리 조작
1) 디렉토리 생성 (fs.mkdir)
const fs = require('fs');
fs.mkdir('myDirectory', { recursive: true }, (err) => {
if (err) {
console.error('디렉토리 생성 오류:', err);
return;
}
console.log('디렉토리 생성 완료!');
});
2) 디렉토리 내용 읽기 (fs.readdir)
const fs = require('fs');
const directoryPath = './'; // 현재 디렉토리
fs.readdir(directoryPath, (err, files) => {
if (err) {
console.error('디렉토리 읽기 오류:', err);
return;
}
console.log('디렉토리 내용:', files);
});
2.4 파일 및 디렉토리 정보 확인
1) 파일 존재 여부 확인 (fs.existsSync)
const fs = require('fs');
const filePath = 'example.txt';
if (fs.existsSync(filePath)) {
console.log('파일이 존재합니다.');
} else {
console.log('파일이 존재하지 않습니다.');
}
2.5 동기 vs. 비동기: 성능 최적화
Node.js 파일 시스템 모듈은 대부분의 함수에 대해 동기 및 비동기 버전을 제공합니다.
- 비동기 함수: 논블로킹 방식으로 작동하여, 파일 입출력 작업 중에도 다른 코드를 실행할 수 있습니다.
- 동기 함수: 블로킹 방식으로 작동하여, 파일 입출력 작업이 완료될 때까지 다음 코드로 진행하지 않습니다.
예제: 동기 파일 읽기 (fs.readFileSync)
const fs = require('fs');
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log('파일 내용 (동기):', data);
} catch (err) {
console.error('파일 읽기 오류 (동기):', err);
}
주의 사항:
- 동기 함수는 사용하기 간편하지만, 대용량 파일 처리 시 성능 저하를 유발할 수 있습니다.
- Node.js의 비동기 이벤트 기반 모델을 최대한 활용하기 위해, 가능한 비동기 함수를 사용하는 것이 좋습니다.
3. 이벤트 모듈: 비동기 프로그래밍의 핵심
이벤트 기반 프로그래밍은 Node.js의 핵심 개념 중 하나입니다. 이벤트 모듈은 이러한 이벤트 기반 시스템을 구현하기 위한 도구를 제공하여, 비동기 작업을 효율적으로 처리하고 관리할 수 있도록 지원합니다.
3.1 이벤트: 변화를 감지하는 신호
이벤트는 특정한 동작이나 상태 변화를 나타내는 신호입니다. 예를 들어, 버튼 클릭, 파일 읽기 완료, 네트워크 요청 수신 등은 모두 이벤트로 간주될 수 있습니다. 이벤트 기반 프로그래밍에서는 이러한 이벤트를 감지하고, 이벤트 발생 시 특정 동작을 수행하도록 코드를 작성합니다.
3.2 EventEmitter 클래스: 이벤트 생성 및 관리
Node.js의 events
모듈은 EventEmitter
클래스를 제공하여 사용자 정의 객체에서 쉽게 이벤트를 생성하고 관리할 수 있도록 지원합니다.
주요 메서드:
- on(eventName, listener): 지정된 이름의 이벤트에 대한 리스너 (처리 함수)를 등록합니다.
- emit(eventName[, ...args]): 해당 이름의 이벤트를 발생시키고 등록된 리스너에게 인수를 전달합니다.
- removeListener(eventName, listener): 특정 리스너를 제거합니다.
- once(eventName, listener): 지정된 이름의 이벤트에 대한 일회성 리스너를 등록합니다. 이벤트가 처음 발생하면 리스너가 실행되고 자동으로 제거됩니다.
- removeAllListeners([eventName]): 특정 이벤트 또는 모든 이벤트에 대한 모든 리스너를 제거합니다.
3.3 EventEmitter 활용 예제
예제 1: 카운터
const EventEmitter = require('events');
class Counter extends EventEmitter {
constructor() {
super();
this.count = 0;
}
increment() {
this.count++;
this.emit('increment', this.count); // 'increment' 이벤트 발생
}
}
const myCounter = new Counter();
// 'increment' 이벤트 리스너 등록
myCounter.on('increment', (count) => {
console.log(`카운트 증가: ${count}`);
});
myCounter.increment(); // 카운트 증가: 1
myCounter.increment(); // 카운트 증가: 2
myCounter.increment(); // 카운트 증가: 3
예제 2: 파일 읽기 완료 이벤트 처리
const fs = require('fs');
const EventEmitter = require('events');
class FileReader extends EventEmitter {
constructor(filePath) {
super();
this.filePath = filePath;
}
readFile() {
fs.readFile(this.filePath, 'utf8', (err, data) => {
if (err) {
this.emit('error', err); // 에러 이벤트 발생
} else {
this.emit('data', data); // 데이터 이벤트 발생
}
});
}
}
const reader = new FileReader('example.txt');
reader.on('data', (data) => {
console.log('파일 읽기 완료:', data);
});
reader.on('error', (err) => {
console.error('파일 읽기 오류:', err);
});
reader.readFile();
예제 3: 사용자 정의 이벤트 - 로그인 이벤트
const EventEmitter = require('events');
class UserAuth extends EventEmitter {
login(username, password) {
// 로그인 로직 (여기서는 생략)
console.log(`${username}님이 로그인했습니다.`);
this.emit('login', username); // 'login' 이벤트 발생
}
}
const auth = new UserAuth();
auth.on('login', (username) => {
console.log(`로그인 이벤트 발생: ${username}님 환영합니다!`);
});
auth.login('johndoe', 'password123');
예제 4: once를 사용한 일회성 이벤트 처리
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.once('eventOnce', () => {
console.log('이 이벤트는 한 번만 실행됩니다.');
});
emitter.emit('eventOnce'); // 이 이벤트는 한 번만 실행됩니다.
emitter.emit('eventOnce'); // 이 이벤트는 더 이상 실행되지 않습니다.
예제 5: removeAllListeners를 사용한 리스너 제거
const EventEmitter = require('events');
const emitter = new EventEmitter();
const listener1 = () => console.log('리스너 1');
const listener2 = () => console.log('리스너 2');
emitter.on('myEvent', listener1);
emitter.on('myEvent', listener2);
emitter.emit('myEvent'); // 리스너 1, 리스너 2 실행
emitter.removeAllListeners('myEvent'); // 'myEvent'에 대한 모든 리스너 제거
emitter.emit('myEvent'); // 아무 리스너도 실행되지 않음
3.4 이벤트 모듈 활용 분야
- 실시간 애플리케이션: 채팅 애플리케이션에서 메시지 수신 이벤트를 처리하여 사용자에게 실시간 알림을 제공할 수 있습니다.
- 파일 시스템 감시: 파일 변경 이벤트를 감지하여 자동 백업 또는 알림 기능을 구현할 수 있습니다.
- API 서버: 요청 수신 이벤트를 처리하여 각 요청에 대한 적절한 응답을 제공할 수 있습니다.
- 비동기 작업 관리: 여러 비동기 작업의 완료 이벤트를 처리하여 작업 순서를 제어하고 결과를 효율적으로 관리할 수 있습니다.
4. 스트림 모듈: 효율적인 데이터 처리
스트림 모듈은 Node.js에서 대용량 데이터를 효율적으로 처리하도록 설계된 핵심 모듈입니다. 데이터를 작은 조각(chunk)으로 나누어 순차적으로 처리하기 때문에, 메모리 사용량을 최소화하고 성능을 향상시킬 수 있습니다.
4.1 스트림: 데이터의 흐름
스트림은 데이터가 흐르는 추상적인 경로를 의미합니다. 마치 물이 파이프를 통해 흐르듯이, 데이터는 스트림을 통해 이동하며 처리됩니다.
주요 스트림 유형:
- 읽기 스트림 (Readable Stream): 데이터를 읽어오는 스트림입니다.
- 쓰기 스트림 (Writable Stream): 데이터를 쓰는 스트림입니다.
- 변환 스트림 (Transform Stream): 데이터를 읽고 변환하여 쓰는 스트림입니다.
- 듀플렉스 스트림 (Duplex Stream): 읽기와 쓰기가 모두 가능한 스트림입니다.
4.2 스트림의 핵심 장점
- 메모리 효율성: 대용량 데이터를 작은 조각으로 나누어 처리하기 때문에 메모리 사용량을 크게 줄일 수 있습니다.
- 비동기 처리: 스트림은 비동기적으로 작동하여, 데이터 처리 중에도 다른 작업을 수행할 수 있습니다. 이를 통해 애플리케이션의 응답성을 향상시킵니다.
- 파이프라인: 여러 스트림을 연결(pipe)하여 데이터를 효율적으로 처리할 수 있습니다.
4.3 스트림 활용 예제
예제 1: 파일 읽기와 쓰기: 파이프를 이용한 효율적인 복사
const fs = require('fs');
// 읽기 스트림 생성 (input.txt)
const readStream = fs.createReadStream('input.txt', 'utf8');
// 쓰기 스트림 생성 (output.txt)
const writeStream = fs.createWriteStream('output.txt');
// 파이프를 사용하여 읽기 스트림을 쓰기 스트림에 연결
readStream.pipe(writeStream);
// 데이터 이벤트 처리 (선택 사항)
readStream.on('data', (chunk) => {
console.log('읽은 데이터:', chunk);
});
// 완료 이벤트 처리
writeStream.on('finish', () => {
console.log('파일 복사 완료!');
});
예제 2: HTTP 요청 데이터 처리: 스트림을 이용한 대용량 요청 처리
const http = require('http');
const server = http.createServer((req, res) => {
let body = '';
// 데이터 이벤트 처리: 요청 본문을 스트림으로 읽기
req.on('data', (chunk) => {
body += chunk.toString(); // 청크 단위로 데이터 누적
});
// 종료 이벤트 처리: 요청 처리가 완료되었을 때
req.on('end', () => {
console.log('받은 요청 본문:', body);
res.end('요청 받음');
});
});
server.listen(3000, () => {
console.log('서버가 3000번 포트에서 시작되었습니다.');
});
예제 3: 변환 스트림: 데이터 변환 및 가공
const { Transform } = require('stream');
// 대문자로 변환하는 변환 스트림 생성
const toUpperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
});
// 표준 입력 스트림을 대문자 변환 스트림에 연결하고, 결과를 표준 출력 스트림으로 전달
process.stdin.pipe(toUpperCaseTransform).pipe(process.stdout);
예제 4: 파일 압축 (zlib 사용)
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip(); // gzip 압축 스트림 생성
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('input.txt.gz');
readStream.pipe(gzip).pipe(writeStream); // input.txt를 압축하여 input.txt.gz로 저장
writeStream.on('finish', () => {
console.log('파일 압축 완료!');
});
예제 5: 파일 복호화 (crypto 사용)
const fs = require('fs');
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const password = 'your-secret-password';
const key = crypto.scryptSync(password, 'salt', 32);
const iv = crypto.randomBytes(16); // Initialization Vector
const decipher = crypto.createDecipheriv(algorithm, key, iv); // 복호화 스트림 생성
const readStream = fs.createReadStream('encrypted.dat');
const writeStream = fs.createWriteStream('decrypted.txt');
readStream.pipe(decipher).pipe(writeStream); // encrypted.dat를 복호화하여 decrypted.txt로 저장
writeStream.on('finish', () => {
console.log('파일 복호화 완료!');
});
예제 6: CSV 파싱 (csv-parser 사용)
const fs = require('fs');
const csv = require('csv-parser');
const results = [];
fs.createReadStream('data.csv')
.pipe(csv()) // CSV 파싱 스트림
.on('data', (data) => results.push(data)) // 각 행을 객체로 변환하여 배열에 추가
.on('end', () => {
console.log('CSV 파싱 결과:', results);
});
결론: Node.js 핵심 모듈 정복을 통한 성장
지금까지 Node.js의 핵심 모듈인 HTTP, 파일 시스템, 이벤트, 그리고 스트림 모듈에 대해 자세히 살펴보았습니다. 각 모듈의 개념, 주요 기능, 그리고 다양하고 실용적인 예제를 통해 Node.js의 강력한 기능을 이해하고 활용하는 방법을 익혔습니다.
'프로그래밍 > Node.js' 카테고리의 다른 글
Node.js 웹 개발: Express.js와 Koa.js 프레임워크 비교 (0) | 2025.02.18 |
---|---|
Node.js로 웹 애플리케이션 개발하기: 기본부터 실전까지 (0) | 2025.02.18 |
Node.js 정복을 위한 세 가지 열쇠: 이벤트 기반 프로그래밍, 비동기 I/O, 모듈 시스템 (0) | 2025.02.18 |
Node.js 정복 가이드: 설치부터 NPM 활용까지, 서버 개발 첫걸음 (0) | 2025.02.17 |
Node.js: 서버사이드 JavaScript의 혁명 (0) | 2025.02.17 |