[ETC] HTTP 톺아보기

2022년 01월 23일19분 소요
포스트 배너

HTTP, HyperText Transfer Protocol은 인터넷에서 하이퍼텍스트(HyperText)를 전송(Transfer)하기 위한 통신 규약(Protocol)입니다. 이번 포스트에서 HTTP가 무엇인지 살펴보도록 하겠습니다.

이번 포스트는 HTTP/1.* 버전을 기준으로 작성되었습니다. HTTP/2, HTTP/3에 대한 포스팅은 [ETC] HTTP 버전에서 확인할 수 있습니다.

HTTP란

HTTP는 W3(World Wide Web)에서 정보를 주고 받을 수 있는 프로토콜로, 클라이언트와 서버 사이에 이루어지는 요청/응답(request/response) 프로토콜입니다. 예를 들어 웹 브라우저가 HTTP를 통해 서버로부터 웹 페이지(HTML)나 이미지를 요청하면 서버는 이 요청에 응답하여 필요한 정보를 전달합니다.

HTTP 특징

HTTP는 아래와 같이 간단함, 확장성, 무상태, 비연결성 4가지 특징을 가집니다.

  • 간단함: HTTP의 요청/응답 메시지 구조를 보면 알 수 있듯이 사람이 읽을 수 있도록 만들어진 프로토콜입니다.
  • 확장성: HTTP/1.0에 도입된 HTTP 헤더를 사용하면 프로토콜을 쉽게 확장할 수 있습니다. 새로운 기능을 위한 새로운 헤더에 대해 클라이언트와 서버가 서로 합의가 이루어진다면 도입할 수 있는 확장성을 가집니다.
  • 무상태: HTTP는 통신 상태나 이전 호출 결과 등을 저장하지 않습니다. 상태 저장이 필요한 경우 쿠키나 세션 등을 사용해야 합니다.
  • 비연결성: HTTP는 클라이언트와 서버가 연결을 맺은 후 클라이언트의 요청에 서버가 응답을 마치면 연결이 끊어지게 됩니다. 이런 특징은 연결을 계속 유지할 때 필요한 자원을 줄일 수 있지만 매 요청 마다 연결을 맺어야 하는 오버헤드가 발생하게 됩니다. HTTP 버전이 올라가면서 이런 오버헤드는 최적화 되었습니다.

HTTP 요청 메시지

클라이언트가 서버에게 보내는 요청 메시지의 구조와 요청 메서드, 요청 헤더에 대해 살펴보겠습니다.

요청 메시지 구조

요청 메시지는 요청 라인, 요청 헤더, 빈 라인, 메시지 본문 4가지로 구성됩니다. 코드로 살펴보면 메시지 본문이 없는 GET 요청 메시지는 아래와 같습니다.

GET /http.html HTTP/1.1
Host: beomy.github.io

메시지 본문이 있는 POST 요청 메시지를 표현하면 아래와 같습니다.

POST /http.html HTTP/1.1
Host: beomy.github.io
Content-Type: application/json
Content-Length: 32

{
    "data": "데이터가 바디에 담깁니다."
}

각 줄의 끝은 <CR><LF>로 끝나야 합니다. 캐리지 리턴(Carriage Return)과 라인 피드(Line Feed) 외에 다른 화이트 스페이스가 포함되면 안됩니다.

캐리지 리턴(Carriage Return)과 라인 피드(Line Feed)

캐리지 리턴과 라인 피드는 타자기에서 사용하는 용어로, 캐리지 리턴(줄여서 CR)은 현재 위치를 나타내는 커서를 맨 앞으로 이동시킨다 라는 뜻입니다. 라인 피드(줄여서 LF)는 커서의 위치를 아랫줄로 이동시킨다는 뜻입니다.

타자기에서 다음 줄로 이동 할 때, 타자기의 커서를 맨 앞으로 이동한 후 종이를 위로 올려줘야 합니다. 즉 CR 후 LF를 해야 다음 줄로 이동하게 됩니다. 타자기와 동일하게 PC에서도 CR + LF 이 두 동작을 합쳐서 Enter 동작을 하게 됩니다.

