이 포스트는 다음 강의의 내용을 포함하고 있습니다 링크

목표

  • Request 객체 & Response 객체에 대해 이해한다.

 

 


 

API 서버를 구성하다보면 reqest와 response 객체를 자주 접하게 된다.

 

주로 Node.js의 express.js로 서버를 구축하면, API 요청을 응답할 때 req, res로 줄여서 사용하곤 한다.

/* GET home page. */
router.get("/", function(req, res, next) {
  console.log(res);    // 객체 정보가 정말 많으므로 콘솔로 바로 확인하는것은 추천하지 않는다...
  console.log(req);
  res.render("index", { title: "Express" });
});

다음 코드는 express에서 get요청을 받았을 때 다음을 수행한다.

  • console.log로 각 객체를 출력해준다.
  • index페이지를 렌더링 해준다. (단순히 라우팅의 역할을 수행한다)

response 객체를 출력해보면... 다음과 같다

ServerResponse {
  _events:
   [Object: null prototype] { finish: [Array], end: [Function: onevent] },
  _eventsCount: 2,
  _maxListeners: undefined,
  output: [],
  outputEncodings: [],
  outputCallbacks: [],
  outputSize: 0,
  writable: true,
  _last: false,
  chunkedEncoding: false,
  shouldKeepAlive: true,
  useChunkedEncodingByDefault: true,
  sendDate: true,
  _removedConnection: false,
  _removedContLen: false,
  _removedTE: false,
  _contentLength: null,
  _hasBody: true,
  _trailer: '',
  finished: false,
  _headerSent: false,
  socket:
   Socket {
     connecting: false,
     _hadError: false,
     _handle: [TCP],
     _parent: null,
     _host: null,
     _readableState: [ReadableState],
     readable: true,
     _events: [Object],
     _eventsCount: 8,
     _maxListeners: undefined,
     _writableState: [WritableState],
     writable: true,
     allowHalfOpen: true,
     _sockname: null,
     _pendingData: null,
     _pendingEncoding: '',
     server: [Server],
     _server: [Server],
     timeout: 120000,
     parser: [HTTPParser],
     on: [Function: socketOnWrap],
     _paused: false,
     _httpMessage: [Circular],
     _peername: [Object],
     [Symbol(asyncId)]: 7,
     [Symbol(lastWriteQueueSize)]: 0,
     [Symbol(timeout)]: [Timeout],
     [Symbol(kBytesRead)]: 0,
     [Symbol(kBytesWritten)]: 0 },
  connection:
   Socket {
     connecting: false,
     _hadError: false,
     _handle: [TCP],
     _parent: null,
     _host: null,
     _readableState: [ReadableState],
     readable: true,
     _events: [Object],
     _eventsCount: 8,
     _maxListeners: undefined,
     _writableState: [WritableState],
     writable: true,
     allowHalfOpen: true,
     _sockname: null,
     _pendingData: null,
     _pendingEncoding: '',
     server: [Server],
     _server: [Server],
     timeout: 120000,
     parser: [HTTPParser],
     on: [Function: socketOnWrap],
     _paused: false,
     _httpMessage: [Circular],
     _peername: [Object],
     [Symbol(asyncId)]: 7,
     [Symbol(lastWriteQueueSize)]: 0,
     [Symbol(timeout)]: [Timeout],
     [Symbol(kBytesRead)]: 0,
     [Symbol(kBytesWritten)]: 0 },
  _header: null,
  _onPendingData: [Function: bound updateOutgoingData],
  _sent100: false,
  _expect_continue: false,
  req:
   IncomingMessage {
     _readableState: [ReadableState],
     readable: true,
     _events: [Object],
     _eventsCount: 1,
     _maxListeners: undefined,
     socket: [Socket],
     connection: [Socket],
     httpVersionMajor: 1,
     httpVersionMinor: 1,
     httpVersion: '1.1',
     complete: true,
     headers: [Object],
     rawHeaders: [Array],
     trailers: {},
     rawTrailers: [],
     aborted: false,
     upgrade: false,
     url: '/',
     method: 'GET',
     statusCode: null,
     statusMessage: null,
     client: [Socket],
     _consuming: false,
     _dumped: false,
     next: [Function: next],
     baseUrl: '',
     originalUrl: '/',
     _parsedUrl: [Url],
     params: {},
     query: {},
     res: [Circular],
     _startAt: [Array],
     _startTime: 2020-02-09T07:52:51.231Z,
     _remoteAddress: '::1',
     body: {},
     secret: undefined,
     cookies: [Object: null prototype] {},
     signedCookies: [Object: null prototype] {},
     _parsedOriginalUrl: [Url],
     route: [Route] },
  locals: [Object: null prototype] {},
  _startAt: undefined,
  _startTime: undefined,
  writeHead: [Function: writeHead],
  __onFinished: { [Function: listener] queue: [Array] },
  [Symbol(isCorked)]: false,
  [Symbol(outHeadersKey)]: [Object: null prototype] { 'x-powered-by': [Array] } }

