[20/11/13] - TIL โŽฎ Mini Node Server Sprint

NOWANDHEREยท2020๋…„ 11์›” 13์ผ
3

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
47/53
post-thumbnail

๐ŸŒป Mini Node Server Sprint


์ด๋ฒˆ์—๋Š” node.js์˜ http ๋ชจ๋“ˆ์„ ์ด์šฉํ•˜์—ฌ ์›น ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ดค๋‹ค. ์•ž์„œ ํŒŒ์ผ์„ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๊ธฐ ์œ„ํ•œ fs ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ด๋ณธ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ http ๋ชจ๋“ˆ์€ HTTP ์š”์ฒญ, ์‘๋‹ต์„ ๋‹ค๋ฃฌ๋‹ค. HTTP ์ง€์‹์ด ์„ ํ–‰๋˜์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋จผ์ € HTTP ํŠธ๋žœ์žญ์…˜ ํ•ด๋ถ€๋ผ๋Š” ๊ณต์‹ ๊ฐ€์ด๋“œ ๋ฌธ์„œ๋ฅผ ๊ณต๋ถ€ํ–ˆ๋‹ค.


HTTP ํŠธ๋žœ์žญ์…˜ ํ•ด๋ถ€


node.js HTTP ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ์ด ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ์ดํ•ดํ•ด๋ณด์ž.

1. ์„œ๋ฒ„ ์ƒ์„ฑ

๋ชจ๋“  node ์›น ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์›น ์„œ๋ฒ„ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์ด ๋•Œ createServer๋ฅผ ์ด์šฉํ•œ๋‹ค.

const http = require('http');

const server = http.createServer((request, response) => {
  // ์—ฌ๊ธฐ์„œ ์ž‘์—…์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค!
});

์ด ์„œ๋ฒ„๋กœ ์˜ค๋Š” HTTP ์š”์ฒญ๋งˆ๋‹ค createServer์— ์ „๋‹ฌ๋œ ํ•จ์ˆ˜๊ฐ€ ํ•œ ๋ฒˆ์”ฉ ํ˜ธ์ถœ๋œ๋‹ค. HTTP ์š”์ฒญ์ด ์„œ๋ฒ„์— ์˜ค๋ฉด node๊ฐ€ ํŠธ๋žœ์žญ์…˜์„ ๋‹ค๋ฃจ๋ ค๊ณ  request์™€ response ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜๋ฉฐ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์š”์ฒญ์„ ์‹ค์ œ๋กœ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด 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๋ผ๋Š” ์ „์šฉ ๊ฐ์ฒด๊ฐ€ ์žˆ๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ—ค๋”๋ฅผ ์„ค์ •ํ–ˆ๋Š”์ง€์— ๊ด€๊ณ„์—†์ด ๋ชจ๋“  ํ—ค๋”๋Š” ์†Œ๋ฌธ์ž๋กœ๋งŒ ํ‘œํ˜„๋œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์ž. ์ด๋Š” ์–ด๋–ค ๋ชฉ์ ์ด๋“  ํ—ค๋”๋ฅผ ํŒŒ์‹ฑํ•˜๋Š” ์ž‘์—…์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.


3. ์š”์ฒญ ๋ฐ”๋””

POST๋‚˜ PUT ์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ ๋ฐ”๋””๋Š” ์ค‘์š”ํ•  ๊ฒƒ์ด๋‹ค. ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋œ request ๊ฐ์ฒด๋Š” ReadableStream ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด ์ŠคํŠธ๋ฆผ์˜ 'data'์™€ 'end' ์ด๋ฒคํŠธ์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋“ฑ๋กํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

๊ฐ 'data' ์ด๋ฒคํŠธ์—์„œ ๋ฐœ์ƒ์‹œํ‚จ ์ฒญํฌ๋Š” Buffer์ด๋‹ค. ์ด ์ฒญํฌ๋Š” ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์—ด์— ๋‹ด๊ณ , 'end' ์ด๋ฒคํŠธ์—์„œ ์ด์–ด ๋ถ™์ธ ๋‹ค์Œ ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹๋‹ค.

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // ์—ฌ๊ธฐ์„œ `body`์— ์ „์ฒด ์š”์ฒญ ๋ฐ”๋””๊ฐ€ ๋ฌธ์ž์—ด๋กœ ๋‹ด๊ฒจ์žˆ์Šต๋‹ˆ๋‹ค.
});

Sprint ๊ตฌํ˜„ํ•˜๊ธฐ


