[Web Server] 기초 | Node.js HTTP 모듈로 간단한 서버 만들기

Eunji Lee·2022년 12월 9일
0

[TIL] Front-end

목록 보기
21/36
post-thumbnail

미리 알아두면 좋은 개념

chunk, buffer, Stream

  • 데이터는 특정 request에 stream이라는 형태로 전송됨
  • 이러한 stream은 chunk를 담고 있는데, chunk는 일종의 데이터 조각임
  • chunk는 일종의 데이터 임시 저장소인 buffer에 저장되었다가 buffer에 chunk가 다 차면 buffer가 의미있는 데이터로 변함
  • 데이터가 흐르는 시냇물(stream)이라고 가정한다면...
    • buffer는 시냇물에서 물(데이터)를 퍼오기 위해 임시로 물(데이터)을 저장하고 있는 물통
    • chunk는 물통에 물을 담기 위해 사용하는 바가지

참고자료
What is chunk in Node.js?
버퍼와 스트림 이해하기

emitter.on(eventName, listener)

  • eventName이라는 이벤트는 emmiter라는 객체에 함수 객체(Listner)를 호출함
    → dom에서 addEventListener와 비슷한 개념
  • response.on("이벤트", 콜백함수), reques.on("이벤트", 콜백함수) 의 형태로 사용함



서버 만들기

  1. http 모듈을 가져오고 http라는 변수에 저장하기
  2. server객체에 HTTP 요청이 올 때마다 createServer에 전달된 함수가 한 번씩 호출되게 하기
    • HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response
      객체를 전달하며 요청 핸들러 함수를 호출함
  3. 요청을 실제로 처리하기 위해 server 객체에서 listen 메소드 호출하기
const http = require('http'); // (1)
const PORT = 4999

const server = http.createServer((request, response) => { // (2)
  // 여기서 작업이 진행됩니다!
});
server.listen(PORT) // (3)



HTTP Message 작성하기

HTTP Messages의 구조

request 바디에 접근하기

  • 필요한 경우: POST나 PUT 메소드처럼 request body가 중요한 경우
  • 핸들러에 전달된 request객체는 ReadableStream인터페이스를 구현하고 있음
    • 이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있음
    • 스트림의 data이벤트와 end이벤트에 이벤트 리스너를 등록해서 데이터 받기

1. error 이벤트

  • 오류가 발생할 경우 대비하기
    • request스트림의 오류가 발생하면 스트림에서 'error'이벤트가 발생하면서 오류를 전달함
    • 이벤트에 리스너가 등록되어 있지 않다면 Node.js 프로그램을 종료시킬 수도 있는 오류가 던져질 수 있음
    • 따라서 request 스트림에 'error'리스너를 추가

2. data 이벤트

  • 데이터를 배열에 수집하기
    • 각 data이벤트에서 발생시킨 청크(2)는 Buffer 이며, 이 청크는 문자열 데이터임
    • 따라서 이 데이터를 배열에 수집함 (3)

3. end 이벤트

  • 수집된 데이터를 이어 붙여서 하나의 문자열로 만들기(4)
    • buffer 배열에 body 배열을 붙이고(concat()) 문자열로 만들기(toString())
let body = [];
  request.on('error', (err) => {
    console.error(err); // (1)
  }).on('data', (chunk) => { // (2)
    body.push(chunk); //(3)
  }).on('end', () => { //(4)
    body = Buffer.concat(body).toString();
    // 여기서 헤더, 메서드, url, 바디를 가지게 되었고
    // 이 요청에 응답하는 데 필요한 어떤 일이라도 할 수 있게 되었습니다.
  })

response 부분 작성하기

1. response 헤더 작성하기

  • response 바디 부분을 작성하기 앞서 상태 코드와 헤더를 설정해야 함
  • response.setHeader(헤더)
    • 헤더 이름의 대소문자 구분 안 함
    • 헤더를 여러 번 설정하면 맨 마지막에 설정한 값을 보냄
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');
  • response.writeHead(상태코드, 헤더): 명시적으로 응답 스트림에 헤더 작성하기
response.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'bacon'
});

2. response 바디 작성하기

1. 오류 처리하기

  • reponse 스트림에서 error 이벤트가 발생될 경우 오류 처리하기
response.on('error', (err) => {
      console.error(err);
});

2. 바디 작성하기

  • response.write() : HTTP 응답 출력 스트림에 정보를 전달하기
response.write('<html>');
response.write('<body>');
response.write('<h1>Hello, World!</h1>');
response.write('</body>');
response.write('</html>');
response.end();
  • response.end(): 스트림에 보낼 데이터의 마지막 비트를 선택적으로 전달
response.end('<html><body><h1>Hello, World!</h1></body></html>');

전체 코드

const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();

    response.on('error', (err) => {
      console.error(err);
    });

    response.writeHead(200, {'Content-Type': 'application/json'})

    const responseBody = { headers, method, url, body };
    response.end(JSON.stringify(responseBody))
  });
}).listen(8080);



Mini-node-server 작성하기

참고사항

  • 바디를 string으로 정의하는 법 : 데이터가 간단할 때
let body = '';
request.on('data', (chunk) => {
	body = body + chunk
 })
  • prefligth에 대한 요청은 OPTIONS로 들어옴 → OPTIONS 메소드일 때 대응을 추가해야함
  • 조건문으로 경우를 다 나눠도 edge case를 방지하기 위해 response.writeHead()response.end()를 적는 게 좋음

전체 코드

const http = require('http');

const PORT = 4999;

const ip = 'localhost'; //기본값으로 localhost가 IP로 지정됨.

const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*', //응답을 보내주고 싶은 client의 url
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

const server = http.createServer((request, response) => {

  if(request.method === 'OPTIONS') {
    response.writeHead(200, defaultCorsHeader);
    response.end();
  };

  if(request.method === 'POST') {
    let body = [];
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString(); //`body`에 전체 요청 바디가 문자열로 담겨있음
    });
    if(request.url === '/upper') {
      request.on('end', ()=>{
        response.writeHead(200, defaultCorsHeader);
        response.end(body.toUpperCase());
      })
    }else if(request.url === '/lower') {
      request.on('end', ()=>{
        response.writeHead(200, defaultCorsHeader);
        response.end(body.toLowerCase());
      })
    }
  }else {
    //에러로 처리, bad request
    response.writeHead(400, defaultCorsHeader);
    response.end();
  }

  console.log(
    `http request method is ${request.method}, url is ${request.url}`
  );

});

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



Express로 리팩토링하기

Node.js에서 사용할 수 있는 프레임워크인 Express를 활용하면 훨씬 간단하게 서버를 구현할 수 있음

Express 알아보러 가기
[Web Server] 기초 | Express

const express = require('express')
const app = express()
const port = 4999

//모든 요청과 응답에 CORS 헤더 붙이기
const cors = require('cors');
app.use(cors());

//POST 요청에 body 구조화하기
const jsonParser = express.json({strict: false});

app.get('/', (req, res) => {
  res.send('This is mini-node-server')
})

app.post('/upper', jsonParser, (req, res) => {
  console.log(req.body);
  res.json(req.body.toUpperCase())
})

app.post('/lower', jsonParser, (req, res) => {
  res.json(req.body.toLowerCase())
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

참고자료
HTTP 트랜잭션 해부

0개의 댓글

관련 채용 정보