Mini-Node-Server 만들기

유슬기·2023년 2월 6일
0

프론트엔드

목록 보기
40/64
post-thumbnail

HTTP 트랜잭션 해부 문서를 참고하여 mini node server를 구현하기

0. 요구사항

클라이언트의 액션(버튼 클릭)에 따라 각기 다른 HTTP 요청을 서버로 보내고, HTTP 요청에 담아 보낸 단어를 소문자 또는 대문자로 응답을 받아 화면에 보여 줍니다.

  • Endpoint(URL)에 따른 Method 기능 구현하기

    Endpoint(URL)Method기능
    /lowerPOST문자열을 소문자로 만들어 응답해야 합니다
    /upperPOST문자열을 대문자로 만들어 응답해야 합니다
  • POST에 문자열을 담아 요청을 보낼 때는 HTTP 메시지의 body(payload)를 이용합니다.

  • 서버는 요청에 따른 적절한 응답을 클라이언트로 보내야 합니다.

    • CORS 관련 헤더를 OPTIONS 응답에 적용해야 합니다.
    • 클라이언트의 preflight request에 대한 응답을 돌려줘야 합니다.

1. 서버 생성

모든 node 웹 서버 애플리케이션은 웹 서버 객체를 만들어야 한다. 이 때 createServer를 이용한다.

const http = require("http");

const PORT = 4999;

const ip = "localhost";

const server = http.createServer((request, response) => {
  // 코드 작성
})

이 서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출된다. HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 requestresponse 객체를 전달하며 요청 핸들러 함수를 호출한다.

server.listen(PORT, ip, () => {
  console.log(`http server listen on ${ip}:${PORT}`);
});
// 대부분은 아래와 같이 포트 번호 전달
server.listen(PORT)

요청을 실제로 처리하려면 listen 메서드가 server 객체에서 호출되어야 한다. 대부분은 서버가 사용하고자 하는 포트 번호를 listen에 전달하기만 하면 된다.

2. 메서드, URL, 헤더

요청을 처리할 때, 우선 메서드와 URL을 확인한 후 이와 관련된 적절한 작업을 실행해야한다. node는 request 객체에 각 프로퍼티를 넣어두었으므로 쉽게 작업 가능하다.

const { method, url } = request;
const { headers } = request;
const userAgent = headers['user-agent'];

여기서 method는 일반적인 HTTP 메서드/동사가 될 것이고, url은 전체 URL에서 서버, 프로토콜, 포트를 제외한 것으로, 세 번째 슬래시 이후의 나머지 전부라고 볼 수 있다.

request에 headers라는 전용 객체가 있고, 클라이언트가 어떻게 헤더를 설정했는지에 관계없이 모든 헤더는 소문자로만 표현된다. 이는 어떤 목적이든 헤더를 파싱하는 작업을 간편하게 해준다.

참고 문서에는 위와 같은 설명이 되어있었으나, 구조분해할당을 하지 않고 request.method, request.url 와 같이 사용하여도 문제는 없었다.

3. 요청 바디

POSTPUT 요청을 받을 때 애플리케이션에 요청 바디는 중요할 것이다. 핸들러에 전달된 request 객체는 ReadableStream 인터페이스를 구현하고 있는데, 이 스트림의 'data''end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있다.

'data' 이벤트에서 발생시킨 청크는 Buffer이다. 이 청크는 문자열 데이터이므로 이 데이터를 배열에 담고, 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋다.

let body = [];
request
  .on('data', (chunk) => {
  body.push(chunk);
})
  .on('end', () => {
  body = Buffer.concat(body).toString();
  // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
});

참고한 문서의 예제에는 body를 선언 후 빈 배열을 할당하여 push 메소드로 넣어주었는데, 문자열로 처리하면 toString이나 JSON.stringify를 쓰지 않아도 될 것 같았다.
다행히(?) 아래와 같이 작성 해도 에러 없이 잘 작동한다.

let body = '';
request
  .on('data', (chunk) => {
  body += chunk;
})
  .on('end', () => {
   ...
});

4. CORS 적용해보기

현재 작업 환경은 클라이언트, 서버 모두 로컬 환경에서 이루어지고 있다. CORS를 적용하기 위해선 클라이언트를 특정 포트로 실행해야 할 필요가 있는데, serve를 이용하면 특정 포트로 클라이언트를 실행할 수 있다. (간혹 이미 사용중인 포트 This port was picked because 4999 is in use. 라고 나오는데, 해당 포트를 사용하고 싶다면 그 포트를 죽이고 다시 생성하면 된다.)
아래 명령어로 가상 클라이언트를 생성하면 된다. 난 5100으로 생성해주었다.

npx serve -l 포트번호 client/

그리고, 서버의 헤드 중 Access-Control-Allow-Origin을 위에서 만든 클라이언트의 Origin으로 특정해준다.

const defaultCorsHeader = {
  // 출처 설정 헤더
  "Access-Control-Allow-Origin": "http://localhost:5100",
  // 허용할 메서드를 설정하는 헤더
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  // 허용할 헤더를 설정하는 헤더
  "Access-Control-Allow-Headers": "Content-Type, Accept",
  // 프리플라이트의 내용을 캐싱 설정: 초 단위
  "Access-Control-Max-Age": 10,
};
  • http://localhost:5100에 접속해서 POST 요청이 문제없이 되었다면 CORS 적용이 잘 된 것이다.
  • 만약 http://localhost:5000(포트번호 5100 이외) 으로 클라이언트를 연 경우 아래와같이 CORS 에러가 난다.

5. 전체 코드

const http = require("http");

const PORT = 4999;

const ip = "localhost";

const server = http.createServer((request, response) => {
  if (request.method === "OPTIONS") {
    // CORS 설정 응답 보내주기
    response.writeHead(200, defaultCorsHeader)
    response.end();
  }
  if (request.method === "POST") {
    let body = '';
    request
      .on("data", (chunk) => {
        body += chunk;
      })
      .on("end", () => {
        response.writeHead(200, defaultCorsHeader)
        if (request.url === "/upper") {
          // 대문자로 응답 보내주기
          response.end(body.toUpperCase());
        } else if (request.url === "/lower") {
          // 소문자로 응답 보내주기
          response.end(body.toLowerCase());
        } else {
          // 나머지 경우 에러처리 
          response.statusCode = 404;
          response.end();
        }
      });
  }
});

server.listen(PORT, ip, () => {
  console.log(`http server listen on ${ip}:${PORT}`);
});

const defaultCorsHeader = {
  // 출처 설정 헤더
  "Access-Control-Allow-Origin": "http://localhost:5100",
  // 허용할 메서드를 설정하는 헤더
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  // 허용할 헤더를 설정하는 헤더
  "Access-Control-Allow-Headers": "Content-Type, Accept",
  // 프리플라이트의 내용을 캐싱 설정: 초 단위
  "Access-Control-Max-Age": 10,
};

서버에 대해 이제야 쪼오금 이해가 가는 것 같다. 아직 깊이 이해하진 못했지만....🥲
이번 주말엔 혼자서 한번 더 만들어 봐야 될 것 같다..!

profile
아무것도 모르는 코린이

0개의 댓글