메시지 본문이 있는 POST 요청 메시지를 보면서 아래 그림과 같이 4가지 구조로 나누어 볼 수 있습니다.

요청 메시지 구조

4가지 구조를 하나씩 살펴보도록 하겠습니다.

요청 라인(Request line)

POST /http.html HTTP/1.1 이 부분이 요청 라인입니다. 요청 라인의 구조는 아래 그림과 같습니다.

요청 라인 구조

요청 라인은 요청 메서드, 요청하는 서버의 URL, HTTP 버전, 줄 바꿈을 위한 CR + LF 순서로 작성됩니다. 요청 메서드는 대소문자를 구분(GET과 get은 다름)합니다.

0개 이상의 요청 헤더(Request header fields)

HTTP/1.0에서는 요청 헤더가 존재하지 않아도 상관없었지만, HTTP/1.1 버전 이후로 Host 헤더가 필수 헤더가 되어 1개 이상의 헤더가 존재해야 합니다.

아래 코드 부분이 요청 헤더입니다.

Host: beomy.github.io
Content-Type: application/json
Content-Length: 32

요청 헤더의 구조는 아래 그림과 같습니다.

요청 헤더 구조

헤더 이름은 대소문자를 구분하지 않습니다. 즉, host: beomy.github.ioHost: beomy.github.io는 동일한 헤더입니다.

빈 라인(Empty line)

<CR><LF>으로만 구성된 줄입니다.

메시지 본문(Message body) - 선택

요청 메시지의 본문은 데이터를 담아 서버에 요청을 보내도 보내지 않아도 되는 선택적인 부분입니다. 메시지 본문에 어떤 형태의 데이터가 담겨 있는지는 Content-Type 헤더를 통해 알 수 있습니다.

요청 메서드

HTTP/0.9에서는 GET 메서드가, HTTP/1.0에서는 GET, HEAD, POST 메서드가, HTTP/1.1에서는 PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 메서드가 추가되었습니다. 각 메서드는 아래 표와 같이 정리할 수 있습니다.

HTTP 메서드 RFC 요청에 Body가 있음 응답에 Body가 있음 안전 멱등(Idempotent) 캐시 가능
GET RFC 7231 Optional Yes Yes Yes Yes
HEAD RFC 7231 Optional No Yes Yes Yes
POST RFC 7231 Yes Yes No No Yes
PUT RFC 7231 Yes Yes No Yes No
DELETE RFC 7231 Optional Yes No Yes No
CONNECT RFC 7231 Optional Yes No No No
OPTIONS RFC 7231 Optional Yes Yes No No
TRACE RFC 7231 No Yes No Yes No
PATCH RFC 5789 Yes Yes No No No
안전한 메서드

해당 메서드를 사용한 요청이 서버에 영향을 주지 않는 요청 메서드를 안전한 메서드라고 합니다. GET, HEAD, OPTIONS, TRACE 메서드를 안전한 메서드라고 하는데 안전한 메서드는 읽기 전용(서버 데이터에 영향을 주지 않는) 메서드 입니다. 그 외 POST, PUT, DELETE, CONNECT, PATCH 메서드는 서버 데이터를 변경하기 위해 사용되는 메서드이므로 안전하지 않는 메서드로 분류됩니다.

멱등 메서드

여러 번 요청을 보내도 결과가 동일한 메서드를 멱등 메서드라고 합니다. 예를 들어 A라는 요청 메시지를 10번 보냈을 때와 1번만 보냈을 때 두 요청의 서버 결과가 동일한 경우를 말합니다.

서버에 영향을 주지 않는 GET 메서드나 특정 데이터를 대체하는 PUT 메서드, 특정 데이터를 삭제하는 DELETE 메서드는 10번 호출 해도 동일한 결과가 나타나기 때문에 멱등 메서드입니다. 반면 데이터를 추가하는 POST 메서드의 경우 10번 호출할 경우 10번 데이터가 추가 되기 때문 멱등 메서드가 아닙니다.

캐시 가능한 메서드

