on
Web Server - 서버 만들기 연습
Web Server - 서버 만들기 연습
Mini node Server 구축하기
Node.js 공식 문서의 HTTP 트랜잭션 해부 와 express 공식문서 시작하기 를 참고하여 작성하면 쉽게 만들 수 있다
HTTP 트랜잭션 해부
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction
express 공식문서 시작하기
https://expressjs.com/ko/starter/installing.html
1. 서버 생성을 위한 빈 폴더 생성 mkdir 생성할폴더이름 cd 생성할폴더이름 2. package.json 파일 생성하기 npm init // default 설정값으로 만들면 된다(enter~) 3. express 설치 npm install express --save // --save 옵션을 통해 package.json에 종속 항목 목록에 추가(dependencies 에)
이렇게 하면 기본적인 뼈대가 될 폴더와 파일들이 생성된다.
만약 내가 이 폴더와 파일들의 구조를 보고 싶다면?
-> tree를 설치하면 터미널에서 구조 파악하는데 좋다
* tree 설치 및 버전 확인 sudo apt install tree tree --version // tree 설치 후 버전이 정상적으로 나오면 정상적으로 설치된 것 tree --help 를 통해 각종 옵션을 써서 원하는 구조를 출력 가능 ex) tree -df // 어느 경로에서 tree를 쓰느냐에 따라서 보이는 구조가 다르므로 이를 이용해 구조 출력해보자
웹 서버 생성의 구조 파악
기본적인 Node.js 명령어로 작성하는 것도 좋지만, express를 이용하면 다양한 미들웨어를 통해 좀더 직관적이면서
효율적인 코딩이 가능하므로, express를 이용하여 작성해 보자
기본적으로 웹 서버를 생성할 때, 순서를 생각해 보자
1. 웹 서버 객체를 생성하고 서버 생성 2. 메서드, url, 헤더 생성하기 3. 요청바디 만들기 4. 응답 헤더 설정 및 HTTP 상태 코드 부여 5. 응답 바디 만들기 및 전송
기본적인 서버 생성 문법은 다음과 같다
// app.js 파일을 생성 후 코드 작성을 한 예시 const express = require('express') //express 모듈을 가져옴 const app = express() // express 인스턴스를 생성 const port = 3000 // 사용할 포트 app.get('/', (req, res) => { //get 메서드를 사용, 루트(/) 라우트 <- 요청 res.send('Hello World!') // <- 응답 }) app.listen(port, () => { //서버를 listen 상태로 만들면서 포트와 매칭 console.log(`Example app listening at http://localhost:${port}`) })
기본 node.js 로 서버 구현
만약, express를 사용하지 않고 node.js로 서버 구현을 한다고 생각해 보자
먼저 메소드 부분에 대한 수도코드를 생각해 보면,
// 프리플라이트 처리 // if(메소드가 OPTIONS 이면){ // CORS 설정을 돌려줘야 한다 // } //upper, lower의 엔드포인트를 구현해야함! // 이거만 만들고 나머지는 error처리하면 됨 // if(메소드가 POST 이고, url이 /upper 면){ // 대문자로 응답해줘야 한다 // } // else if(메소드가 POST dlrh url이 /lower 면){ // 소문자로 응답해줘야 한다 // } // else{ // 에러로 처리. bad request 처리 // }
이런 구조를 내부에서 짜야 한다
이 부분을 생각하여 코드를 작성하면 다음과 같다(server.js)
// server/basic-server.js 파일 const http = require('http'); const PORT = 5000; const ip = 'localhost'; const server = http.createServer((request, response) => { const {method, url, headers } = request; // 구조 분해 할당 request.on('error', (err) => { console.log(err); response.writeHead(404, defaultCorsHeader); response.end("에러 입니다") }); response.on('error', (err) => { console.log(err); }) if(method==='OPTIONS'){ console.log("옵션 메서드 입니다") response.writeHead(200, defaultCorsHeader); // 200의 상태 코드(status code)와 defaultCorsHeader 라는 헤더 설정을 돌려줌 response.end(); //이 상태로 끝냄 응답 바디가 필요한건 아니니깐 } if(method==='POST' && url==='/upper'){ let body=[]; // 빈 배열에 넣어 주는 방식을 이용 request.on('data', (chunk)=> { //요청에 data가 왔을때, callback함수를 실행해라~ //이벤트리스너 생각 body.push(chunk); }).on('end', ()=>{ body=Buffer.concat(body).toString(); //body.toString() 과 똑같음 // 여기에 요청바디가 문자열로 담겨있다. response.writeHead(201, defaultCorsHeader); console.log(body); response.end(body.toUpperCase()); }) } else if(method==='POST' && url==='/lower'){ let body=[]; request.on('data', (chunk)=> { body.push(chunk); // console.log(body); }).on('end', ()=>{ body=Buffer.concat(body).toString(); response.writeHead(201, defaultCorsHeader); console.log(body); response.end(body.toLowerCase()); }) } else{ // 배드 리퀘스트 처리 response.writeHead(400, defaultCorsHeader); response.end("잘못된 메시지입니다"); } console.log( `http request method is ${method}, url is ${url}` // 구조 분해 할당을 했으므로 request.method -> method로 쓰면됨 ); response.writeHead(200, defaultCorsHeader); response.end('hello mini-server sprints'); }); server.listen(PORT, ip, () => { console.log(`http server listen on ${ip}:${PORT}`); }); // 만약에 cors 미들웨어를 사용하지 않은 경우 아래서처럼 디폴트 헤더를 만들고 options 메서드도 구현해야 할 것이다 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 //응답 헤더는 결과를 브라우저 캐시에 캐시 할 수있는 기간을 나타냅니다. };
참고: 브라우저를 사용한 프리플라이트 캐싱
프리 플라이트 캐시는 다른 캐싱 메커니즘과 유사하게 작동합니다.
브라우저가 프리 플라이트 요청을 할 때마다 먼저 프리 플라이트 캐시에서 해당 요청에 대한 응답이 있는지 확인합니다.
브라우저가 응답을 찾으면 서버에 Preflight 요청을 보내지 않고 대신 캐시 된 응답을 사용합니다.
브라우저는 프리 플라이트 캐시에 응답이없는 경우에만 프리 플라이트 요청을 보냅니다.
express를 활용한 서버 구현(리팩토링)
그런데 위의 코드는 너무 복잡하고 코드도 간결하지 않다.
아래는 이러한 부분을 express를 사용하여 basic-server.js 파일을 리팩토링한 것이다
// server/basic-server.js 파일 const http = require('http'); const express = require('express'); const cors=require('cors'); const PORT = 5000; const ip = 'localhost'; const server = express(); server.use(express.json({strict: false})); // primitive data type 도 parsing 해주도록 설정 //! 해당 부분에 대한 설정 ({strict: false}) 을 해주지 않으면 데이터 내용이 보이지 않는 증상이 발생할 수 있다! //! 즉 .body 부분의 내용이 {} [] 이런식으로 껍데기만 보일 수 있다. json 내용을 파싱하려면 반드시 false 설정해줘야함 //! 만약 json 형식이 아닌 일반 텍스트(text/plain)를 요청, 응답 받는 경우 //! express.text() 메서드를 사용하자 server.use(cors()); // 모든 요청에 대해 CORS 를 적용 server.get('/', (req, res) => { console.log("get 메서드 실행됨") res.send('hello mini-server sprints'); }) server.post('/upper', (req, res)=> { console.log('upper 테스트'); res.json((req.body).toUpperCase()); }) server.post('/lower', (req, res) => { console.log('lower 테스트'); res.json((req.body).toLowerCase()); }) server.listen(PORT, ip, ()=> { console.log(`http server listen on ${ip}:${PORT}`); })
다음은 클라이언트 부분의 코드이다(App.js)
// 해당 부분은 서버 부분이 아닌 클라이언트 부분이므로 기본적인 node.js 문법으로 작성 // client/App.js class App { init() { document .querySelector('#to-upper-case') .addEventListener('click', this.toUpperCase.bind(this)); document .querySelector('#to-lower-case') .addEventListener('click', this.toLowerCase.bind(this)); } post(path, body) { fetch(`http://localhost:5000/${path}`, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json', // 'Content-Type': 'application/xml' //컨텐츠 타입이나 // 'Accept': 'application/json' //Accept 부분을 바꾸어 여러 테스트를 해봐도 된다 } }) .then(res => res.json()) .then(res => { this.render(res); }); } toLowerCase() { const text = document.querySelector('.input-text').value; this.post('lower', text); } toUpperCase() { const text = document.querySelector('.input-text').value; this.post('upper', text); } render(response) { const resultWrapper = document.querySelector('#response-wrapper'); document.querySelector('.input-text').value = ''; resultWrapper.innerHTML = response; } } const app = new App(); app.init();
서버와 관련한 보충 내용 및 tip
spread(...) 연산자를 잘 활용하자
... 연산자를 통해 배열의 요소들을 꺼내올 수 있다는 점을 생각!
const arr=[el1, el2, el3, el4]; cosole.log([...arr, el5, el6]) // [el1,el2,el3,el4,el5,el6]
쿼리 파라미터를 이용해 요청(request)를 보낸 경우, 이 쿼리 내용은 어디에 담겨있는가?
인자명.query 에 해당 쿼리 내용이 담겨 있다
req.param() 또는 req.params
req.param(name [, defaultValue])
특정 파라미터에 대한 값을 가져오는 듯?
다음의 형태로 사용할 수 있다
// ?name=tobi req.param('name') // => "tobi" // POST name=tobi req.param('name') // => "tobi" // /user/tobi for /user/:name req.param('name') // => "tobi"
자세한 내용은 express 공식문서를 참고
http://expressjs.com/ko/api.html#req.param
구조 분해 할당의 다양한 응용
// 구조 분해 할당의 응용 예시 1 ...생략 update: (req, res) => { let data; const list = flights.filter((flight) => { return flight.uuid===req.param('id') }); [data]=list; // 구조 분해 할당 Object.assign(data, req.body); return res.status(200).json(data); ...생략 // 구조 분해 할당의 응용 예시 2 create: (req, res) => { const {flight_uuid , name, phone} =req.body // 구조 분해 할당 booking.push({ flight_uuid, name, phone }) res.location(`/book/${flight_uuid}`) return res.status(201).json({book_id: flight_uuid}); },
from http://lesil.tistory.com/57 by ccl(A) rewrite - 2021-10-29 00:01:57