스프린트 mini node server 구현 과정 블로깅
HTTP 트랜잭션 해부 를 참고
const http = require('http');
const PORT = '5000';
const ip = 'localhost';
const server = http.createServer((request, response) => {
})
createServer
를 이용하여 웹 서버 객체를 만들어야 한다.createServer
에 전달된 함수가 한 번씩 호출된다.request
와 response
객체를 전달하며 요청 핸들러 함수를 호출한다.server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
//or
server.listen(PORT);
listen
메서드가 server
객체에서 호출되어야 한다. 대부분은 서버가 사용하고자 하는 포트 번호를 listen
에 전달하기만 하면 된다.routing?
- 경로를 찾아가게 하는 과정
routing protocol
- 찾아주는 규칙 사용하는 프로토콜
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
};
const server = http.createServer((request, response) => {
if (request.method === 'OPTIONS') {
// 1.OPTIONS 메소드가 오는 경우
response.writeHead(200, defaultCorsHeader);
response.end('hello mini-server sprints');
}
if (request.method === 'POST' && request.url === '/upper') {
// 2.POST 메소드와 end point가 /upper일 때
let body = [];
request.on('data', (chunk) => { //각 data 이벤트에서 발생시킨 청크는 Buffer다.
body.push(chunk);//청크를 배열에 모아둔다.
}).on('end', () => {
body = Buffer.concat(body).toString();// end 이벤트에서 이어 붙인 다음 문자열로 만든다.
response.writeHead(200, defaultCorsHeader);
response.end(body.toUpperCase()); //response객체를 보낼 때 해당 데이터를 넣어 보낸다.
});
} else if (request.method === 'POST' && request.url === '/lower') {
// 3.POST 메소드와 end point가 /lower 때
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
response.writeHead(200, defaultCorsHeader); //CORS
response.end(body.toLowerCase());
});
} else {
// 4.에러처리
response.statusCode = 404;
response.end();
}
})
OPTIONS
메소드로 요청이 오는 경우는 해당 요청이 preflight request
인 경우다. 이 요청을 받은 경우 서버는 요청 수락 여부를 결정한 뒤 접근 가능한 조건을 알려준다. 그러면 클라이언트는 서버에 요청을 할지 말지 결정한다.2.3. POST 메소드와 end point가 /upper or /lower 일 때
POST
+ /upper
로 요청을 한 경우다. request
는 ReadableStream
인터페이스를 구현하고 있다. 스트림의 'data'
와 'end'
이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있다.response
객체에 statusCode
를 담고 응답을 보낸다.현재 작업하는 환경은 클라이언트와 서버 모두 로컬에서 이루어지고 있다. CORS를 적용해보기 위해 serve 유틸리티를 이용해 가상의 클라이언트를 만들고 CORS를 적용해본다.
$ npx serve client/ -l 1234
로 가상의 클라이언트를 생성한다.
그 다음, 서버의 Access-Control-Allow-Origin
을 클라이언트의 방금 만든 클라이언트로 지정한다. (해당 클라이언트만 서버에 접근 가능 여부를 판단하기 위함)
const defaultCorsHeader = {
'Access-Control-Allow-Origin': 'http://localhost:1234',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Accept',
'Access-Control-Max-Age': 10
};
http://localhost:1234
에 접속한 뒤, POST
요청을 한 경우 된다면 CORS 적용이 잘 된 것.
위 사진은 preflight request
이후 POST
요청까지 정상적으로 이루어진 경우다.
만약 기존 클라이언트인 http://localhost:5000
에 접속한 뒤, POST
요청을 한 경우 에러가 발생해야 정상
예상한대로 에러가 발생한다. 에러의 내용을 보면 실제로 CORS policy 정책에 의해 막혔다고 나온다.
preflight request
말고 single request
로 요청하도록 변경하는 작업을 해보겠다.single request
가 되기 위해선 여러 조건이 있는데 조건 중 하나인 'Content-Type' : 'text/plain'
으로 변경해 보겠다. post(path, body) {
fetch(`http://localhost:5000/${path}`, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'text/plain'
}
})
.then(res => res.json())
.then(res => {
this.render(res);
});
}
OPTIONS
요청은 건너뛰고 POST
요청이 바로 전달된다. /upper
로 POST
요청이 하나만 간 것을 볼 수 있다.
preflight request
를 보내서 서버에서 요청 가능 여부를 확인 한 뒤, 요청이 가능하다면POST
요청을 하는데 이 때 응답 헤더에 CORS 관련 헤더를 붙여야 하는가?
예시 코드
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
};
if (request.method === 'POST' && request.url === '/upper') {
//대문자로 응답을 돌려줘야 한다
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
response.writeHead(200, defaultCorsHeader); // POST 요청에 대한 응답 헤더에 CORS 관련 헤더를 붙여주고 있다.
response.end(body.toUpperCase());
});
CORS 관련 MDN에 보면 preflight request
를 한 경우 요청, 응답 코드를 볼 수 있다.
<preflight request>
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method 헤더는 preflight request의 일부로, 실제 요청을 전송할 때 POST 메서드로 전송된다는 것을 알려줍니다. Access-Control-Request-Headers 헤더는 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려줍니다.
<preflight request 응답>
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
서버는 Access-Control-Allow-Methods 로 응답하고 POST 와 GET 이 리소스를 쿼리하는데 유용한 메서드라고 가르쳐줍니다. 이 헤더는 Allow 응답 헤더와 유사하지만, 접근 제어 컨텍스트 내에서 엄격하게 사용됩니다.
또한 Access-Control-Allow-Headers 의 값을 "X-PINGOTHER, Content-Type" 으로 전송하여 실제 요청에 헤더를 사용할 수 있음을 확인합니다. Access-Control-Allow-Methods와 마찬가지로 Access-Control-Allow-Headers 는 쉼표로 구분된 허용 가능한 헤더 목록입니다.
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
<preflight request 완료 후 실제 요청>
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
<preflight request 완료 후 실제 요청에 대한 응답>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
preflight request
이후 POST 요청에 대한 응답 헤더에도 Origin이 추가되는 것을 볼 수 있다.
이처럼 다른 헤더는 포함시키지 않더라도Access-Control-Allow-Origin
에 관한 코드는 응답 헤더에 넣어줘서 어느 Origin이 허용되는지 명시해줘야 한다.
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
정리하자면, preflight request
를 통해 요청 가능 여부를 확인하더라도 실제 요청 응답 헤더엔 허용 Origin
을 적어줘야 한다.
잘 봤습니다. 본문 포스트에서 npx serve client/ -1234가 아니라 npx serve client/ -l 1234 로 수정 부탁드릴게요
오타 발견했습니다