프론트엔드 개발을 해본 사람이라면 vscode의 live-server, webpack-dev-server, vite등등 다양한 도구를 사용해서 프론트엔드 서버를 띄워본 경험이 있을 것이다. 심지어 express를 사용한 서버에서도 static 설정만 하면 전부 설정이 완료된다.
그런게 나는 지금까지 이런 도구를 사용하면서 과연 이게 어떻게 동작하는가?에 대해서는 한번도 생각해본 적이 없었다.
이번 기회를 통해 제대로 파악해보자.
createServer
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction
createServer모듈을 사용하면 http서버를 만들 수 있다.
request 이벤트가 발생했을때, 콜백함수로 (req,res)=>{}형태의 함수를 받아 요청과 응답을 해줄 수 있는 것이다.
const server = createServer();
server.on('request', async (req, res) => {
req.end(데이터);
});
server.listen(3000, () => {
console.log('Server On');
});
요런식으로 작성하면 요청이 왔을때, 응답을 해줄 수 있다.
우리는 프론트엔드 서버이므로, html파일을 보내줄 것이다.
fs모듈을 사용해서 파일을 읽고, 보내줘보자.
sendHTML함수를 만들어서 파일을 읽고, res.end로 데이터를 보내주었다.
const sendHTML = async (res) => {
const data = await fs.readFile(path.join(__dirName, '/index.html'));
res.end(data);
};
이렇게 하면 html파일까지는 잘 전송이 될 것이다.
다양한 정적 자원들
그러나, html에는 다양한 리소스들이 함께 들어간다.
css, js, img, svg등 다양한 정적 데이터들이 들어가있고, 브라우저는 HTML을 파싱하다가 태그를 만나면 서버에게 데이터를 요청한다.
<!DOCTYPE html>
<html>
<head>
<link href="/style/style.css" rel="stylesheet" /> #css 요청!
</head>
<body>
<img src="사진.png" alt="사진"/> #png 요청!
</body>
<script src="/js/index.js" type="module"></script> #script 요청!
</html>
이때, 서버가 정적자원들에 대해 제대로 응답해주지 못한다면, 이 자원들(css, js, img, svg)이 제대로 작동하기 어려울 것이다.
다행히 요청 url에 자원의 확장자가 있어서 path 모듈을 활용해서 제대로 자원을 줄 수 있을 것 같다.
server.on('request', async (req, res) => {
const { url } = req;
if (url === '/') return await sendHTML(res); //root 요청인 경우 html을 보내주고
const data = await fs.readFile(path.join(__dirName, `${url}`));
res.end(data); //아니면 정적 자원을 응답해줘!
}
자 이제 실행해보자.
아무것도 안나온다.
왜일까?
Content-Type과 MIME Type
이유는 Content-Type이 정해지지 않았기 때문이다.
네트워크탭을 뜯어보면 응답은 제대로 오는 것을 확인할 수 있다.
하지만 Content-Type 헤더가 없기에 적용은 되지 않았다. 그냥 텍스트 데이터로 인식되는 것이다.
https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Content-Type
https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types
이 MIME Type을 참고해서 Content-Type을 세팅하는 것을 도와줄 객체를 하나 만들어주었다.
const contentType = {
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.svg': 'image/svg+xml',
};
이걸 활용해서 Static한 Resource들에 대해 응답을 해주는 코드를 작성해주자.
const sendStaticResource = async (res, url) => {
const data = await fs.readFile(path.join(__dirName, `${url}`));
const contentType = {
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.svg': 'image/svg+xml',
};
const type = path.extname(url);
res.writeHead(200, { 'Content-Type': contentType[type] });
res.end(data);
};
request 시 코드
server.on('request', async (req, res) => {
const { url } = req;
try {
if (url === '/') return await sendHTML(res);
return await sendStaticResource(res, url);
} catch (err) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<div>404 Not Found</div>');
}
});
이렇게 하면 성공적으로 서버가 정적 데이터들을 잘 응답한다.
다음 게시글에서는 실제 모듈들(webpack-dev-server, express)에서 어떻게 웹서버를 구현하고 있는지 코드를 뜯어볼 것이다.