재사용 될 수 있는 요청에 대한 응답의 경우 캐시가 가능합니다. GET, HEAD, POST 메서드의 경우 캐시가 가능한 요청입니다.

이미지를 가져오는 요청의 경우 화면을 새로 고침 할 때 마다 이미지를 네트워크를 통해 가져오는 것은 비효율적이기 때문에 캐시 해 두기 좋습니다. 아래 그림과 같이 크롬 개발자 도구의 네트워크 탭에서 Size 필드에 (memory cache)로 보이는 이미지는 캐시 되었기 때문에 네트워크를 통해 가져오지 않는 것을 볼 수 있습니다.

HTTP 캐시

아래 그림에서 cache-control, expires 등의 응답 헤더를 사용하여 캐시를 제어할 수 있습니다.

HTTP 캐시 응답 헤더

요청 메서드를 하나씩 살펴보도록 하겠습니다.

GET

GET은 가져오다 라는 뜻으로 단어의 뜻처럼 서버에서 데이터를 가져오기 위해 사용되는 메서드입니다. GET 메서드 요청은 데이터를 가져오기만 하고 데이터를 수정(혹은 생성)하는 등의 다른 역할을 해서는 안됩니다.

GET 메서드 요청은 URL에 요청에 필요한 정보를 담기 때문에 북마크나 링크 공유가 가능합니다. 또한 캐시가 가능하기 때문에 동일한 GET 메서드 요청을 할 경우 네트워크 비용을 줄일 수 있습니다.

HEAD 메서드 요청은 GET 메서드 요청을 했을 때 응답 받을 헤더를 요청하는 메서드입니다. HEAD 메서드 요청에 대한 응답에는 메시지 본문이 포함되지 않습니다. 하지만 Content-Length 헤더처럼 응답 메시지 본문의 정보를 담는 헤더는 포함될 수 있습니다.

서버가 정상 동작하는지 확인(Health Check)하거나 Content-Length 헤더를 확인하여 응답 받을 메시지의 크기를 알아내는 등의 헤더 정보를 통한 메타 정보를 조회할 때 유용한 메서드입니다.

POST

POST 메서드는 보통 서버에 새로운 데이터를 생성할 때 사용되지만, 메일을 보내야 하는 요청 같은 CRUD(Create, Read, Update, Delete)로 정의하기 힘든 요청에도 POST 메서드를 사용하기도 합니다.

POST 메서드 요청은 서버에 새로운 데이터를 추가하기 때문에 안전한 메서드가 아닙니다. 또한 매번 요청할 때마다 서버에 새로운 데이터를 추가하기 때문에 멱등 메서드 역시 아닙니다.

HTML의 <form> 태그의 action 속성으로 POST로 설정하면 <form> 태그 안에 <input> 태그 데이터들이 메시지 본문에 담겨 POST 메서드 요청을 하게 됩니다.

POST 메서드 요청은 Cache-Control 헤더나 Expires 헤더가 정확히 정의되어 있다면 캐시가 가능합니다.

PUT

PUT 메서드 요청은 요청 데이터에 해당되는 데이터가 서버에 존재한다면 서버 데이터 값을 요청 데이터로 대체 하고, 데이터가 존재하지 않는다면 데이터를 생성합니다. 데이터를 생성하게 되면 응답 코드로 201 (Created)를 응답해야 하고, 수정이 되었다면 응답 코드로 200 (OK) 또는 204 (No Content)를 응답해야 합니다.

데이터가 없다면 생성하고 있다면 대체 하기 때문에 N번 요청을 하더라도 동일한 결과가 나타나는 멱등 메서드입니다. 하지만 PUT 메서드 요청은 캐시 되지 않습니다.

DELETE

DELETE 메서드 요청은 데이터를 삭제하기 위해 사용되는 메서드입니다.

CONNECT

CONNECT 메서드는 클라이언트가 프록시를 통해 서버와 SSL 통신을 할 때 사용됩니다. HTTP의 CONNECT 메서드는 HTTP 터널링의 가장 일반적인 형태입니다.

