Mini Node Server
HTTP 트랜잭션 해부
1 서버생성
모든 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}`);
});
2 메서드, URL, 헤더
const { method, url } = request;
const { headers } = request;
const userAgent = headers['user-agent'];
여기서 method는 항상 일반적인 HTTP 메서드/동사가
url은 전체 URL에서 서버, 프로토콜, 포트를 제외한 것으로, 세 번째 슬래시 이후의 나머지 전부라고 볼 수 있다
request에 headers라는 전용 객체가 있고
클라이언트가 어떻게 헤더를 설정했는지에 관계없이 모든 헤더는 소문자로만 표현된다
3 요청 바디
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
// 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
});
POST나 PUT 요청을 받을 때 애플리케이션에 요청 바디는 중요할 것입니다.
요청 헤더에 접근하는 것보다 바디 데이터를 받는 것은 좀 더 어렵습니다.
핸들러에 전달된 request 객체는 ReadableStream 인터페이스를 구현하고 있습니다.
이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있습니다.
스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있습니다.
4 명시적인 헤더 데이터 전송
원한다면 명시적으로 응답 스트림에 헤더를 작성할 수 있습니다.
헤더를 작성하는 writeHead 메서드가 있습니다. 이 메서드는 스트림에 상태 코드와 헤더를 작성합니다.
response.writeHead(200, {
'Content-Type': 'application/json',
'X-Powered-By': 'bacon'
});
5 응답 바디 전송
response.write('</html>');
response.end();
HTTP 상태 코드
따로 설정하지 않으면 응답의 HTTP 상태 코드는 항상 200이다
모든 HTTP 응답이 이를 보장하는 것은 아니고 어떤 경우에는 다른 상태 코드를 보내기를 원할 것이기 때문에
상태 코드를 변경하려면 statusCode 프로퍼티를 설정해야 한다
response.statusCode = 404; // 클라이언트에게 리소스를 찾을 수 없다고 알려줍니다.
basic-server.js
const http = require('http');
const PORT = 4999;
const IP = 'localhost';
const server = http.createServer(async (request, response) => {
console.log(`http request method is ${request.method}, url is ${request.url}`);
switch (request.method) {
case 'OPTIONS': {
response.writeHead(200, getCorsHeader());
response.end();
break;
}
case 'POST': {
switch (request.url) {
case '/lower': {
const body = await getBody(request);
response.writeHead(200, getCorsHeader());
response.end(body.toLowerCase());
break;
}
case '/upper': {
const body = await getBody(request);
response.writeHead(200, getCorsHeader());
response.end(body.toUpperCase());
break;
}
default: {
response.writeHead(404, getCorsHeader());
response.end('404 Not Found');
break;
}
}
break;
}
default: {
response.writeHead(405, getCorsHeader());
response.end('405 Method Not Allowed');
break;
}
}
});
server.listen(PORT, IP, () => {
console.log(`http server listen on ${IP}:${PORT}`);
});
async function getBody(request) {
return new Promise((resolve, reject) => {
let body = '';
request.on('data', chunk => {
body += chunk.toString();
});
request.on('end', () => {
resolve(body);
});
});
}
function getCorsHeader() {
return {
'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,
};
}