์ด๋ฒˆ์— ๋งŒ๋“ค์–ด์•ผํ•  ์›น ์„œ๋ฒ„์˜ ๊ธฐ๋Šฅ์€ ๋งค์šฐ ๋‹จ์ˆœํ–ˆ๋‹ค. ๋ฒ„ํŠผ์„ ํด๋ฆญํ•จ์— ๋”ฐ๋ผ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉฐ, ๊ฐ๊ฐ ๋ณด๋‚ธ ๋‹จ์–ด๋ฅผ ์†Œ๋ฌธ์ž ๋˜๋Š” ๋Œ€๋ฌธ์ž๋กœ ๋ฐ”๊พธ๋ฉด ๋˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

URL์ด /lower์ด๊ณ , POST ์š”์ฒญ์ด ์™”๋‹ค๋ฉด ์„œ๋ฒ„๋Š” ๋ฌธ์ž์—ด์„ ์†Œ๋ฌธ์ž๋กœ ๋งŒ๋“ค์–ด ์‘๋‹ตํ•ด์•ผ ํ–ˆ๊ณ , URL์ด /upper์ธ POST ์š”์ฒญ์—๋Š” ๋ฌธ์ž์—ด์„ ๋Œ€๋ฌธ์ž๋กœ ๋งŒ๋“ค์–ด ์‘๋‹ตํ•ด์•ผ ํ–ˆ๋‹ค. ๋˜ํ•œ, CORS ๊ด€๋ จ ํ—ค๋”๋ฅผ OPTIONS ์‘๋‹ต์— ์ ์šฉํ•ด์•ผ ํ–ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ๋จผ์ € Method๊ฐ€ POST์ธ์ง€ OPTIONS์ธ์ง€ ๋ถ„๊ธฐํ•˜๊ณ , POST ์š”์ฒญ์ด ์™”๋‹ค๋ฉด URL์„ ํ™•์ธํ•˜๊ณ  ์•Œ๋งž์€ ์š”์ฒญ์„ ๋ณด๋‚ด์ฃผ๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

const http = require('http');
const PORT = 5000;
const ip = 'localhost';

const server = http.createServer((request, response) => {
  if (request.method === 'OPTIONS') {
    // ํด๋ผ์ด์–ธํŠธ์˜ preflight request์— ๋Œ€ํ•œ ์‘๋‹ต์„ ๋Œ๋ ค์ค€๋‹ค
    response.writeHead(200, defaultCorsHeader);
    response.end();
  }
  if (request.method === 'POST') {
    let body = [];
    request
      .on('data', (chunk) => {
        // ์œ„์˜ HTTP ํŠธ๋žœ์žญ์…˜ ํ•ด๋ถ€ ๋ฌธ์„œ์—์„œ ๊ณต๋ถ€ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ,
        // body ๋ฐฐ์—ด์— chunk๋ฅผ ๋‹ด๊ณ 
        body.push(chunk);
      })
      .on('end', () => {
        // end ์ด๋ฒคํŠธ์—์„œ ์ด์–ด ๋ถ™์ด๊ณ  ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“ ๋‹ค
        body = Buffer.concat(body).toString();
        response.writeHead(201, defaultCorsHeader);
        if (request.url === '/lower') {
          response.end(body.toLowerCase());
        } else if (request.url === '/upper') {
          response.end(body.toUpperCase());
        } else {
          response.writeHead(404, defaultCorsHeader);
          response.end();
        }
      });
  }
});

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

// ์‘๋‹ต ํ—ค๋”
const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10,
};

reference code

์ด ์ฝ”๋“œ๋Š” ์• ์ดˆ์— ๋ฌธ์ž์—ด ๋ณ€์ˆ˜ data๋ฅผ ์„ ์–ธํ•˜์—ฌ 'data' ์ด๋ฒคํŠธ์—์„œ chunk๋ฅผ ๋”ํ•ด์ฃผ๊ณ , 'end' ์ด๋ฒคํŠธ์—์„œ ๊ทธ data๋ฅผ ์†Œ๋ฌธ์ž ํ˜น์€ ๋Œ€๋ฌธ์ž๋กœ ๋ฐ”๊พธ์–ด ์‘๋‹ตํ•˜๋Š” ํ˜•ํƒœ์˜ ์ฝ”๋“œ์ด๋‹ค. ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•œ ๋ชจ์Šต์ด๋‹ค.