OPTIONS

OPTIONS 메서드는 유효한 요청인지 확인하기 위한 예비 요청(Preflight request)으로 사용되는 메서드입니다. CORS에서 사용되는 예비 요청에 대한 자세한 내용은 [Browser] CORS란?을 참고 부탁드립니다.

TRACE

TRACE 메서드는 웹 브라우저가 보내는 HTTP 요청을 HTTP 응답의 body에 담아 응답하는 역할을 합니다. 아래 그림과 같이 HTTP 요청을 보내고 응답을 받습니다.

HTTP TRACE

HTTP 요청 데이터를 HTTP 응답의 body에 담아 응답하기 때문에 요청 메시지가 변조되었는지 확인하기 위한 용도로 사용되는 메서드입니다.

PATCH

PATCH 메서드는 데이터를 업데이트 한다는 의미에서 PUT 메서드와 유사하지만 PUT 메서드는 전체 수정(대체), PATCH 메서드는 부분 수정을 의미합니다. PATCH 메서드는 수정하려고 하는 데이터를 찾을 수 있는 식별자와 수정하려는 값만 요청 데이터에 담아 보내면 되기 때문에, PUT 메서드 보다 네트워크 비용을 줄 일 수 있습니다.

PATCH 메서드는 PUT 메서드와 달리 멱등 메서드가 아닙니다. 예를 들어 블로그의 방문자 수를 카운트 해야 할 경우 새로운 데이터를 추가하는 것도, 전체 데이터를 교체하는 것도 아니기 때문에 PATCH 메서드를 사용할 수 있는데, 방문자 수를 카운트하는 PATCH 메서드를 10번 호출한다면 10명의 방문자가 카운트 되어 호출 할 때 마다 다른 결과가 나타나기 때문에 멱등 메서드가 아니게 됩니다.

요청 헤더

요청 헤더는 Host와 같은 표준 헤더와 X-Request-With와 같은 Custom의 약자인 X-로 시작하는 비표준 헤더가 있습니다. 요청 헤더는 자유롭게 만들어 사용할 수 있기 때문에 X-로 시작하지 않는 비표준 헤더를 만들어 사용할 수 있습니다.

요청 헤더 목록에서 표준 헤더와 자주 사용되는 비표준 헤더의 목록을 더 자세히 확인할 수 있습니다.

HTTP 응답 메시지

서버에서 클라이언트로 보내는 응답 메시지의 구조와 응답 코드, 응답 헤더에 대해 살펴보겠습니다.

응답 메시지 구조

요청 메시지는 아래 그림과 같이 상태 라인, 응답 헤더, 빈 라인, 메시지 본문 4가지로 구성됩니다.

응답 메시지 구조

4가지 구조를 하나씩 살펴보도록 하겠습니다.

상태 라인(Status line)

HTTP/1.1 200 OK 이 부분이 상태 라인입니다. 상태 라인의 구조는 아래 그림과 같습니다.

상태 라인 구조

0개 이상의 응답 헤더(Response header fields)

아래 코드 부분이 응답 헤더입니다.

Content-Type: text/html; charset=UTF-8
Content-Length: 155

응답 헤더의 구조는 요청 헤더의 구조와 동일합니다.

요청 헤더 구조

응답 메시지의 헤더와 동일하게 헤더 이름은 대소문자를 구분하지 않습니다. 즉, content-type: text/html; charset=UTF-8Content-Type: text/html; charset=UTF-8는 동일한 헤더입니다.

빈 라인(Empty line)

요청 메시지의 빈 라인과 동일하게 <CR><LF>으로만 구성된 줄입니다.

메시지 본문(Message body) - 선택

응답 메시지의 본문은 요청 메시지와 동일하게 보내도 보내지 않아도 되는 선택적인 부분입니다. 메시지 본문에 어떤 형태의 데이터가 담겨 있는지는 Content-Type 헤더를 통해 알 수 있습니다.

응답 코드