엄청 많은 정보들이 포함되어 있는 것을 확인할 수 있다!

이 객체에 대해 전부 설명하기 보다는 필요한 부분만 설명하고자 한다.

Request, Response 객체?

request객체는 클라이언트(브라우저)를 통해 서버에 어떤 정보를 요청하는 정보를 담고있는 객체이다.

response객체는 서버가 클라이언트의 요청에 응답하는 정보를 담고 있는 객체이다.

 

일반적으로 요청은 다음과 같은 순서로 이루어진다.

  1. 클라이언트에서 서버로 요청
  2. 서버는 클라이언트에 응답

requset > response 로 간략화 할 수도 있을것이다.

클라이언트와 서버의 응답

여기서 WAS는 Web Application Server를 의미하므로 편의상 서버라고 하자.

 

이 그림에선 요청와 응답 객체에 어떤 정보를 포함하고 있는지 나와있지 않다.

과연 어떤 정보를 포함하고 있을까?

 

캡틴판교님의 글에 나와있는 그림을 확인해보자

 

출처 https://joshua1988.github.io/web-development/http-part1/

 

각 객체가 가지고 있는 정보

더 자세한 정보는 MDN 문서에서 확인할 수 있다. 링크

 

HTTP 메시지

HTTP 메시지는 서버와 클라이언트 간에 데이터가 교환되는 방식입니다. 메시지 타입은 두 가지가 있습니다. 요청(request)은 클라이언트가 서버로 전달해서 서버의 액션이 일어나게끔 하는 메시지고, 응답(response)은 요청에 대한 서버의 답변입니다.

developer.mozilla.org

Request

시작 줄

  • 요청 메소드 타입 (GET, POST, PUT, DELETE... 그리고 잘 사용하지 않는 HEAD, OPTION)
  • URL 정보

헤더

  • HTTP 헤더

본문 (옵션)

  • 패러미터 (전달받은 데이터)

Response

상태 줄

  • 프로토콜 버전
  • 상태 코드
  • 상태 텍스트

헤더

  • HTTP 헤더

본문 (옵션)

response객체의 헤더 예시

Java Servlet으로 실습

다음 링크의 코드를 이용해 실습을 해보자 링크

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <h1>Request API</h1>
        <ul>
            <li>접속자 IP주소: <%= request.getRemoteAddr() %></li>
            <li>서버 이름: <%= request.getServerName() %></li>
            <li>요청 방식: <%= request.getMethod() %></li>
            <li>프로토콜: <%= request.getProtocol() %></li>
            <li>요청 URL: <%= request.getRequestURL() %></li>
        </ul>
    </body>
</html>

다음 jsp 파일을 보면 request 객체의 값들을 다음 메소드들을 통해서 가져옴을 알 수 있다.

 

다음 링크에서 Servlet에서 제공하는 Requset, Response의 API들을 확인할 수 있다.

HttpServletRequest

 

HttpServletRequest (Servlet 3.0 API Documentation - Apache Tomcat 7.0.99)

Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and server path, but it does not include query string parameters. Because this method returns a StringBuffer, not a string, you can mo

tomcat.apache.org

HttpServletResponse

 

HttpServletResponse (Servlet 3.0 API Documentation - Apache Tomcat 7.0.99)

Deprecated.  As of version 2.1, due to ambiguous meaning of the message parameter. To set a status code use setStatus(int), to send an error with a description use sendError(int, String).

tomcat.apache.org

 

request객체에 parameter가 같이 오는 경우 어떻게 이용할 수 있을까??

 

다음와 코드를 살펴보자

package examples;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ParameterServlet
 */
