상권's

TIL 16 (HTTP 트랜잭션 해부) (2021.10.22) 본문

~2022 작성 글/TIL

TIL 16 (HTTP 트랜잭션 해부) (2021.10.22)

라마치 2021. 10. 22. 18:36

HTTP OPTIONS Method

출처

 

HTTP OPTIONS method 는 목표 리소스와의 통신 옵션을 설명하기 위해 사용됩니다. 클라이언트는 OPTIONS 메소드의 URL을 특정지을 수 있으며, aterisk(*) 를 통해 서버 전체를 선택할 수 있습니다.

Request has body NO
Successful response has body YES
Safe(안전함) YES
멱등성 YES
Cacheable NO
Allowed in HTML forms NO
안전함 (HTTP 메서드)
HTTP 메서드가 서버의 상태를 바꾸지 않으면 그 메서드가 안전하다고 말합니다. 다른 말로 하면, 읽기 작업만 수행하는 메서드는 안전합니다. 흔히 쓰이는 HTTP 메서드 중에서는 GET, HEAD, OPTIONS가 안전합니다. 모든 안전한 메서드는 멱등성 또한 갖지만, 모든 멱등성을 지닌 메서드가 안전한 것은 아닙니다. 예컨대 PUT과 DELETE는 둘 다 멱등성을 가졌지만 안전하지는 않은 메서드입니다.

 

Syntax

OPTIONS /index.html HTTP/1.1 OPTIONS * HTTP/1.1

Preflighted requests in CORS

In CORS 에서, OPTIONS 메소드를 통해 프리플라이트 요청 (preflight, 사전 전달), 즉 사전 요청을 보내 서버가 해당 parameters를 포함한 요청을 보내도 되는지에 대한 응답을 줄 수 있게 한다. 

 

Access-Control-Request-Method 헤더는 프리플라이트 요청의 일부분으로 서버에게 실제 요청이 전달 될 때  POST 요청 메소드로 전달될 것 임을 명시한다. 

Access-Control-Request-Headers 헤더는 서버에게 실제 요청이 전달될 때 X-PINGOTHER 와 Content-Type custom headers 와 함께 전달될 것 임을 명시한다. 서버는 그럼 이러한 요구사항들에 맞춰 요청을 수락할 것인지 정할 수 있다.  

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

서버는 Access-Control-Allow-Methods (en-US)로 응답하고, POST, GET, 그리고 OPTIONS 메소드를 통해서 해당하는 자원을 문의 (query) 할 수 있다고 알려준다. 이 헤더는 Allow 응답 헤더와 비슷하지만 반드시 CORS 에 한해서만 사용된다.

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

HTTP 트랜잭션 해부

서버 생성

모든 node 웹 서버 애플리케이션은 웹 서버 객체를 만들어야 합니다. 이 때 createServer를 이용합니다.

const server = http.createServer();
server.on('request', (request, response) => {
  // 여기서 작업이 진행됩니다!
});

이 서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출됩니다. 사실 createServer가 반환한 Server 객체는 EventEmitter이고 여기서는 server 객체를 생성하고 리스너를 추가하는 축약 문법을 사용한 것입니다.

 

HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며 요청 핸들러 함수를 호출합니다.

요청을 실제로 처리하려면 listen 메서드가 server 객체에서 호출되어야 합니다. 대부분은 서버가 사용하고자 하는 포트 번호를 listen에 전달하기만 하면 됩니다. 

 

오류에 대한 간단한 설명

request 객체가 ReadableStream이므로 EventEmitter이기도 하고 오류가 발생했을 때 EventEmitter처럼 동작합니다.

 

request 스트림의 오류가 발생하면 스트림에서 'error' 이벤트가 발생하면서 오류를 전달합니다. 이벤트에 리스너가 등록되어 있지 않다면 Node.js 프로그램을 종료시킬 수도 있는 오류를 던질 것입니다. 그러므로 단순히 오류를 로깅만 하더라도 요청 스트림에 'error' 리스너를 추가해야 합니다.(하지만 HTTP 오류 응답을 보내는 것이 좋을 겁니다. 이에 대해는 뒤에서 더 자세히 살펴봅니다.)

 

