본문 바로가기
Computer Science

[CS] HTTP 웹 서버는 어떻게 동작할까?

by bkuk 2023. 3. 10.

HTTP의 통신 규약

 

웹 클라이언트(대부분 웹 브라우저)는 웹 서버와 데이터를 주고 받기 위해 HTTP라는 서로 간에 약속된 규약을 따른다. 웹 클라이언트가 웹 서버에 요청을 보내기 위한 규약은 아래와 같다.

 

 

요청 데이터의 첫 번째 라인은 요청 라인(Request Line), 두 번째 라인부터 빈 공백 문자열 라인까지 요청 헤더(Request Header), 빈 공백 문자열 다음부터 본문(Request Body) 데이터가 전송된다. 모든 HTTP 요청에 대해 요청 라인, 요청 헤더, 빈 공백 문자열은 필수이고, 요청 본문은 필수가 아니다.

 

자, 이제 하나씩 뜯어보자.

 

 

요청 라인(Request Line)

 

요청 데이터의 첫 번째 라인은 요청 라인(Request Line)이라고 부른다. 요청 라인은 "HTTP 메서드", "URI", "HTTP-버전"으로 구성되어 있다. URI는 클라이언트가 서버에 유일하게 식별할 수 있는 요청 자원의 경로를 의미한다.

 

 

요청 헤더(Request Header)

 

요청 헤더는 <필드 이름> : <필드 값> 쌍으로 이루어져 있다. 만약 필드 이름 하나에 여러 개의 필드 값을 전달하고 싶다면 쉼표(,)를 구분자로 전달할 수 있다. <필드 이름> : <필드 값1>, <필드 값2>

 

이와 같이 클라이언트에서 요청을 받으면 서버는 클라이언트 요청에 대한 응답을 한다. HTTP 응답 또한 요청과 같이 헤더와 본문으로 구성되어 있다.

 

응답 메서지의 첫 번째 라인은 상태 라인(Status), 두 번째 라인부터 빈 공백 문자열 라인까지 응답 헤더(Header)이고, 빈 공백 문자열 다음부터 응답으로 보낼 본문(Body) 데이터이다.

 

 

상태 라인(Status Line)

응답 헤더의 첫 번째 라인은 상태 라인이라고 부른다. 응답 라인은 "HTTP-버전", "상태코드", "응답구문"으로 구성되어 있다.

 

만약 클라이언트가 /index.html 요청을 보냈다고 가정해보자.

http://localhost:8080/index.html

 

서버에서는 아래와 같이 여러개의 추가 요청이 발생한다.

13:28:19.624 [DEBUG] [Thread-8] [webserver.RequestHandler] - GET /index.html HTTP/1.1
13:28:19.675 [DEBUG] [Thread-9] [webserver.RequestHandler] - GET /css/bootstrap.min.css HTTP/1.1
13:28:19.685 [DEBUG] [Thread-10] [webserver.RequestHandler] - GET /css/styles.css HTTP/1.1
13:28:19.689 [DEBUG] [Thread-11] [webserver.RequestHandler] - GET /js/jquery-2.2.0.min.js HTTP/1.1
13:28:19.691 [DEBUG] [Thread-12] [webserver.RequestHandler] - GET /js/bootstrap.min.js HTTP/1.1
13:28:19.697 [DEBUG] [Thread-13] [webserver.RequestHandler] - GET /js/scripts.js HTTP/1.1
13:28:19.760 [DEBUG] [Thread-14] [webserver.RequestHandler] - GET /fonts/glyphicons-halflings-regular.woff HTTP/1.1
13:28:19.799 [DEBUG] [Thread-15] [webserver.RequestHandler] - GET /favicon.ico HTTP/1.1

 

이 같은 많은 요청이 발생하는 이유는 웹 서버가 웹 페이지를 구성하는 모든 자원( HTML, CSS, JS, 이미지 등)을 한번에 응답으로 보내지 않기 때문이다. 웹 서버는 첫 번째로 /index.html 요청에 대한 응답에 HTML만 보낸다. 응답을 받은 브라우저는 HTML 내용을 분석해 CSS, JS, 이미지 드의 자원이 포함되어 있으면 서버에 해당 자원을 다시 요청하게 된다. 따라서 하나의 웹 페이지를 사용자에게 정상적으로 서비스하려면 클라이언트와 서버 간에 한 번의 요청이 아닌 여러 번의 요청과 응답을 주고 받게 된다.

 

GET 방식과 POST 방식

  <form name="question" method="GET" action="/user/create">
      <div class="form-group">
          <label for="userId">사용자 아이디</label>
          <input class="form-control" id="userId" name="userId" placeholder="User ID">
      </div>
      <div class="form-group">
          <label for="password">비밀번호</label>
          <input type="password" class="form-control" id="password" name="password" placeholder="Password">
      </div>
      <div class="form-group">
          <label for="name">이름</label>
          <input class="form-control" id="name" name="name" placeholder="Name">
      </div>
      <div class="form-group">
          <label for="email">이메일</label>
          <input type="email" class="form-control" id="email" name="email" placeholder="Email">
      </div>
      <button type="submit" class="btn btn-success clearfix pull-right">회원가입</button>
      <div class="clearfix" />
  </form>

 