응답 코드는 클라이언트의 요청에 대한 서버의 결과를 나타내는 3자리 정수입니다. 404 Not Found와 같이 응답 코드와 응답 결과에 대한 이유를 함께 클라이언트로 전달합니다. 응답 결과에 대한 이유는 권장 사항이기 때문에 서버에서 전달 받은 문구가 아닌, 클라이언트에서 서비스에 따라 적당한 문구를 사용자에서 얼마든지 제공할 수 있습니다.

응답 코드는 아래와 같이 크게 5가지로 그룹 지을 수 있습니다. 응답 코드 목록에서 더 자세한 응답 코드 설명을 참고 바랍니다.

  • 1xx (응답 정보): 요청이 접수되어 요청이 진행 중인 것을 나타내는 코드 영역입니다.
  • 2xx (성공): 요청이 성공적으로 수행되었다는 것을 나타내는 코드 영역입니다.
  • 3xx (리다이렉션): 요청을 완료하려면 추가 조치가 필요하다는 것을 나타내는 코드 영역입니다.
  • 4xx (클라이언트 에러): 요청에 잘못된 내용이 포함되어 있어 요청을 수행할 수 없다는 것을 나타내는 코드 영역입니다.
  • 5xx (서버 에러): 올바른 요청을 보냈지만 서버가 수행하지 못했다는 것을 나타내는 코드 영역입니다.

응답 헤더

응답 헤더는 요청 헤더와 동일하게 Content-Type과 같은 표준 헤더와 Custom의 약자인 X-로 시작하는 비표준 헤더가 있습니다. 응답 헤더 역시 자유롭게 만들어 사용할 수 있기 때문에 X-로 시작하지 않는 비표준 헤더를 만들어 사용할 수 있습니다.

응답 헤더 목록에서 표준 헤더와 자주 사용되는 비표준 헤더의 목록을 더 자세히 확인할 수 있습니다.

부록

GET 메서드 요청에서 Body

보통 GET 메서드 요청은 메시지 본문(Message body)을 사용하지 않고 https://beomy.github.io/?id=1234에서 ?id=1234와 같이 URL의 ?문자 뒤에 오는 쿼리를 사용하여 데이터를 서버로 전달하는 방법을 사용합니다. GET 메서드 요청은 URL에 데이터를 담아야 한다는 내용은 RFC2616의 9.3 GET에 아래와 같이 명시 되어 있습니다.

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

그리고 RFC2616의 4.3 Message Body에 아래와 같이 메시지 본문에 대해 설명하고 있습니다.

if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.

이 설명에 의하면 GET 메서드의 데이터는 쿼리를 통해 전달되기 때문에 GET 요청에서 메시지 본문은 무시되어 서버에 전달되지 않게 됩니다. 이 설명은 RFC7230의 3.3 Message Body에서 삭제 되고, 아래와 같은 설명이 추가 되었습니다.

Request message framing is independent of method semantics, even if the method does not define any use for a message body.

메서드의 의미와 메서드 본문의 의미가 독립적으로 구분되면서 GET 메서드 요청에도 메시지 본문을 사용하는 것이 가능해졌습니다. 하지만 클라이언트나 서버에서 GET 메서드의 메시지 본문을 무시하는 경우가 종종 있기 때문에, GET 메서드에서 메시지 본문을 사용할 때 주의가 필요합니다.

HTTP 헤더

HTTP 헤더는 클라이언트와 서버가 요청/응답에 대한 부가적인 정보를 전달할 수 있도록 해줍니다. HTTP 헤더는 대소문자를 구분하지 않는 헤더 이름과 콜론(:), 그 다음에 오는 헤더 값(헤더 값 앞에 붙은 빈 문자열은 무시됩니다)으로 이루어져있습니다. 비표준 헤더는 Custom의 약자인 X-를 붙여 사용했지만(RFC 6648) 비표준 헤더가 표준이 됬을 때 X-를 제거해야 하는 불편함이 있어 2012년 6월 폐기 되었습니다.

컨텍스트에 따른 그룹