@WebServlet("/param")
public class ParameterServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public ParameterServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head><title>form</title></head>");
        out.println("<body>");

        String name = request.getParameter("name");        // "name"이란 key를 가진 parameter의 값을 가져오는 부분
        String age = request.getParameter("age");

        out.println("name : " + name + "<br>");
        out.println("age : " +age + "<br>");

        out.println("</body>");
        out.println("</html>");
    }

}

다음 코드에서 이 부분을 유심히 살펴보자

String name = request.getParameter("name");        // "name"이란 key를 가진 parameter의 값을 가져오는 부분

이 방법으로 request 객체의 패러미터를 이용할 수 있다.

 


 

강의의 댓글 중 다음과 같은 질문이 눈에 띄었다.

 

 

궁금한게 있습니다.

ParameterServlet.java 파일에

request의 모든 헤더를 확인하는 코드도 추가해봤습니다(이전 동영상 실습)

request에 getParameter() 함수도 있으니까 파라미터 정보도 담고있는지 확인하고 싶어서요

그런데 모든 헤더 정보를 출력하는 부분에 파라미터 정보는 왜 출력되지 않는걸까요?

아시는분 답글 부탁드릴게요~! 감사합니다

 

질문자가 첨부한 이미지 파일

왜 헤더 정보를 출력할 때 파라미터를 확인할 수 없을까?

 

답은 본문의 request 객체가 포함하고 있는 정보를 설명한 부분에 있다.

 

파라미터 정보는 애초에 헤더에 들어가지 않는다!

 

크롬 네트워크 탭으로 확인해보자

 

크롬 개발자도구의 Network 탭에서 다양한 정보를 확인할 수 있다.

 

 

웹 서비스를 배포 했을 때, 일반적으로 사용자는 브라우저를 통해서 배포한 웹 페이지에 접근한다.

 

이 과정을 간략히 요약해보자.

 

브라우저가 80번 포트로 웹 서버 (WAS)에 접근해 html, css, javascript 파일을 읽어들인다.

(읽어 들일 때 한번에 다 받는것이 아닌 여러번에 걸쳐 나누어 받는다)

html 에서는 DOM 구조를 설정하고 (뼈대를 구성한다)
css 에서 스타일 속성을 부여하며
javascript 에서 동작을 부여한다.

 

우리가 매일 사용하고 있는 브라우저는 사실 많은 동작을 수행하고 있다.

 

웹 페이지를 보여주기 위해 브라우저는 노력하고있다.

웹 브라우저의 구성 요소

이러한 브라우저의 구성 요소에는 아래와 같은 것들이 존재한다.

  1. 사용자 인터페이스
  2. 브라우저 엔진
  3. 렌더링 엔진
  4. 통신
  5. UI 백엔드
  6. 자바스크립트 해석기
  7. 자료 저장소