위 코드에서 form 태그를 보자. method 속성의 값이 "GET"이다. 

웹 브라우저는 회원가입 버튼을 클릭하면 HTML form 태그 구현에 따라 요청 라인을 생성해 서버에 요청을 보낸다. 요청 라인의 "GET"은 form 태그 method 속성 값이고, 요청 URI는 action 속성 값(/user/create)이다. GET 메서드 방식으로 요청을 보낼 경우 사용자가 입력한 값을 물음표 뒤에 매개변수명1=값1&매개변수명2=값2 형식으로 전송한다.

GET 방식으로 사용자가 입력한 데이터를 전달하는 데는 몇가지 문제점이 있다. 대표적으로 사용자가 입력한 데이터가 브라우저 URL 입력창에 표시된다. 비밀번호까지 URL에 노출되기 때문에 보안 측면에서도 좋지 않다. 또한, 요청 라인의 길이에 제한이 있다. 

따라서 GET 방식은 회원가입, 블로그 글쓰기, 질문하기 등과 같이 사용자가 입력한 데이터를 서버에 전송해 데이터를 추가할 때는 적합하지 않다. 

이와 같은 GET 방식의 한계를 극복하기 위해 HTTP는 POST 방식을 지원한다.

 

POST 방식으로 요청하면 GET 방식으로 요청할 때 요청 URI에 포함되어 있던 쿼리 스트링이 없어진다. 요청 URI에 포함되어 있던 쿼리 스트링은 HTTP 요청 본문(Body)를 통해 전달된다. 따라서 본문 데이터에 대한 길이가 Content-Length라는 필드 이름으로 전달된다.

 

최근 REST API 설계와 AJAX 기반으로 웹 어플리케이션을 개발하는 방향으로 발전하고 있는데, 이 기반으로 개발할 때는 PUT, DELETE 메서드까지 활용할 것을 추천하고 있다.

 

리다이렉션(Redirection) / 302 상태 코드 

 

 

위 그림을 보자.

클라이언트의 처음 요청은 GET방식이고, URI는 /doc이다. 서버에서는 해당 요청을 처리한 후 302 응답과 함께 Location 내용을 같이 보낸다. 

클라이언트는 첫 라인의 상태 코드를 확인한 후 302라면 Location 값을 읽어 서버에 재요청을 보내게 된다. 다시 한번, 이에 따른 서버에 응답을 주게 된다.

302 상태 코드를 활용해 페이지를 이동할 경우 요청과 응답이 한 번이 아니라 두 번 발생한다. 302 상태 코드를 활용한 페이지 이동 방식은 많은 라이브러리와 프레임워크에서는 리다이렉트 이동 방식으로 알려져 있다. 따라서, 리다이렉트 방식으로 페이지를 이동한다고 하면 내부적으로 302 상태 코드를 활용하겠구나라고 생각하면 된다.

 

HTTP는 무상태 프로토콜이다.

 

HTTP는 요청을 보내고 응답을 받으면 클라이언트와 서버 간의 연결을 끊는다. 이와 같이 클라이언트와 서버 간의 연결을 끊기 때문에 각 요청 사이에 상태를 공유할 수 없다. 이 때문에 HTTP를 무상태 프로토콜이라고 한다.

따라서, HTTP는 로그인과 같이 클라이언트의 행위를 기억하기 위한 목적으로 지원하는 것이 쿠키(Cookie) 이다. 하지만, 전달하는 쿠키 정보는 클라이언트에 저장해 관리하기 때문에 보안 이슈가 있다. 이 같은 단점을 보완하기 위해 세션이 등장했다. 세션 또한 쿠키를 기반으로 하는 것은 같으나, 좀 더 보안을 강화하기 위한 방법으로 상태 데이터를 서버에 저장한다는 것만 다르다.

 

Content-Type

 

브라우저는 응답을 받은 후 Content-Type 헤더 값을 통해 응답 본문에 포함되어 있는 컨텐츠가 어떤 컨텐츠인지를 판단한다. 

따라서, CSS나 script 요청에 대해 Content-Type 헤더 값을 각기 다르게 응답을 보내야 한다. 

위와 같이 헤더는 본문 컨텐츠에 대한 정보를 제공하고 있다. 이와 같이 데이터에 대한 정보를 포함하고 있는 헤더 정보들을 메타데이터라고 부른다. 예를 들어, Content-Type, Content-Length 헤더 정보는 본문 컨텐츠에 대한 타입과 길이 정보를 포함하고 있는데, 실제 데이터가 아닌 본문 데이터에 대한 정보를 포함하고 있다.

댓글