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. 서버 최적화 및 모범 사례

  1. 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");
  }

  // ... 요청 처리 로직
});
  1. 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("압축되지 않은 내용입니다.");
  }
});
  1. 요청 제한: 너무 큰 요청 본문을 방지하기 위한 제한을 설정합니다.
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의 네트워크 기능을 효과적으로 활용하는 데 필수적입니다.

results matching ""

    No results matching ""