별도의 추상화나 도구를 이용해서 오류를 처리하는 다른 방법도 존재하지만, 항상 오류는 발생할 수 있다는 것을 명심하고 오류를 처리해야 합니다.

 

요청 바디

POST나 PUT 요청을 받을 때 애플리케이션에 요청 바디는 중요할 것입니다. 요청 헤더에 접근하는 것보다 바디 데이터를 받는 것은 좀 더 어렵습니다. 핸들러에 전달된 request 객체는 ReadableStream 인터페이스를 구현하고 있습니다. 이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있습니다. 스트림

 'data'와 'end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있습니다.

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
});

응답 바디 전송

명시적으로 응답 스트림에 헤더를 작성할 수 있습니다. 헤더를 작성하는 writeHead 메서드가 있습니다. 이 메서드는 스트림에 상태 코드와 헤더를 작성합니다.

response.writeHead(200, { 
	'Content-Type': 'application/json', 'X-Powered-By': 'bacon' 
    });

 

 

response 객체는 WritableStream이므로 클라이언트로 보내는 응답 바디는 일반적인 스트림 메서드를 사용해서 작성합니다.

response.write('<html>'); 
response.write('<body>'); 
response.write('<h1>Hello, World!</h1>'); 
response.write('</body>'); 
response.write('</html>'); 
response.end();

스트림의 end 함수에 스트림에 보낼 데이터의 마지막 비트를 선택적으로 전달할 수 있으므로 위의 예제는 다음과 같이 간단하게 작성할 수 있습니다.

response.end('<html><body><h1>Hello, World!</h1></body></html>');

에코 서버 예제

위의 예제를 간략하게 바꾸어 간단한 에코 서버를 만들어보겠습니다. 에코 서버는 요청받은 데이터를 그대로 응답으로 돌려보내는 서버입니다. 앞에서 했던 것처럼 요청 스트림에서 데이터를 가져와 응답 스트림에 쓰기만 하면 됩니다.

const http = require('http'); 

http.createServer((request, response) => [
	let body = []; 
    request.on('data', (chunk) => {
    body.push(chunk); 
    }).on('end', () => {
    body = Buffer.concat(body).toString(); 
    response.end(body); 
    }); 
}).listen(8080);

이제 약간 변경해보겠습니다. 다음의 조건에서만 에코 응답을 보내려고 합니다.

  • 요청 메서드가 POST인 경우
  • URL이 /echo인 경우

위 조건이 아닌 경우에는 404를 응답합니다.

const http = require('http'); 
http.createServer((request, response) => {
	if (request.method === 'POST' && request.url === '/echo') { 
    let body = []; 
    request.on('data', (chunk) => {
    body.push(chunk); 
    })
    .on('end', () => {
    body = Buffer.concat(body).toString(); 
    response.end(body); }); 
    } else {
    response.statusCode = 404; 
    response.end(); 
    } 
}).listen(8080);

오늘 학습을 통해서 서버에 요청을 하면 해당 서버에서 처리를 해서 반환 받는 것을 배웠습니다. 아직 사용하는 게 많이 익숙하지 않지만, 지속적인 사용과 다른 강의를 통해서 전반적인 서버 요청, 응답에 대해서 학습해보도록 하겠습니다.

 

const http = require('http');

const PORT = 5000;

const ip = 'localhost';

