웹 서버
웹 서버는 주로 html, css, js 파일을 전송해주는 일종의 파일 서버라고 생각해도 무방하다. 웹 서버를 사용하게 되면 여러 사용자가 동일한 자원을 사용할 수 있으므로 가장 최신버전의 데이터를 다자간에 공유할 수 있다는 장점이있다. restful 기반의 다양한 기능을 쉽게 프로그래밍할 수 있는 프레임워크를 함께 사용하는 것이 일반적이다.
nodejs
nodejs는 이런 웹 서버를 쉽게 구성할 수 있는 내장함수를 제공한다. 이전에 작성한 html 파일을 활용하여 웹 서버에서 페이지를 제공할 수 있도록 구성해보자
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");
});
server.listen(8000, () => {
console.info("server is ready on http://localhost:8000");
});
nodejs에서는 http.createServer()
를 활용하면 아주 쉽게 구성해볼 수 있다. index.js
를 위 처럼 저장하고 node index.js
명령으로 실행하면 서버가 준비상태로 들어가는 것을 확인할 수 있다. 이후 브라우저에서 http://localhost:8000
URL로 접속해보면 응답 객체에 설정한 Hello World
가 표시되는 것을 확인할 수 있다.
디렉토리 구성
.
├── index.js
├── package.json
└── public
├── app.js
├── index.html
└── style.css
디렉토리 public
을 하나 만든 후 앞서 만든 index.html, style.css, app.js를 위치시키고 접속하는 url에 따라서 public 하위에 있는 파일을 전송하도록 하겠다.
파일 서비스
const fs = require("fs");
const path = require("path");
const http = require("http");
const server = http.createServer((req, res) => {
let { pathname } = new URL(req.url,`http://${req.headers.host}`);
// 루트로 접속하면 index.html 파일 지정
if ("/" === pathname) pathname = "/index.html";
const filepath = path.join(__dirname, "public", pathname);
if (!fs.existsSync(filepath)) {
res.writeHead(404);
return res.end("Not Found");
}
res.writeHead(200);
fs.createReadStream(filepath).pipe(res);
});
server.listen(8000, () => {
console.info("server is ready on http://localhost:8000");
});
코드를 수정하고 서버를 다시 실행시켜 접속해보면 페이지가 정상적으로 응답하는 것을 볼 수 있다. 이미지 파일도 위치시키고 해당하는 파일명으로 접근하면 응답받는 것도 확인해볼 수 있다.
<head>
<title>연락처</title>
<meta charset="utf-8">
<!-- else... -->
css와 js도 정상적으로 응답받고 잘 실행되었지만 로컬에서 실행한 것과는 달리 문자가 깨지는 문제가 발생한 것을 확인할 수 있는데, HTML을 해석할 때 어떤 문자셋을 사용해야 하는지 index.html의 헤드 태그 안에 메타 태그를 정의함으로써 해결할 수 있다.
과거에는 latin1
, ms949
, euc-kr
등 국가별, 버전별 너무도 많은 문자셋들이 난립하여 호환 때문에 골머리1를 앓아야 하는 경우도 많았었다. 최근에는 통합된 utf8
문자셋을 사용하도록 전세계가 권장하고 또 따르고 있기 때문에 왠만해서는 다른 문자셋을 사용하지 않도록 주의한다.
라우트
let { pathname } = new URL(req.url,`http://${req.headers.host}`);
// 루트로 접속하면 index.html 파일 지정
if ("/" === pathname) pathname = "/index.html";
const filepath = path.join(__dirname, "public", pathname);
위 코드를 보면 req
객체에 url을 참조하여 요청된 url 별로 파일을 불러오는 부분을 확인할 수 있다. req 객체에는 req.method
도 참조할 수 있는데 정해진 url과 method를 사용하여 기능을 구분하여 restful 서버로 구성할 수 있다. 여기서 url
과 method
로 구분되는 경로를 route
라고 하며 endpoint
라고도 한다.
expressjs
const path = require("path");
const express = require("express");
const app = express();
app.get("*", (req, res) => {
let { pathname } = new URL(req.url,`http://${req.headers.host}`);
if ("/" === pathname) pathname = "/index.html";
const filepath = path.join(__dirname, "public", pathname);
res.sendFile(filepath);
});
app.listen(8000, () => {
console.info("server is ready on http://localhost:8000");
});
이러한 일련의 작업들을 쉽게 작성할 수 있도록 만들어진 패키지로 expressjs
를 대표적으로 들 수 있다. npm i express
를 통해서 설치할 수 있다. expressjs를 활용하면 nodejs에서 제공하는 내장함수로 작성하는 것보다 더 쉽게 작성할 수 있다.
app.get("/api/hello", (req, res) => {
res.json({ message: "Hello, world" });
});
app.get("*", (req, res) => {
/** else... */
앞서 설명한 라우트의 경우도 아주 쉽게 설정할 수 있다. 라우트별로 기능을 분리하거나 파일을 분리하여 관리하기도 쉽다. 라우트를 정의한 순서대로 URL을 매치하기 때문에 이 예시처럼 와일드카드를 사용한 경우라면 순서에 유의하여야 한다.
미들웨어
app.use((req, res, next) => {
/** codes... */
next();
});
미들웨어는 모든 URL에 대하여 전처리하는 기법이다. 예를 들자면 모든 요청에 대해서 로그를 남긴다거나, 모든 응답헤더에 서버 버전을 스탬프처럼 남긴다거나 할 때 사용한다거나, 서버 코드 실행시 발생하는 예기치 않는 오류에 대한 공통처리 등 에러 핸들러로 사용할 수도 있다. 이러한 미들웨어 방식은 매 기능마다 필요한 코드를 사용할 필요없이 간략하게 한 곳에서 정리할 수 있다.
const express = require("express");
const app = express();
app.use(express.static(path.join(__dirname, "public")));
app.use((req, res, next) => {
/** codes... */
next();
});
app.get("/api/hello", (req, res) => {
res.json({ message: "Hello, world" });
});
app.listen(8000, () => {
console.info("server is ready on http://localhost:8000");
});
이러한 미들웨어와 express에서 제공하는 함수를 활용하면 코드를 더 간단하게 줄일 수 있다.
다른 프레임워크들...
nodejs를 기반으로 작성된 웹 서버 프레임워크는 expressjs
를 포함하여 거의 모든 것이 대동소이하다. req, res를 기반으로 작동하며 접근하는 라우트별로 기능을 분할하는 기능이 핵심이다. 이 원리를 이해한다면 어떤 프레임워크를 사용한다 하더라도 쉽게 학습할 수 있을 것이다.
이 기반 하에서 작성되는 다양한 프레임워크들, 이를테면 koa
, adonis
, nextjs
, nuxtjs
, meteojs
, sailsjs
, nestjs
등등 웹 프레임워크라고 하는 것을 자세히 살펴보면 동일한 구조하에 추가적인 편의 기능을 제공하는 형식이다. 만들어야할 서비스의 특성을 잘 파악하고 가장 어울리는 프레임워크를 선택하면 된다.
Footnotes
-
악명높은
占쏙옙
이 문제다. ↩