DevLog__[nodeJS: Web Architecture, HTTP, Express]

Jaewon Lee·2020년 11월 16일
0

Node.js

목록 보기
2/3
post-thumbnail

# Intro

오늘은 web architecture에 대해서 공부를 했다. 드디어 서버....

한번도 안해봐서 그런가....어렵네...

그래도 이제 데이터베이스만 공부하면 어떻게든 뭐하나 만들 수 있겠지 하하

얼마전에 interactive developer 유튜브 채널의 개발자 엉아가 만든 웹페이지를 봤는데, 웹개발로도 아트를 할 수 있다고 생각했다.

넘나 멋져부리기...리액트로 할 수 있다고 하는데, 나도 언젠간 그런 웹페이지를 만들 수 있을 것이다.

장인이 될 때까지 한번 뽀개보자

컄캬캬ㅑ캬캬컄

🤪🤪


1. Web Architecture


1. Browser

  • javascript, css, html 등의 언어로 구성된 파일을 서버로부터 건내받아 chrome V8 엔진으로 해석하여 우리에게 보여주는 역할

  • 그냥 평소에 우리가 보는 웹사이트


2. Server

  • 클라이언트에서 받은 요청을 처리해 주는 공간
  • 네트워크를 통해서 정보나 서비스같은 리소스를 제공한다.

3. HTTP(Hypertext Transfer Protocol)

  • TCP와 IP주소 기반으로 돌아가는 Protocol. (밑에서 설명)
  • 웹은 수많은 요청과 응답으로 이루어져 있고, 이를 통해 데이터를 주고 받음.
  • HTTP는 데이터를 전송하기 위한 상호간의 규칙이다.
  • 규칙을 정해두었기 때문에, 모든 프로그램이 이 규칙에 맞춰 개발해서 서로 정보를 교환할 수 있게 된 것이다.

HTTP 특징

  • 단방향성
    🎈 요청과 응답은 1set
    🎈 클라이언트에서 서버 방향으로만 요청을 보낼 수 있다.
  • 비연결성
    🎈 요청과 응답이 끝나면 연결을 바로 닫는다.
  • 무상태성
    🎈 비연결성으로 인해 클라이언트 사이드, 서버 사이드 어디에서도 형태를 가지지 않는다.

HTTP 메소드

HTTP 캐시

HTTP 메세지 해석

1) Request

  • HTTP 메소드 : 클라이언트가 수행하고자 하는 동작을 정의한 GET, POST, OPTIONS, DELETE, PUT을 나타낸다.
  • PATH : 가져오려는 리소스의 경로
  • HTTP Version
  • HEADER : 서버에 대한 추가 정보를 전달 (host에 대한 내용은 밑에서 설명)
  • POST와 같은 몇 가지 메서드를 위한 BODY

2) Response

  • HTTP Version
  • Status Code : 요청의 성공 여부
  • Status Message : 상태 코드의 짧은 설명을 나타내는 메시지
  • HEADER : 요청과 비슷함. (ETag는 쿠키관련, Server는 아파치서버...등 읽을 줄 알아야함.)
  • 선택 사항으로, 가져온 리소스가 포함되는 BODY

http status code

https://velog.io/@honeysuckle/HTTP-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-HTTP-status-code-

  • 여기 참고하셈 굳굳

4. API(Application Programming Interface)

  • API는 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다.
  • 나는 API를 어떤 기능을 구현해 놓은 모듈 혹은 라이브러리라고 판단했다!



2. HTTP 모듈로 서버 구축하기


  • 각각의 쓰임새를 알고, 사용하는 틀을 이해하면 서버를 구축하는 것은 어렵지 않다!
  • 크게 server 객체, request 객체, response객체에 대해서 살펴보자
  • 꼭 알아야 할 개념

    개념 설명
    요청 웹 서버에 보내는 모든 요청을 말합니다.
    응답 요청을 받은 웹 서버가 처리하는 작업을 말합니다.
    http 모듈 http웹 서버와 관련된 모든 기능을 담은 모듈입니다.
    server 객체 웹 서버를 생성할 때 사용하는 객체입니다.
    request 객체 createServer 메소드의 첫 번째 매개변수로 전달되는 객체입니다.
    response 객체 createServer 메소드의 두 번째 매개변수로 전달되는 객체입니다.