[출처](https://d2.naver.com/helloworld/59361)

여기서 앞에서 설명한 방식대로 (html, css, javascript) view를 렌더링하는 '렌더링 엔진'에 대해서 알아보자.

렌더링 엔진의 동작 과정

렌더링 엔진의 동작 순서는 다음과 같다.

  1. DOM 트리 구축을 위한 HTML 파싱

  2. 렌더 트리 구축

  3. 렌더 트리 배치

  4. 렌더 트리 그리기

여기서 1번 DOM 트리 구축을 위한 HTML 파싱 부분을 자세히 살펴보자.

[출처](https://ko.wikipedia.org/wiki/%EB%AC%B8%EC%84%9C_%EA%B0%9D%EC%B2%B4_%EB%AA%A8%EB%8D%B8)

DOM 은 Document Object Model의 약자이다.

 

위키피디아에서는 이를 문서 객체 모델이라고 번역했다.

 

위 그림을 보면 html 문서와 많이 비슷해 보이지 않는가??

바로 들여쓰기로 정렬된 html 문서의 구조와 트리구조가 비슷하다는 것을 알 수 있다.

 

렌더링 트리의 주요 동작 중 하나는, html 문서를 파싱해 트리구로조 변환한다는 것이다!

 

물론 html 만으로 구성된 트리는 뼈대만 있는 구조이다.

스타일을 적용하기 위해 css또한 css 파서를 이용해 파싱 후 CSS 트리를 생성한다.

렌더링 엔진은 최종적으로 DOM 트리 CSS 트리를 합쳐 렌더링 트리를 만든다.

 

여기서 재미있는 점은, html 문서에서 태그가 제대로 닫히지 않는 문제가 발생하더라도,

오류가 발생하지 않는다는 것이다!

 

body 태그가 닫히지 않았지만, hello world는 정상적으로 출력된다.

 

브라우저 동작을 더 빠르게!

작성한 코드 (html css js 등을 편의상 코드라 부르겠다)를 브라우저에 더 빨리 렌더링 할 수 있는 방법은 무엇일까??

 

이를 웹 사이트 최적화 라고도 하며, 자잘한 팁들이 있어 소개하고자 한다.

css와 javascript의 위치

html 파일에서 css파일과 javascript 파일을 읽는 코드의 위치를 다음과 같이 해보자.

css : 상단

javascript: 하단

 

css파일의 경우 렌더링 트리를 구성하는 데 중요한 역할을 하기 때문에, DOM 트리가 만들어질 때 이미 생성되어 있다면 바로 렌더링 할 수 있을것이다.

 

브라우저는 파일을 다운로드 할 때까지 DOM 트리 구성을 멈추므로, html 문서 중간에 javascript 파일이 존재한다면 DOM 트리를 생성하는 것을 멈출 것이다.

 

이미지 잘라서 사용하기

브라우저는 한번에 읽어들일 수 있는 이미지의 수가 제한적이다 (렌더링 시 다운로드 하는 이미지)

 

따라서 페이지 UI에 필요한 이미지의 경우, 파일 하나로 다운을 받고 필요한 부분별로 쪼개 쓰는 방식을 사용한다면 초기 로딩시 읽어들여야 하는 이미지의 수를 줄일 수 있다.

네이버에서 사용하는 UI에 대한 이미지

 

 


브라우저는 다양한 기능을 수행한다. 

그리고 브라우저 렌더링을 최적화 하기 위해 다양한 기술들이 존재하고 있다.

 

리액트의 경우 하위 컴포넌트가 변경되었을 때 전체를 다시 render하지 않도록 하기 위해 Virtual DOM (가상 DOM) 기술을 이용하고 있다.

 

그만큼 브라우저의 렌더링이 시스템 자원을 많이 잡아 먹는다는것에 대한 반증일 수 도 있다.

 

 

다음 질문에 답해보자

 

Q : 우리가 흔히 브라우저 탐색을 할 때 스크롤을 하거나, 어떤 것을 클릭하면서 화면의 위치를 바꿀 때, 브라우저는 어떻게 다시 화면을 그릴까요?

A : 처음 브라우저를 로딩할 때 생성된 렌더링 트리를 메모리에 캐싱한다. 스크롤 등의 작업을 통해 화면의 위치가 바뀌작은변화가 일어난다면 repainting의 과정만 거쳐 시스템 자원을 아낄 수 있다.

 

 

2018년 즈음에 일본에서 유학하면서 들었던 web 과정 (backend가 java spring) 을 복습하려고 한다.

그 때 수강하면서 제대로 이해하지 못한 내용이 많기 (거의 전부) 때문이다.

부스트 캠프 2019 과정을 참가하면서 node.js를 공부하며 기본을 잡았으니 이번에는 이해하기 편할것이라고 생각한다...

 

아마도?

 


포트

이름 프로토콜 포트 기능
WWW HTTP 80 웹 서비스
Email SMTP/POP3/IMAP 25/110/114 이메일 서비스
FTP FTP 21 파일 전송 서비스
DNS TCP/UDP 53 네임 서비스
NEWS NNTP 119 인터넷 뉴스 서비스

위의 표는 알아두는 편이 좋다. 적어도 HTTP 프로토콜이 80번 포트를 사용하는건 중요하다!

 

AWS 서버에 배포하면서 80번 포트를 뚫어주면 URL (도메인 호스팅을 사용하지 않는다면 IP주소) 로 접근했을 때 배포한 페이지가 바로 나타난다.

 

포트 관련해서 할 이야기는 많이 있다.

 

주요 데이터베이스 포트

  • MySQL (MariaDB) : 3306
  • MongoDB : 27017

이 외에도 VM (가상머신)으로 다른 운영체제를 가상화 한 경우에 포트포워딩 할 때 포트를 지정하기 등등...

 

이 부분은 기회가 되면 이야기 하고 싶다.

 

HTTP

하이퍼 테스트 트랜스퍼 프로토콜 (Hypertext Transfer Protocol)

최신 버전은 HTTP/2 (ver 2)이다. 주로 많이 사용하는 버전은 ver 1.1

 

'Client' 가 'Server'에 요청을 보내면, 'Server'가 'Client'에게 응답을 보내는 방식이다.

 

이 부분이 가장 중요한데, 'Server'가 'Client'에게 먼저 요청을 보낼 수가 없기 때문이다.

이런 이유 중 한 가지는, 요청>응답 이후 연결을 끊기 때문이다.

 

연결을 끊는 것의 장점은, 연결을 계속 유지할 필요가 없기 때문에 더 많은 사용자들의 요청을 해결할 수 있다.

 

그렇다면 'Server'가 'Client'에게 먼저 요청을 보내려면 어떻게 해야 할까?

 

'Socket'이 답이 될 수 있다. 'Socket'은 연결을 계속 유지하기 때문이다.

 

그리고 'Socket'의 단점은, HTTP의 장점과 정확히 상반된다. (연결을 끊는지, 유지하는지에 대한 차이)

 

HTTP의 sequence diagram

HTTP를 수행할 때, request 객체와 response 객체를 자세히 보면 위 정보들이 들어있다.

 

요청 메시지의 요청 메소드에 주의하자. 특히 Get 과 Post를 구분하는것은 정말 중요하다!

차후 Restful API 내용을 포스팅 할 때 자세히 설명하려고 한다. (저도 Restful을 거의 지키지 않습니다...)

 

그리고 응답에 숫자로 된 번호가 있는것에 유의하자. 각 숫자들을 응답의 상태를 의미하는데,

대표적으로 404 (not found) 등이 존재한다.

 

그리고 개발자들의 숙명인 500번 에러...

Cookie

HTTP 연결은 응답이 완료되면 연결이 끊는다. 이 과정에서 클라이언트의 이전 상황을 알 수 가 없다.

이를 무상태 (Stateless)라고 한다.

 

그렇다면... 요청 이후 변경되는 정보를 저장하고 싶으면 어떻게 할까?

 

  • 서버에 정보를 저장한다 (데이터베이스, 메모리, 캐싱)
  • 클라이언트에 정보를 저장한다 (쿠키)

크롬 사용자의 경우 사용자 정보 제거 (Ctrl + Shift + Delete) 를 누르면 쿠키를 제거하는 체크박스가 존재한다.

보통 쿠키에 로그인 여부 등등을 저장하곤 하는데 (나는 주로 JWT를 저장했었다...)

 

이것과 관련해서 처리하기 애매한 보안 이슈들이 발생한다!

 

ex) 로그인 정보를 쿠키에 저장할지??

 

왜나하면 쿠키는 브라우저에서 까볼수 있기 때문이다! (확인 가능하다는 의미)

브라우저 콘솔 창을 열고 document.cookie를 확인해보자...

document.cookie

 

document 객체에 대해서 나중에 기회가 되면 설명하고 싶다.

 

URL

공부를 하다보면 URI 와 URL이란 단어가 존재하는 것을 확인 할 수 있다.

정말 대충 설명하면 URI = URL + URN 이다.

 

일반적으로 URL만 알아두어도...

 

https://developer.mozilla.org/ko/docs/Learn/Common_questions/What_is_a_URL

 

What is a URL?

Hypertext 와 HTTP 함께, URL 은 웹에서 중요한 개념 중 하나이다.  URL은 웹에 게시된 어떤 자원을 찾기 위해서 browsers에 의해 사용되는 메카니즘이다 .

developer.mozilla.org

MDN 문서를 확인하면 문법에 대해 더 자세히 알 수 있다.

 

바쁜 사람들을 위해 대충 설명하면

 

접근 프로토콜 :// IP또는 도메인 : 포트번호 / 문서경로 / 문서이름

 

으로 나타낼 수 있다. (포트번호는 80번을 사용하는 경우 생략가능하다)

 

부스트코스 강의에서는 포트번호 관련 내용이 빠져있어서 추가했다.

 

 


오랫만에 한 복습이였는데, 예전엔 그냥 수강했던 강의의 내용이 지금에서 보니 엄청 중요했구나 라는 것을 느꼇다...

특히 개론에 대한 설명인데도, 본질적인 부분을 꿰뚫고 있는 부분이 있었다. (HTTP의 장 단점)

 

공부는 끝이 없다...

+ Recent posts