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});
},
'SE Bootcamp 내용 정리' 카테고리의 다른 글
react - 상태 관리 1(기본 상태 관리, Redux) (0) | 2021.11.02 |
---|---|
react - 컴포넌트 디자인 (0) | 2021.10.28 |
Web Server - 기초 / express 활용하기 (0) | 2021.10.27 |
react - 데이터 흐름의 이해와 비동기 요청 처리 보충 내용 (0) | 2021.10.21 |
react - 데이터 흐름의 이해와 비동기 요청 처리 1 (0) | 2021.10.21 |