- 학습 내용
이번 파트에서는 본격적으로 node.js를 통해 서버를 만들어보고 만든 서버를 통해 데이터를 요청하고 응답해본다. 그 과정 중에 API Server를 직접 구현해보고 Express, 라우팅, Server-side를 디버깅하는 방법을 학습한다.
- CORS
예전에는 서버에서 클라이언트라는 파일을 가지고 있고 유저의 요청에 의해 서버에 있던 클라이언트를 유저가 가져가서 그 클라이언트에서 서버와 통신을 하거나 그 클라이언트에 담겨있던 데이터를 보는 방식으로 작동했다. 그러다 보니 서버에서 보낸 클라이언트는 서버에 어떠한 위해도 주지 않을 것이라는 명제가 당연시됐다. origin이라는 것이 서버에서 나와서 그 서버의 것으로 요청하는 것이 당연히 same origin이었던 것이다. 그러나 최근에는 single page application이라는 기술이 등장하고 웹 앱이 고도화되면서 요청을 하나의 서버에서만 하는 것이 아니라 다른 여러 서버에서도 요청을 하게 되었다. 이것을 cross origin이라고 한다. 즉, CORS란 Cross Origin Resource Sharing이란 뜻으로 한 서버가 아닌 여러 서버에 자원들을 요청하여 공유하는 것이다. 그러니 당연히 브라우저는 보안상의 위협에 의해 cross origin을 제한했다. 예전에 당연시되던 명제를 만족시키지 못하기 때문에 브라우저 입장에서 서버가 가진 클라이언트가 아닌 다른 클라이언트가 침범하는 꼴이 되기 때문이다. 그러나 계속된 발전을 거듭하다 보니 이러한 제한 역시 개선될 수밖에 없었고 지금은 브라우저에서 허용한 범위 내에서 cross origin 요청을 허용하는 방향을 채택했다. 그 허용에 대한 코드는 이렇다.
const defaultCorsHeaders = {
'access-control-allow-origin': '*', // 모든 도메인(*)을 허용함
'access-control-allow-method': 'GET, POST, DELETE, OPTIONS', // 메소드는 GET POST DELETE OPTIONS만 허용함
'access-control-allow-headers': 'content-type, accept', //헤더에는 content-type과 accept만 쓸 수 있음
'access-control-max-age': 10 // seconds단위, proflight requests는 10초까지 허용함.
}
- HTTP 서버 생성과 요청 응답
1)createServer
모든 node 웹 서버 앱은 웹 서버 객체를 만들어야 한다. createServer 키워드를 사용해 만들 수 있다.
const http = require('http');
const server = http.createServer((request, response) => {
// 작업
});
이렇게 사용하면 서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출된다. 이런 식으로 축약 문법을 사용할 수도 있다. HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며 요청 핸들러 함수를 호출한다.
const server = http.createServer();
server.on('request', (request, response) => {
// 작업
});
요청을 실제로 처리하려면 listen 메소드가 server객체에서 호출되어야 한다. 대부분 서버가 사용하고자 하는 포트 번호를 listen에 전달하기만 하면 된다.
const PORT = 5000;
const ip = 'localhost';
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
2)메소드, URL, 헤더
요청을 처리할 때, 우선은 메소드와 URL을 확인한 후 이와 관련된 작업을 주로 한다. 이것은 Node가 request객체에 프로퍼티를 넣어 두었다가 프로퍼티를 value를 사용하듯 할 수 있다는 것이다. 그래서 이것을 구조 분해 할당하여 사용하면 매우 편리하다.
const { method, url } = request
여기서 메소드는 HTTP 메소드이고, url은 전체 url에서 서버, 프로토콜, 포트를 제외한 세 번째 슬래시 이후의 나머지 전부이다. 그리고 헤더 역시 request안에 프로퍼티가 있다.
const { header } = request;
const userAgent = headers['user-agent'];
주의할 점은 모든 헤더는 소문자로만 표현된다는 것이다. 그리고 일부 헤더를 반복해서 설정할 경우 이 값은 헤더에 따라 덮어씌워지거나 콤마로 구분된 문자열로 합쳐진다.
3) 요청 바디
POST나 PUT 메소드 요청의 경우 앱의 요청 바디를 작성해야 한다. 바디의 경우 request 객체의 ReadableStream 인터페이스를 구현하고 있다. 이것으로 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있다. 스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있다. 각 'data' 이벤트에서 발생시킨 청크는 Buffer이다. 이 청크가 문자열 데이터라는 것을 알고 있다면 이 데이터를 배열에 수집한 다음 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋다.
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
// 여기서 'body'에 전체 요청 바디가 문자열로 담겨있다.
});
4) 오류 처리
request 객체가 ReadableStream이므로 EventEmitter이기도 하다. 그래서 오류가 발생하면 EventEmitter처럼 동작한다. request 스트림의 오류가 발생하면 스트림에서 'error' 이벤트가 발생하면서 오류를 전달한다. 이벤트 리스너가 등록되어 있지 않다면 Node.js 프로그램을 종료시킬 수도 있는 오류를 throw 한다. 그러므로 단순히 오류에 접근하더라도 요청 스트림에 'error'리스터를 추가해야 한다.
request.on('error', (err) => {
console.error(err.stack);
});
5) HTTP 상태 코드
HTTP 상태 코드는 따로 설정하지 않으면 항상 200이다. 상태 코드를 변경하려면 statusCode 프로퍼티를 설정하면 된다.
response.statusCode = 404;
6) 응답 헤더 설정
setHeader메소드로 헤더를 설정할 수 있다. 응답에 헤더를 설정할 때 헤더 이름의 대소문자는 중요하지 않다. 그리고 만약 헤더를 여러 번 설정하면 마지막에 설정한 값을 보내게 된다.
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');
명시적으로도 응답 스트림에 헤더를 작성할 수도 있다. writeHead메소드를 사용하면 된다. 이 경우 상태 코드와 헤더를 동시에 작성할 수 있다.
response.writeHead(200, {
'Content-Type': 'application.json';
'X-Powered-By': 'bacon'
});
7) 응답 바디 전송
response 객체는 WritableStream이다. 그래서 클라이언트로 보내는 응답 바디는 일반적인 스트림 메소드를 사용한다.
response.write('<html>');
response.write('<body>');
response.write('<div>Hello, World!</div>');
response.write('</body>');
response.write('</html>');
response.end();
스트림의 end 함수에 스트림에 보낼 데이터의 마지막 비트를 선택적으로 전달할 수 있으므로 위의 예제는 다음과 같이 간단하게도 작성할 수 있다.
response.end('<html><body><div>Hello, World!</div></body></html>');
8) 예제
지금까지 본 내용을 모두 사용하면 이렇게 서버를 만들고 요청과 응답을 할 수 있다.
const http = require('http');
http.createServer((request, response) => {
const { headers, method, url } = request;
let body = [];
request.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
response.on('error', (err) => {
console.error(err);
});
response.statusCode = 200;
response.setHeader('Content-Type', 'application/json');
const responseBody = { headers, method, url, body };
response.write(JSON.stringify(responseBody));
response.end();
});
}).listen(8080);
'아키텍처' 카테고리의 다른 글
[배포] Amazon Web Service (0) | 2022.06.29 |
---|---|
네트워크 심화 (0) | 2022.06.27 |
클라이언트 빌드와 배포 (0) | 2021.12.08 |
[Web Server] Express (0) | 2021.12.08 |
네트워크 기초 (0) | 2021.11.03 |