const server = http.createServer((request, response) => {
  if (request.method === 'OPTIONS') { // cors policy에 해당하기 위해서 진행하는 부분.
    response.writeHead(200, defaultCorsHeader);
    response.end();
  }
  else if (request.method === 'POST' && request.url === '/upper') {
    response.end(data.toUpperCase())
    console.log(request)
    // let body = [];
    // request.on('data', (chunck) => {
    //   body.push(chunck)
    // }).on('end', () => {
    //   body = Buffer.concat(body).toString();
    //   response.writeHead(200, defaultCorsHeader)
    //   response.end(body.toUpperCase())
    // })
  }
  else if (request.method === 'POST' && request.url === '/lower') {
    response.end(data.toLowerCase())
    // let body = [];
    // request.on('data', (chunck) => {
    //   body.push(chunck)
    // }).on('end', () => {
    //   body = Buffer.concat(body).toString();
    //   response.writeHead(200, defaultCorsHeader)
    //   response.end(body.toLowerCase())
    // })
  } 
  else {
    response.writeHead(404, defaultCorsHeader);
    response.end();
  }
  
  console.log(
    `http request method is ${request.method}, url is ${request.url}`
  );
})

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
};

2021.10.25 공식 문서를 바탕으로 입력된 값을 버튼에 따라 대문자로 만들어주거나, 소문자로 만들어주는 서버를 구축해봤습니다. 공식문서를 학습한 이후, 더 간편하게 코드를 구현해봤습니다.


-2021.10.22 오늘의 코플릿-
임의의 tree를 구성하는 노드 중 하나의Node 객체를 입력받아, 해당 노드를 시작으로 너비 우선 탐색(BFS, Breadth First Search)을 합니다. 이 때, 탐색되는 순서대로 노드의 값이 저장된 배열을 리턴해야 합니다.
    // 리턴할 빈 배열을 하나 만든다.
    // 해당 노드가 value를 배열에 추가한다.
    // 자식이 있다면 재귀해서 출력하고 해당 값을 배열에 추가한다.

첫 수도코드 => 이 코드로 진행을 하니 너비우선탐색이 아니라, 깊이우선탐색이 진행되어 버렸습니다. 계속해서 고민해봤지만, 어떻게 해야지 풀 수 있을 지 해답이 나오질 않아,  구글링으로 코드를 참고하고 변경해보았습니다.

let bfs = function (node) {
    let result = []; // value를 넣을 빈 배열을 만든다.

    let queue =[node] // 새로운 배열에다가 인자를 넣어준다.
    while(queue.length) { // 이 배열의 길이가 0이 될 때까지.
      let cur = queue.shift() // 먼저 배열의 첫 요소를 뺀다. => 인자 node가 빠지게 되면서, 빈배열이 된다.
      result.push(cur.value) // 해당 값의 value를 result에 담아둔다.

      if(cur.children) { // 만약에 이 요소가 자식들을 가지고 있다면,
        for ( let el of cur.children ) { // 탐색을 하는 queue의 배열 뒤쪽에다가 넣는다.
          queue.push(el) 
        }
      }
    }
    return result
  };
  // 이 아래 코드는 변경하지 않아도 됩니다. 자유롭게 참고하세요.
  let Node = function (value) {
    this.value = value;
    this.children = [];
  };
  
  // 위 Node 객체로 구성되는 트리는 매우 단순한 형태의 트리입니다.
  // membership check(중복 확인)를 따로 하지 않습니다.
  Node.prototype.addChild = function (child) {
    this.children.push(child);
    return child;
  };

=> 이렇게 코드가 진행이 된다면, 빠지는 요소들 마다 queue의 뒤편에 넣어주기 때문에 너비우선탐색을 진행 할 수 있습니다. 아직 알고리즘을 푸는 방식이 익숙하지 않은데, 매일 푸는 토이 문제를 리뷰하고 분석함으로써 해당 문제를 제 것으로 만들고 새로운 문제를 직면했을 때 해당 방법을 구현할 수 있도록 노력하겠습니다. 

'~2022 작성 글 > TIL' 카테고리의 다른 글

TIL 18 (2021.10.24)  (0) 2021.10.24
TIL 17 (2021.10.23)  (0) 2021.10.23
TIL 15(멱집합&WindowLocalStorage) (2021.10.21)  (0) 2021.10.21
TIL 14 (2021.10.20)  (0) 2021.10.20
TIL 13 (2021.10.19)  (0) 2021.10.19
Comments