Server 객체

  • createServer 메소드를 이용해서 생성한다.
  • 서버로 오는 HTTP 요청마다 createServer의 콜백 함수가 한 번씩 호출된다.
  • createServer가 반환한 Server 객체는 EventEmitter이다.
  • 나는 EventEmitter를 이벤트 리스너와 유사하게 생각했다. (틀리면 말해주세용ㅠㅅㅠ..)

Server 객체 메소드

메소드 설명
listen(port, callback) 서버를 실행하고, 요청을 대기하는 상태로 만든다.
close(callback) 서버를 종료한다.


Server 생성하기

// http모듈을 추출합니다.
const http = require('http')
// 웹 서버를 생성합니다.
const server = http.createServer()
// 웹 서버를 3001 포트로 실행합니다.
server.listen(3001, function(){
  console.log('3001번 포트로 서버가 실행되었습니다.')
})

Server 객체 이벤트

이벤트 설명
request 클라이언트가 요청할 때 발생하는 이벤트
connection 클라이언트가 접속할 때 발생하는 이벤트
close 서버가 종료될 때 발생하는 이벤트
checkContinue 클라이언트가 지속적인 연결을 하고 있을 때 발생하는 이벤트
upgrade 클라이언트가 http 업그레이드를 요청할 때 발생하는 이벤트
clientError 클라이언트에서 오류가 발생할 때 발생하는 이벤트


Server 이벤트핸들러 예제

// http모듈을 추출합니다.
const http = require('http')
// 웹 서버를 생성합니다.
const server = http.createServer()
//server 객체에 이벤트를 연결합니다.
server.on('request', function (code) {
  console.log('Request Event')
})
server.on('connection', function (code) {
  console.log('Connection Event')
})
// 웹 서버를 3001 포트로 실행합니다.
server.listen(3001, function(){
  console.log('3001번 포트로 서버가 실행되었습니다.')
})
  • createServer()메서드의 매개변수로 request이벤트를 on메서드를 사용할 필요 없이 정의할 수 있습니다.
// 웹 서버를 생성하고 실행합니다.
require('http').createServer(function(req, res){ // request, response
  res.writeHead(200, { 'Content-Type': 'text/html' })
  res.end('<h1>Hello World...</h1>')
})

Request 객체의 Property

속성 설명
method 클라이언트의 요청방식
url 클라이언트가 요청한 URL을 나타낸다.
headers 요청 메시지 헤더를 나타낸다.
trailers 요청 메시지 트레일러를 나타낸다.
httpVersion http 프로토콜 버전을 나타낸다.

Response 객체 메소드

메소드 설명
writeHead(statusCode, statusMessage, headers) 응답 헤더를 작성한다.
end(data, encoding, callback) 응답 본문을 작성한다.


Response Header의 content type 종류

Content Type 설명
text/plain 기본적인 텍스트
text/html HTML 문서
text/css CSS 문서
text/xml XML 문서
image/jpeg JPG/JPEG 파일
image/png PNG파일
video/mpeg MPEG 비디오 파일
audio/mp3 MP3 파일

http 모듈로 서버 구축한 코드

  • index.html (클라이언트 사이드)
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>chatterbox</title>
  <link rel="stylesheet" href="styles/styles.css" />
</head>
<body>
  <div id="main">
    <img src="../../../../Downloads/line_Shopping_mall_main_desktop.jpg">
    <span><b>Room : </b></span>
    <select>
      <option value='홍인자'>홍인자</option>
      <option value='양재동마라토너'>양재동마라토너</option>
      <option value='수원역당근마켓'>수원역당근마켓</option>
      <option value='대전공주모임'>대전공주모임</option>
    </select>
    <br>
    <br>
    <br>
    <input type="text" id="username" placeholder="이름을 입력해주세요.">
    <br>
    <br>
    <input type="text" id="text" placeholder="메세지를 남겨주세요.">
    <br>
    <br>
    <br>
    <button type="submit">submit</button>
    <button type="reset">reset</button>
  </div>
  <!-- #chats 엘리먼트의 id는 바꾸지 마세요 -->
  <div id="chats">
    <!-- 자유롭게 HTML을 작성하세요 -->
  </div>
  <script src="scripts/app.js"></script>
