오늘은 web architecture에 대해서 공부를 했다. 드디어 서버....
한번도 안해봐서 그런가....어렵네...
그래도 이제 데이터베이스만 공부하면 어떻게든 뭐하나 만들 수 있겠지 하하
얼마전에 interactive developer 유튜브 채널의 개발자 엉아가 만든 웹페이지를 봤는데, 웹개발로도 아트를 할 수 있다고 생각했다.
넘나 멋져부리기...리액트로 할 수 있다고 하는데, 나도 언젠간 그런 웹페이지를 만들 수 있을 것이다.
장인이 될 때까지 한번 뽀개보자
컄캬캬ㅑ캬캬컄
🤪🤪
javascript, css, html 등의 언어로 구성된 파일을 서버로부터 건내받아 chrome V8 엔진으로 해석하여 우리에게 보여주는 역할
그냥 평소에 우리가 보는 웹사이트
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-
- 여기 참고하셈 굳굳
개념 | 설명 |
요청 | 웹 서버에 보내는 모든 요청을 말합니다. |
응답 | 요청을 받은 웹 서버가 처리하는 작업을 말합니다. |
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;
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 여기 👍👍 참조하셈
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;
기본기가 탄탄한 개발자는 CS 기초 지식이 풍부해야 할 것 같다ㅎ
자료구조, 운영체제, 네트워크, 알고리즘, 데이터베이스는 꼭 거쳐야 한다는 것을 몸으로 느끼고 있다.
공부하면서 무엇을 공부해야 될지 길이 보이는 것은 매우 값진 일이다.
나의 시야가 조금씩 넓어지고 있다는 것!
한번에 터득하면 개꿀딱이지만, 꾸준히 정리하면서 보고보고 또 보는 개린이가 되어보자!