HTTP 요청/응답 컨텍스트에 따라 Request 헤더, Response 헤더, Representation 헤더, Payload 헤더 4가지로 분류할 수 있습니다.

  • Request 헤더: 대표적으로 Accept-로 시작하는 헤더들 입니다. 요청 메시지에 대한 정보를 담는 헤더로 서버에서 응답 메시지를 만들 때 사용됩니다. 요청 메시지에 포함된 헤더를 모두 Request 헤더로 생각할 수 있지만, 요청 메시지에 포함된 헤더가 모두 Request 헤더는 아닙니다. 예를 들어 Content-Type 헤더의 경우 Representation 헤더입니다.
  • Response 헤더: Location이나 Server 헤더와 같이 서버의 정보나 위치 등을 담는 헤더입니다. Request 헤더와 동일하게 응답에는 Content-Type 헤더를 포함 할 수 있기 때문에 응답 메시지에 포함된 헤더들이 모두 Response 헤더는 아닙니다.
  • Representation 헤더: Content-Type, Content-Encoding 헤더와 같이 HTTP 메시지 본문에 담긴 데이터의 정보를 담는 헤더입니다. Representation 헤더는 요청과 응답 메시지에 모두 포함될 수 있습니다.
  • Payload 헤더: 페이로드(payload)는 전송되는 데이터를 말합니다. payload 헤더는 전송을 위한(또는 전송되는 데이터)를 위한 헤더입니다. payload 헤더로는 전송되는 데이터 길이를 나타내는 Content-Length, 어떠한 형태로 전송 되는지 나타내는 Transfer-Encoding 등이 있습니다.

프록시 처리 방법에 따른 그룹

프록시 서버에서 헤더를 어떻게 처리하느냐에 따라 End-To-End 헤더, Hop-by-Hop 헤더로 분류할 수 있습니다.

  • End-to-End 헤더(종단간 헤더): 최종 수신자(요청 메시지의 경우 서버, 응답 메시지의 경우 클라이언트)에게 전송되는 헤더입니다. 중간 중간에 있는 프록시 서버는 이런 헤더를 수정하지 않고 그대로 재전송해야 하고, 캐시 해야 합니다. Content-Encoding 헤더는 End-to-End 헤더입니다.
  • Hop-by-Hop 헤더(홉간 헤더): 한번의 전송(한번의 홉)에만 유효한 헤더입니다. 프록시 서버는 이 헤더를 재전송하거나 캐시 하면 안됩니다. Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade 헤더가 홉간 헤더입니다.
Hop

홉(Hop)은 깡충깡충 뛰다 라는 뜻을 가진 단어입니다. 클라이언트와 서버 사이에는 게이트웨이나 캐시 역할을 하는 프록시, 라우터, 모뎀 등 많은 것들이 있습니다. 네트워크에서 이야기하는 홉은 데이터가 이런 노드(게이트웨이, 프록시, 라우터, 모뎀 등등..)를 건너가는 모습을 비유적으로 표현한 단어입니다. 아래 그림을 참고하면 홉이 무엇인지 좀 더 쉽게 이해 하실 수 있습니다.

Hop

Content-EncodingTransfer-Encoding 헤더

Content-EncodingTransfer-Encoding 헤더는 비슷하지만 약간의 차이가 있습니다. Content-Encoding은 전달할 데이터를 어떠한 형태로 메시지 본문에 저장 할 지를 나타내고, Transfer-Encoding은 어떠한 형태로 전송 할 지를 나타냅니다. Content-Encoding은 저장에 Transfer-Encoding은 전송에 초점이 있습니다.

메시지 본문에 데이터가 어떤 형태로 저장되어 있는지 서버 끝 단(혹은 브라우저)까지 전송되어야 하기 때문에 Content-Encoding 헤더는 End-to-End 헤더입니다. 반면에 Transfer-Encoding 헤더는 데이터 전송에 초점이 있기 때문에 서버와 서버 간(홉 간)에 어떠한 형태로 전송되는지는 알면 되는 Hop-by-Hop 헤더입니다.

참고
이전 포스트
[Browser] Cookie 톺아보기
다음 포스트
[ETC] HTTP 버전
© 2024 Beomy. All rights reserved.