node.js 를 사용하여 간단한 웹 서버를 만들어 보는 실습을 하면서 알게된 새로운 개념에 대해서 정리하겠습니다.
HTML input 태그에 value 값을 입력하고 버튼을 눌릴 경우 서버에서 대문자나 소문자로 변경 후 응답 해주는 서버를 만들었습니다. 실습은 POST 메소드만 구현하였습니다.
서버 코드 수정 후 재실행이 필요 없도록 nodemon 라이브러리 설치
npm install nodemon
npx nodemon server/basic-server.js // nodemon으로 서버 실행
특정 포트로 클라이언트 실행 시 다음과 같이 실행
npx serve -l 포트번호 client/
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="./style.css" />
<link rel="stylesheet" href="./variables.css" />
<link rel="icon" type="image/x-icon" href="./images/favicon.ico" />
</head>
<body>
<div id="root">
<h2>요청</h2>
<textarea
placeholder="여기에 작성한 데이터를 서버로 보내면 응답으로 받을 수 있어야 합니다."
class="input-text"
></textarea>
<div>
<button id="to-upper-case">toUpperCase</button>
<button id="to-lower-case">toLowerCase</button>
</div>
<h2>응답</h2>
<pre id="response-wrapper"></pre>
<img id="logo" src="./images/codestates-logo.png" />
</div>
<script src="./App.js"></script>
</body>
</html>
App.js 파일에서는 POST 매소드를 구현하고 버튼에 이벤트 리스너를 달아 클릭할 경우 실행해둔 서버의 4999 포트를 경로로 하여 POST 메소드를 실행합니다.
// App.js
class App {
// this binding
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:4999/${path}`, {
method: "POST", // POST 메소드 사용
body: JSON.stringify(body), // 입력 받은 body 직렬화합니다.
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then((res) => {
// 아래에 구현한 render 메소드 실행합니다.
res.render(res);
});
}
// input 태그의 value(body) 를 post 메소드를 사용하여 ulr-path '/lower'로 요청 합니다.
toLowerCase() {
const text = document.querySelector(".input-text").value;
this.post("lower", text);
}
// input 태그의 value(dody) 를 post 메소드를 사용하여 ulr-path '/upper'로 요청 합니다.
toUpperCase() {
const text = document.querySelector(".input-text").value;
this.post("upper", text);
}
// 응답으로 받아온 innerHTML 메소드로 브라우저 화면에 표시합니다.
render(response) {
const resultWrapper = document.querySelector("#response-wrapper");
document.querySelector(".input-text").value = "";
resultWrapper.innerHTML = response;
}
}
const app = new App();
app.init();
"http" 패키지를 가져와 createServer 메소드를 통해서 서버를 생성하고 클라이언트에서 전달받은 HTTP 메소드에 따라 로직을 작성하였습니다.
우선 첫번째로 OPTIONS 메소드로 요청이 들어올 경우 CORS 조건을 응답해주어 flight Request 조건을 충족 시켜줍니다.
브라우저는 응답받은 Access-Control-Allow-Origin 값이 "*" 이므로 접근 권한이 있다는 것을 확인하고 POST 메소드를 이용하여 서버에 요청을 합니다.
POST 메소드 내부에서는 request 객체의 on 메소드에 전달받은 콜백함수를 이용하여 body에 데이터를 추가합니다.
// basic-server.js
const http = require("http");
const PORT = 4999;
const ip = "localhost";
const server = http.createServer((request, response) => {
// 'OPTIONS' 메소드로 요청이 될 경우에 상태 코드 200번과 defalutCorsHeader을 전달)
if (request.method === "OPTIONS") {
response.writeHead(200, defaultCorsHeader);
response.end();
}
// 'POST' 메소드로 요청이 될 경우에 상태 코드 201번과 defalutCorsHeader을 전달합니다.
if (request.method === "POST" && request.url === "/upper") {
let body = [];
request
.on("error", (err) => { // 에러발생 시 에러 출력 합니다.
console.log(err);
})
.on("data", (chunk) => { // 전달 받은 데이터를 배열(body)에 추가 합니다.
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString(); // 전달받은 데이터들을 합쳐 문자열로 만들어 줍니다.
response.writeHead(201, defaultCorsHeader); // 201번 상태코드와 defalutCorsHeader을 전달합니다.
response.end(body.toUpperCase()); // 문자열을 대문자로 변환합니다.
});
} else if (request.method === "POST" && request.url === "/lower") {
let body = [];
request
.on("error", (err) => {
console.log(err);
})
.on("data", (chunk) => {
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString();
response.writeHead(201, defaultCorsHeader);
response.end(body.toLowerCase());
});
} else {
response.writeHead(404, defaultCorsHeader);
response.end();
}
});
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
// CORS 설정
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,
};
서버 응답 후 Response Header를 보면 COSR 설정이 잘 전달된 것을 확인 할 수 있습니다.