const http = require('http');
const PORT = 5000;
const ip = 'localhost';

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    if (req.url === '/lower') {
      let data = '';
      req.on('data', chunk => {
        data = data + chunk;
      });
      req.on('end', () => {
        data = data.toLowerCase();
        res.writeHead(201, defaultCorsHeader);
        res.end(data);
      });
    } else if (req.url === '/upper') {
      let data = '';
      req.on('data', chunk => {
        data = data + chunk;
      });
      req.on('end', () => {
        data = data.toUpperCase();
        res.writeHead(201, defaultCorsHeader);
        res.end(data);
      });
    } else {
      res.writeHead(404, defaultCorsHeader);
      res.end();
    }
  }
  if (req.method === 'OPTIONS') {
    res.writeHead(200, defaultCorsHeader);
    res.end();
  }
});

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

const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

๊ฒฐ๊ณผ์™€ ๋Š๋‚€ ์ 


์ž˜ ๋™์ž‘ํ•˜๋Š” ๋ชจ์Šต์ด๋‹ค. ์ฒ˜์Œ์œผ๋กœ ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋Š” ์Šคํ”„๋ฆฐํŠธ์˜€๋‹ค. ๊ฐ‘์ž๊ธฐ ๊ณต๋ถ€์˜ ๋‚œ์ด๋„๊ฐ€ ๊ธ‰๊ฒฉํ•˜๊ฒŒ ์–ด๋ ค์›Œ์ง„ ๋Š๋‚Œ์ด์—ˆ๋‹ค. HTTP๋Š” ๋ฌด์—‡์ธ์ง€, API๋Š” ๋ฌด์—‡์ธ์ง€, CORS๋Š” ๋˜ ๋ญ”์ง€... ํ•˜๋‚˜์”ฉ ์ดํ•ดํ•˜๋Š”๋ฐ๋„ ๋ฒ…์ฐฌ ๋Š๋‚Œ์ด์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์—ญ์‹œ ๊ณต๋ถ€ํ•˜๋ฉด ๋œ๋‹ค. ์ฐจ๊ทผ์ฐจ๊ทผ ์•Œ์•„๊ฐ€๊ณ  ๊ฒ€์ƒ‰ํ•ด๋ณด๊ณ , ๊ณต์‹ ๋ฌธ์„œ์˜ ๋‚ด์šฉ๋“ค์„ ๊ณ„์† ๋”ฐ๋ผํ•ด๋ณด๊ณ  ์ฝ˜์†”์— ์ถœ๋ ฅํ•ด๋ณด๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ์˜ HTTP ์š”์ฒญ๊ณผ ์„œ๋ฒ„์˜ HTTP ์‘๋‹ต์ด ์–ด๋–ค์‹์œผ๋กœ ์ด๋ฃจ์–ด์กŒ๊ณ , ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ ์ง€ ๊ฐ์„ ์žก์„ ์ˆ˜ ์žˆ๋Š” ์Šคํ”„๋ฆฐํŠธ์˜€๋‹ค.

๋‚ด๊ฐ€ ์ง์ ‘ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๋‹ค๋‹ˆ... ๋„ˆ๋ฌด ์žฌ๋ฐŒ์—ˆ๋‹ค. ์•„์ง ๋ชจ๋ฅด๋Š”๊ฒŒ ์ •๋ง ๋งŽ๋‹ค. ํ• ๊ฒŒ ๋งŽ๋‹ค๊ณ  ๋Š๊ปด์ง€๋Š”๊ฑด ๋‚ด๊ฐ€ ๋ฌด์—‡์„ ํ•ด์•ผํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ์•Œ๊ณ  ์žˆ๋‹ค๋Š” ๋œป์ด๋ž€๋‹ค. ์ด ๋ชจ๋“  ๊ณผ์ •์ด ๊ณต๋ถ€๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ , ๊ทธ๋ ‡๊ฒŒ ๋Š๊ปด์ง„๋‹ค.

๋‹ค์Œ์ฃผ๋ถ€ํ„ฐ๋Š” ChatterBox Client ์Šคํ”„๋ฆฐํŠธ์˜ ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณธ๋‹ค. ๋ฒŒ์จ๋ถ€ํ„ฐ ๊ธฐ๋Œ€๊ฐ€ ๋œ๋‹ค.


Reference




๋ฐฐ์šฐ๋Š” ๋‹จ๊ณ„๋ผ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€ ๋‹ฌ์•„์ฃผ์‹œ๋ฉด ์ˆ˜์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค :)

profile
๐ŸŒป

0๊ฐœ์˜ ๋Œ“๊ธ€