</body>
</html>

  • app.js (클라이언트 사이드, 웹에서 발생하는 이벤트를 핸들링하는 js)
let commentWindow = document.querySelector('#chats');
let subBtn = document.getElementsByTagName('button')[0]
subBtn.addEventListener('click', function () {
  let message = {
    username: document.querySelector('#username').value,
    text: document.querySelector('#text').value,
    roomname: document.getElementsByTagName('select')[0].value
  };
  app.send(message);
});
const app = {
  server: 'http://localhost:3000/messages',
  init: function () {
    this.fetch()
      .then(json => {
        for (let item of json.results.reverse()) {
          this.renderMessage(item)
        }
      })
  },
  fetch: function () {
    return fetch(this.server)
      .then(response => response.json());
  },
  send: function (message) {
    fetch(app.server, {
      method: 'POST',
      body: JSON.stringify(message),
      headers: {
        "Content-Type": "application/json",
      }
    })
      .then(response => response.json())
      .then(json => {
          this.renderMessage(json)
      })
  },
  clearMessages: function () {
    let chats = document.querySelector('#chats')
    while (chats.children.length !== 0) {
      chats.removeChild(chats.lastChild)
    }
  },
  renderMessage: function (message) {
    let chats = document.querySelector("#chats")
    let main = document.createElement('div')
    main.className = 'chat'
    let user = document.createElement('div')
    user.className = 'username'
    user.textContent = message.username
    let msg = document.createElement('div')
    msg.className = 'message'
    msg.textContent = message.text
    let room = document.createElement('div')
    room.className = 'roomname'
    room.textContent = message.roomname;
    main.append(user, msg, room)
    chats.prepend(main)
  }
};
app.init();

  • basic-server.js (서버 사이드, http 모듈로 구축한 서버 뼈대)
const http = require("http");
const requestHandler = require("./request-handler")
//
const port = 3000;
//
const ip = "127.0.0.1";
//
const server = http.createServer(requestHandler);
//
// console.log("Listening on http://" + ip + ":" + port);
//
server.listen(port, ip);
//
module.exports = server;

  • request-handler.js (서버 사이드, 클라이언트로부터 들어온 요청을 핸들링하는 js)
const fs = require('fs');
//
const requestHandler = function (request, response) {
  const headers = defaultCorsHeaders;
  headers["Content-Type"] = "text/plain";
  fs.readFile('data.json', 'utf8', (err, data) => {
    if (err) throw err;
    let readData = JSON.parse(data);
    if (request.method === 'OPTIONS') {
      response.writeHead(200, headers)
      response.end()
    }
    else if (request.method === 'POST' && request.url === '/messages') {
      let body = []
      request.on('data', (chunk) => {
        body.push(chunk);
      }).on('end', () => {
        body = Buffer.concat(body).toString();
        readData.results.unshift(JSON.parse(body))
        fs.writeFile('data.json', JSON.stringify(readData), 'utf8', (err) => {
          if(err) throw err;
        });
        response.writeHead(201, headers);
        response.end(body);
      })
    }
    else if (request.method === 'GET' && request.url === '/messages') {
      response.writeHead(200, headers);
      response.end(JSON.stringify(readData))
    }
    else {
      response.statusCode = 404;
      response.end();
    }
  });
};
//
const defaultCorsHeaders = {
  "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 // Seconds.
};
//
module.exports = requestHandler;


3. express로 서버 구축하기


1. express에서 라우팅과 미들웨어를 이해하자

Routing

  • 라우팅은 클라이언트로부터 들어온 요청을 어떻게 처리해주어야 할지에 대해서 안내하는 길잡이다.
  • 요청의 방식(GET, POST, PUT, DELETE, OPTIONS)에 따라서 목적에 맞는 응답을 보내주는 역할이다.
  • let express = require('express') : express 모듈 사용하겠다는 소리
  • let 변수 = express() : 지금부터 '변수'를 서버 객체로 사용하겠다는 소리
  • let router = express.Router() : 라우터 미들웨어 사용하겠다는 소리
    • router.OPTIONS / router.GET / router.POST / router.PUT / router.DELETE
    • router.Method(PATH, 기타옵션(cors, jsonPaser 등..), Handler 콜백함수)

