오늘은 웹 서버에 대해서 공부를하였다.
첫번째로 CORS에 대해서 배웠는데 이에 대한 부분은
새로 포스팅하여 자세히 공부해볼 예정이다.
오늘한것은 node.js의 http모듈을 이용하여
웹서버를 만들어보았다.
모듈이라고는 저번에 포스팅했던 fs모듈써본게 다였던지라
많이 어렵고 헷갈렸다.
했지만 하지않은것 같은..??
이번엔 http모듈을 통해 HTTP 요청과 응답을 다루어보았다.
코드스테이츠에서는 node공식 문서에서
HTTP 트랜잭션 해부를 보면서 해보라고 권장하였다.
공식문서에 들어가자마자 든 생각은 이게 뭐야....??
하지만 다시 마음을 다잡고... 하나하나씩 읽어보았다.
chunk,Buffer 등등 생전 처음들어보는 단어들이
난무했지만 하나씩 찾아보면서 알아갔다.
간단하게 chunk, Buffer, stream에 대해 간략하게 설명하겠다.
chunk - 데이터 조각을 말한다.
Buffer - chunk를 받아주는 용기라고 생각하면된다.
즉, chunk들을 Buffer에 채운후 다 차면 Buffer를 통째로 옮기고,
새 Buffer에 아직 옮기지못한 데이터조각chunk를 다시 채우는 과정 반복.
따라서 자주 들어본 버퍼링(buffering)이라는게
buffer에 chunk를 다 채우지못해 다 채울때까지
기다리는 작업이라고 한다.
stream - buffer가 다차면 이를 전송하고
다시buffer를 채우는 버퍼링 작업을 연속으로 하는 것
지속적으로 buffer가 전송되면 이것을 stream buffer라고 부르는 것이다.
아래의 과제로 구현해야하는것은
HTTP 메시지의 body를 이용하여
POST에 문자열을 담아 요청을 보내는 것이고,
서버는 요청에따른 적절한 응답을 클라이언트로 보낸다.
이렇게하여 클라이언트의 버튼클릭에 따라 각기 다른
HTTP 요청을 서버로 보내고, HTTP요청을 담아 보낸 단어를
소문자 또는 대문자로 응답을 받아 화면에 보여주는 것이다.
1. 서버생성
node에서 웹 서버를 만들기 위해서는 웹 서버 객체를 만들어야한다.
이때 http 모듈을 통해 createServer() 메서드를 사용하여,
이 서버로 오는 HTTP요청마다 createServer에 전달된 함수가 한번씩 호출.
HTTP요청이 서버에 오면 node가 트랜잭션을 다루기 위해
request와 response 객체를 전달하며, 요청 핸들러함수를 호출.
요청을 실제로 처리하려면 listen메서드가 server객체에서 호출되어야 함.
따라서 아래와 같은 코드를 작성해주어야한다.
위에서 listen 메서드의 매개변수인 PORT, ip는
PORT는 4999, ip는 'localhost'가 되는 것이다.
2. 요청 바디
POST나 PUT 요청을 받을때 요청바디는 매우 중요하다.
핸들러에 전달된 request객체 ReadableStream 인터페이스를 구현한다.
위의 스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 달아서 데이터를 받는다.
2-1. writeHead
response.writeHead(HTTP 상태코드, 헤더정보를 연관배열로 정리한것)
writeHead는 'http' 모듈의 내장 속성이며,
요청에 대한 응답 헤더를 보내는 역할을 한다.
상태코드는 404,201 등등과 같은 3자리 HTTP 상태코드이다.
두번째 인수로는 defaultCorsHeader가 들어가있는데
크게 말하자면, 헤더 정보를 연관배열로 정리를 한것이라고
생각하면 될것같다. 이부분은 아래에서 다시 설명을하겠다.
2-2. .on
request.on('이벤트이름', 통합 처리(함수))
"on"이라는 메서드는 지정된 이벤트 처리를 통합하는것,
첫번째 인수엔 이벤트이름, 두번째 인수엔 통합처리(함수)를 지정
아래의 예제에서는 이벤트이름을 'data'로 주었고,
통합처리함수를 콜백으로 주어 chunk를 body배열에
push해주는 함수를 넣어주었다.
2-3. .end
res.end();
내용 내보내기가 완료되면 마지막으로
response의 "end"이벤트를 호출하여 컨텐츠 출력을 완료함.
"end"이벤트에 인자로 내보낼 내용의 값을 지정해줄 수 도있다.
그러면 인자의 값을 쓴 후에 내용을 완료한다.
아래의 예제에서는 "end"이벤트의 인자로
익명함수로 콜백을 주었고,
그 콜백은 body에 각 'data' 이벤트에서 발생시킨 chunk인
Buffer를 body와 이어붙인 다음 문자열화 해주고,
그 문자열화 된 body를 각각 대문자화, 소문자화를 해주었다.
아래는 예제의 코드이다.
위의 코드들을 짤라서 살펴보자.
1. 첫번째 if문(OPTIONS 메서드일때)을 보면,
'OPTIONS' 메서드로 요청이 오면 해당 요청이 preflight request이다.
preflight request란, 쉽게말하면 actual 요청(실제요청) 전에
인증 헤더를 전송하여 서버의 허용 여부를 먼저 체크하는 테스트요청이다.
더 자세한 내용은 preflight를 검색해보면 아주 자세히 나온다.
따라서 첫번째 if문에선 서버는 요청 수락 여부를 결정한뒤에
접근 가능한 조건을 알려준다.
그러고나서 클라이언트는 서버에 요청여부를 결정하는 것이다.
2. 두번째 if문(POST메서드와 endpoint가 /upper일때)
request는 ReadableStream 인터페이스를 구현.
스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 등록하여
데이터를 받을 수 있다.
각 'data'이벤트에서 발생되는 chunk는 Buffer이다.
즉, chunk는 문자열데이터 이므로 데이터를 배열에 담고
'end'이벤트에서 concat으로 이어붙이고, 다시 문자열로 만들어준다.
3. 세번째 if문(POST메서드와 endpoint가 /lower일때)
위의 엔드포인트가 /upper일때와 똑같은 구조로 동작한다.
request객체의 url만 "/lower" 로 바꿔주고
'end'이벤트에 문자열이 담긴 body를 toLowerCase()로
소문자로 바꾸어주면 된다.
4. 에러처리
요청이 올바르게 되지 않으면, response 객체에
status를 담고 응답을 보낸다.
아래의 예제를 보면,
HTTP 상태코드를 404로 주어 에러를 처리하였다.
5. CORS 적용
먼저, CORS는 간단하게 설명하겠다.
CORS에 대한 내용이 너무 방대하기 때문에
따로 블로그 포스팅을 할예정이다.
CORS란 (Cross Origin Resource Sharing)의 줄임말로
교차 출처 리소스 공유라고 해석할 수 있다.
브라우저에서 보안적인 이유로 cross-origin HTTP 요청들을 제한하기 때문에
cross-origin 요청을 하려면 서버의 동의가 필요하다.
만약, 서버가 동의한다면 브라우저에선 요청을 수락하고
동의하지않으면 브라우저에서 거절한다.
이러한 허락을 구하고 거절하는 과정은 HTTP-Header를 이용하는데
이를 CORS라고 부른다.
그럼 cross-origin이 뭐야..??
아래의 세가지 중에 한가지라도 다르면 cross-origin이다.
- 프로토콜 - http 와 https은 다른프로토콜이다.
- 도메인 - domain.com 과 other-domain.com은 다른 도메인이다.
- 포트번호 - 8080포트와 3000포트는 다른 포트다.
이렇게 간단히 알아보고 예제에서 사용한 CORS를 알아보자
예제를 살펴보면,
위의 코드는
브라우저에서 크로스 도메인 요청은 기본적으로 제한되어있기에,
이것을 허용하기 위해서는 서버가 허용하는 범위내에서
cross-origin 요청을 허용해야한다.
따라서 위의 코드내용에 따르는 부분만을
허용하겠다. 라고 조건을 걸어준거라고 생각하면된다.
위의 4가지 조건을 해석해보면,
- 모든 도메인(*)을 허용한다.
- 메서드는 GET, PUSH, PUT, DELETE, OPTIONS만 허용한다.
- 헤더에는 content-type과 accept만 사용할 수 있다.
- preflight request는 10초까지만 허용한다.
위의 조건을 써넣은 객체 defaultCorsHeader를 이제
위의 2-1에서 넘긴 부분인 writeHead의 두번째 인수로 넣어주면 되는것이다.
그렇게하여 먼저 method가 OPTIONS일때 서버에서
허용하는 조건들을 다 맞추고 있는지 사전에 서버에 확인요청을 하여
먼저 처리해주고 POST요청을 받게되는 것이다.
이를 preflight 요청이라고 하는 것이다.
많이 의아했던부분이 왜 매번 POST요청마다
CORS관련헤더를 붙여야하나??
preflight request를 마치고나면 어차피 서버에서
허용하는 조건들 다통과해서 preflight request가 통과된거아니야??
라고 생각할 수 있지만
그 이후의 POST 요청에 대한 응답 헤더에서도 Origin이
추가되기 때문에 어느 Origin이 허용되는지 명시를 해주어야한다는 것이다.
즉, preflight request를 통과하여 요청 가능여부를 확인해도
실제 요청응답 헤더에도 허용 origin을 명시해주어야한다는 것이다.