node.js의 http 모듈을 이용하여 웹 서버를 만들어보자
전의 스프린트에서 파일을 읽거나 쓰기 위해서 fs 모듈을 사용했고
이번에 http 모듈은 서버를 만들어서 HTTP 요청, 응답을 할 수 있다
HTTP 트랜잭션 해부라는 공식문서를 보고 어떻게 과제를 풀어야 하는지 알 수 있다
모든 node 웹 서버 애플리케이션은 웹 서버 객체를 만들어야 한다 이 때 createServer를 이용한다
const http = require('http');
const server = http.createServer((request, response) => {
// 여기서 작업이 진행됩니다!
});
서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출, 실행된다
HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며 요청 핸들러 함수를 호출한다
요청을 실제로 처리하려면 서버도 프로그램 이기 때문에 노드가 코드를 실행하는 순간 프로세스로 올려줘야 한다
프로세스로 올릴때는 포트를 하나 써야한다
listen 메서드가 server 객체에서 호출되어야 하는데
서버가 사용하고자 하는 포트 번호를 listen에 전달하기만 하면 된다
server.listen(8080, ip, () => {
console.log(`http server listen on ${ip}:${8080}`);
});
const { method, url } = request;
const { headers } = request;
const userAgent = headers['user-agent'];
여기서 method는 항상 일반적인 HTTP 메서드/동사가
url은 전체 URL에서 서버, 프로토콜, 포트를 제외한 것으로, 세 번째 슬래시 이후의 나머지 전부라고 볼 수 있다
request에 headers라는 전용 객체가 있고
클라이언트가 어떻게 헤더를 설정했는지에 관계없이 모든 헤더는 소문자로만 표현된다
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
// 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
});
POST나 PUT 요청을 받을 때 애플리케이션에 요청 바디는 중요할 것입니다.
요청 헤더에 접근하는 것보다 바디 데이터를 받는 것은 좀 더 어렵습니다.
핸들러에 전달된 request 객체는 ReadableStream 인터페이스를 구현하고 있습니다.
이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있습니다.
스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있습니다.
각 'data' 이벤트에서 발생시킨 청크는 Buffer입니다. 이 청크가 문자열 데이터라는 것을 알고 있다면 이 데이터를 배열에 수집한 다음 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋습니다.
mdn의 설명이 조금 이해하기 어렵다
쉽게 풀어보자
유튜브 스트리밍 서비스를 생각하면 된다
회색바는 데이터가 얼마나 내려받아져서 바로 볼 수 있는지를 나타낸다
만약 회색바 이후의 영상을 보고싶어 클릭을 하게되면 버퍼링
이 걸리게 된다
스트리밍(streaming)이란 물이 끊임없이 흐르듯 인터넷으로부터 바로 영상이나 음악을 재생하는 것을 말한다. 영상이나 음원을 작은 단위로 쪼개서 내려받음과 동시에 파일을 재생시켜서 실시간으로 보는 원리이다
데이터를 조각(청크, chunk)내어 buffer에 채운 후 다 차면 buffer를 통째로 옮기고 새 buffer에 아직 옮기지 못한 데이터 조각을 다시 채운다
데이터 조각을 buffer에 채우는 일을 버퍼링(buffering)이라고 부른다
영상이 버퍼링 중이라며 재생되지 않는 경우를 종종 경험했을텐데 buffer에 데이터를 채울 때까지 기다리는 버퍼링 작업이다
buffer가 다 차면 이를 전송하고 다시 buffer를 채우는 버퍼링 작업을 연속하는 것이 스트림(stream)이다. 단발성 single buffer도 존재하지만 지속적으로 buffer가 나오는 것을 stream buffer라고 한다. 버퍼를 이용해 데이터를 전송하는 '흐름'이 스트림이라 이해하면 된다
데이터는 한번에 오는게 아니라 물줄기를 따라서 조금씩 온다
데이터를 전달하는 작업을 스트리밍이라고 한다
이 데이터 조각을청크
라고 부르고 데이터 조각은버퍼
안에 들어가 있다
내 컴퓨터가 데이터의 조각을 합쳐서 전송해 주는 것이다
이 과정을 코드로 쓰게 되면 밑의 코드가 되고 이제 이해할 수 있다
let body = [];
request.on('data', (chunk) => { // 리퀘스트에 요청이 데이터로 왔을때
body.push(chunk);
// 바디에 청크를 집어넣는다
// 데이터가 도착하면 조각을 바디에 집어넣는다
}).on('end', () => { // 요청이 끝날때
body = Buffer.concat(body).toString();
// 버퍼를 합쳐서 문자열로 바꿔준다
});
원한다면 명시적으로 응답 스트림에 헤더를 작성할 수 있습니다.
헤더를 작성하는 writeHead 메서드가 있습니다. 이 메서드는 스트림에 상태 코드와 헤더를 작성합니다.
response.writeHead(200, {
'Content-Type': 'application/json',
'X-Powered-By': 'bacon'
});
response.write('</html>');
response.end();
따로 설정하지 않으면 응답의 HTTP 상태 코드는 항상 200이다
모든 HTTP 응답이 이를 보장하는 것은 아니고 어떤 경우에는 다른 상태 코드를 보내기를 원할 것이기 때문에
상태 코드를 변경하려면 statusCode 프로퍼티를 설정해야 한다
response.statusCode = 404; // 클라이언트에게 리소스를 찾을 수 없다고 알려줍니다.
이제 과제를 해결해보자 !
버튼 클릭에 따라서 다른 http 요청을 서버로 보내고
http 요청에 보낸 단어를 소문자, 대문자로 변경된 단어를 응답으로 받아서 화면에 보여주면 된다
const http = require('http');
const PORT = 4999;
const ip = 'localhost';
const server = http.createServer((request, response) => {
console.log(
`http request method is ${request.method}, url is ${request.url}`
);
response.writeHead(200, defaultCorsHeader);
response.end('hello mini-server sprints');
});
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
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
};
1번과 2번 까지는 모두 작성이 되어있다 여기서 우리가 해야 할 것은
메소드가 포스트이고 url이 upper이면 대문자 url이 lower이면 소문자
그리고 options 와 error 응답을 해결해주면 끝난다
const http = require('http');
const PORT = 4999;
const ip = 'localhost';
const server = http.createServer((request, response) => {
const {method, url, headers} = request
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
if(request.method === 'POST' && request.url === '/upper'){
response.writeHead(200, defaultCorsHeader)
response.end(body.toUpperCase())
}else if(request.method === 'POST' && request.url === '/lower'){
response.writeHead(200, defaultCorsHeader)
response.end(body.toLowerCase())
}else if(request.method === 'OPTIONS'){
response.writeHead(200, defaultCorsHeader)
response.end()
}else{
response.writeHead(404, defaultCorsHeader)
response.end('잘못된 요청')
}
});
});
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
// 모든 응답때 보내줘야한다
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 request는 10초까지 허용된다
};
node server/basic-server.js
로 서버를 열어서 확인 할 수 있다