Middle Ware

  • 미들웨어 : 구조 내에서 중간 처리를 위한 함수
  • express 프레임워크에서 사용할 수 있는 중간 처리 목적의 소프트웨어 : 기본적인 express 구조 내에서 처리 목적으로 사용
  • 요청에 대한 응답을 완수하기 전까지 중간중간 다양한 일을 처리할 수 있음
  • 미들웨어 함수 생명주기 : request - response 응답을 주기로 종료
  • 미들웨어 함수 우선순위 : 먼저 로드되는 미들웨어 함수가 먼저 실행됨(코드 순서 중요)
  • 미들웨어를 사용하려면 app(express로 할당한 변수).use(미들웨어)를 응답전에 해줘야함.
  • https://jinbroing.tistory.com/126 여기 👍👍 참조하셈

2. 서버를 구축하는 뼈대를 한번에 작성하고, 하나씩 뜯어 보도록 하자!

Express로 서버 구축한 코드

  • basic-server.js (서버 사이드, express로 구축한 서버 뼈대)
const requestHandler = require("./request-handler")
const express = require('express')
// 👇서버객체 생성👇
const app = express()                  
const port = 3000
// 👇requestHandler를 미들웨어로 사용👇
app.use(requestHandler);               
// 👇준비된 port로부터 서버 실행시키고, 대기👇
let server = app.listen(port)              
//
// 👇server라는 것을 다른 파일에서도 사용할 수 있게 한다.👇
module.exports = server;

  • request-handler.js (서버 사이드, 클라이언트로부터 들어온 요청을 핸들링하는 js)
const fs = require('fs');
const express = require('express');
// 👇cors, body-parser와 같은 미들웨어도 require로 사용할 수 있다. (npm 설치해야함)👇
const cors = require('cors')
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()
//
const app = express();
//
// 아래처럼 cors를 전체에게 허용하다가 보안이 빵꾸날 수 있음.
// app.use(cors())
//
const defaultCorsHeaders = {
  "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 // Seconds.
};
//
// 👇요청으로 GET이 오면 처리(라우팅)👇
app.get('/messages',cors(defaultCorsHeaders), (req, res) => {
  fs.readFile('data.json', 'utf8', (err, data) => {
    res.status(200).send(data)
  })
})
// 👇요청으로 OPTIONS이 오면 처리(라우팅)👇
app.options('/messages', cors(defaultCorsHeaders), (req, res) => {
  res.status(200).send('Success pre-flight request')
})
// 👇요청으로 POST가 오면 처리(라우팅)👇
app.post('/messages',cors(defaultCorsHeaders), jsonParser, (req, res) => {
  fs.readFile('data.json', 'utf8', (err, data) => {
    if(err) throw err;
    let readData = JSON.parse(data);
    readData.results.unshift(req.body);
    fs.writeFile('data.json', JSON.stringify(readData), 'utf8', (err) => {
      if(err) throw err;
    })
    res.status(201).send(JSON.stringify(req.body));
  })
})
//
module.exports = app;


4. 정리


1. 서버를 구축하는 기본적인 틀이 있다. 여러번 반복하여 실습해 보즈아

2. 클라이언트-서버 통신은 여러개의 네트워크 layer를 거치면서 데이터가 전송된다. OSI 7 layer와 각 계층에서의 데이터 전달 방법, 프로토콜(TCP/IP, UDP)에 대한 내용을 학습하자



# Work Off


기본기가 탄탄한 개발자는 CS 기초 지식이 풍부해야 할 것 같다ㅎ

자료구조, 운영체제, 네트워크, 알고리즘, 데이터베이스는 꼭 거쳐야 한다는 것을 몸으로 느끼고 있다.

공부하면서 무엇을 공부해야 될지 길이 보이는 것은 매우 값진 일이다.

나의 시야가 조금씩 넓어지고 있다는 것!

한번에 터득하면 개꿀딱이지만, 꾸준히 정리하면서 보고보고 또 보는 개린이가 되어보자!

기본기가 탄탄한 풀스택 개발자가 되는 그날까지 🔥🔥🔥

profile
Communication : any

0개의 댓글