클라이언트와 서버가 서로 데이터를 주고받기 위해 사용되는 통신 규약으로 다음과 같은 데이터 타입을 전송할 수 있다.
HTML, TEXT
이미지, 음성, 영상, 파일 등
JSON, XML
이렇게 거의 모든 형태의 데이터를 전송할 수 있으며 서버 간 데이터를 주고 받을 때 역시 HTTP 프로토콜을 사용한다.
HTTP의 특징
1. 클라이언트-서버 구조
위에서 HTTP 통신은 클라이언트와 서버가 서로 데이터를 주고 받는 형태로 되어있다고 하였다.
즉 클라이언트가 Request를 보내면 서버가 Response를 보내는 요청-응답 구조로 되어있다.
이렇게 클라이언트와 서버를 명확하게 구분함으로써 클라이언트는 UI를 그리는데 집중할 수 있고, 서버에서는 비즈니스 로직과 데이터를 다루는데 집중할 수 있게 된다.
2. 무상태(Stateless)
서버가 클라이언트의 상태를 유지하지 않음
즉 클라이언트가 서버와 통신을 할 때 필요한 상태 정보들은 서버가 관리하는 것이 아니라 통신할 때 마다 상태 정보를 실어서 요청을 보내는 것을 뜻한다.
서버는 이러한 요청을 받아서 응답만 해주므로 상태 유지에 대한 부하가 줄고, 서버가 여러 대 있을 때 로그인 했을 때 해당 서버가 아닌 이후 다른 서버와 통신을 해도 문제가 없음을 뜻한다.
→ 위처럼 서버는 상태를 기억하지 않고 매 요청마다 필요한 상태를 모두 보내주기 때문에 서버 확장성 높다.(scale-out) 하지만 클라이언트가 데이터를 추가적으로 전송한다.
Stateless의 대표적인 프로토콜로는 UDP, HTTP가 있다.
UDP의 경우 TCP와는 다르게 3-way handshake를 수행하는 절차가 없고 서버의 상태와 관계없이 그냥 데이터를 전송하므로 Stateless이다.
근데 HTTP는 응용 계층에 존재하는데 이는 전송 계층인 TCP나 UDP의 상위 계층으로 이를 응용한 프로토콜을 뜻한다.
HTTP 1.1, HTTP 2.0의 경우 TCP를 사용하는데 어떻게 Stateless일까?
HTTP는 클라이언트가 서버에 요청을 보낼 때마다 새로운 연결을 설정하고 요청을 처리한다. (3-way handshake를 한 후 요청, 응답을 받으면 바로 연결을 끊음.)
이 때 각 요청은 독립적으로 처리되며, 이전 요청의 상태나 정보를 기억하지 않기 때문에 서버가 클라이언트의 상태를 유지할 필요가 없다는 의미이다.
이렇게 무상태로 설계하는 것이 좋지만 로그인을 지속하는 경우에는 상태를 유지해야 하는 등 어쩔 수 없이 상태 유지를 해야 하는 경우가 존재한다 물론 이런 경우 쿠키와 세션, 토큰 등을 사용해서 Stateless 프로토콜인 HTTP에서 상태를 유지할 수 있다.
즉 상태 유지를 최소한으로 사용하고 무상태를 지향해야 한다.
무상태(Stateless)와 비연결성(Connectionless)
Stateless : 필요한 상태 정보를 클라이언트가 요청마다 담아 전송하므로 클라이언트의 요청에 어느 서버가 응답해도 상관 없음
Connectionless : 클라이언트가 서버에 요청을 한 후 응답을 받으면 바로 TCP / IP 연결을 끊어 연결을 유지하지 않는다는 의미
Stateless ↔ Stateful
Stateful은 상태를 유지한다는 의미로 대표적으로 TCP의 3-way handshake가 그렇다.
Stateless와 다르게 매 요청마다 클라이언트가 상태를 보내는 것이 아니라 서버가 클라이언트의 상태를 기억하고 있다는 것을 의미한다. 즉 여러 대의 서버가 있을 경우에 한 서버와 연결 했을 경우 다른 서버와는 통신이 어려우며 이로 인해 확장이 아닌 Scale up의 방식에 적합하다.
Scale out (서버 대수를 늘림) ↔ Scale up (장비 사양을 올림)
HTTP 발달 과정
HTTP 1.1 → Persistent connection 예를 들어 홈페이지 하나를 렌더링하는데 무수히 많은 요청을 보내고 응답을 받아야 한다. (이미지, js, css 파일 등등..) HTTP 1.1에서는 Persistent connection 이라고 해서 모든 요청에 handshake를 하는 것이 아니라 최초에 연결을 수립하고 모든 요청을 처리하면 연결을 끊는 식으로 종료 신호를 고의적으로 늦추어 연결을 지속하는 식의 방식을 도입하였다. (3-way handshake로 연결 수립 → 모든 요청에 대한 응답을 받음 → 마지막 데이터 전송 후 보통 10초 정도 연결 유지 후 종료) 즉 HTTP는 기본적으로 비연결성이라는 특징을 가지지만 일정 시간 연결을 가진다. HTTP 헤더에 Connection: Keep-Alive를 적용하면 된다.
HTTP 1.1 → Pipelining
기존에는 하나의 Request를 보낸 후 이 요청에 대한 응답이 발생한 뒤에야 다른 Request를 보낼 수 있었다. 이는 홈페이지 하나를 렌더링 하는 경우에 여러 Request를 보낼 경우 지연이 발생하게 되는데 HTTP 1.1에서는 Pipelining으로 아직 응답을 받지 않았어도 연속으로 요청을 보낼 수 있다.
단 요청은 순서대로 처리되어야 하며, 이 때문에 오래 걸리는 요청의 경우 이후에 병목이 발생할 수 있다.
HTTP 2.0 → Multiplexed Streams 여러 흐름(streams)들이 하나의 연결 안에서 묶여서 처리되는 것으로 HTTP 1.1 Pipelining 기술에서 여러 Request를 보낼 경우 응답을 순서대로 받아야 하므로 지연이 발생할 수 있었는데 이는 여러 요청을 동시에 처리할 수 있어, 요청의 지연이 다른 요청에 영향을 미치지 않아 성능과 효율성이 향상되었다.
HTTP 2.0 → Server Push HTTP 1.1에서는 연관되어 있는 정보라도 각각 요청을 만들어서 보내야했다. 2.0에서는 이런 방식이 아니라 한 요청에 연관되어 있는 정보를 서버가 알아서 전송해주는 Server Push를 지원한다.
HTTP 3.0 HTTP/3.0의 경우 아예 TCP가 아닌 UDP 방식으로 데이터를 전송함으로써 3-way handshake 자체를 하지 않는다.
Start line : HTTP version, Status Code(HTTP 상태 코드), Status Message(상태 코드에 대한 결과 메세지)
Header : HTTP 전송에 필요한 부가 정보
Empty line : 헤더와 바디 구분
Body : 전송받은 데이터
HTTP 메서드 종류
주 메서드
GET : 리소스 조회, Body 부분에 데이터 전달할 수 있지만 권장하지 않고 데이터 전달 시에 쿼리 스트링, 쿼리 파라미터를 통해 전달
POST : 메세지 바디를 통해 요청 데이터를 전달하고 서버가 이를 처리, 주로 등록에 사용
단순히 데이터 생성을 넘어 프로세스 처리해야 하는 경우
값 변경 시에도 단순 값 변경이 아니라 프로세스의 상태가 변경되는 경우
다른 메서드로 처리하기 애매할 때 사용함
PUT : 리소스 대체(덮어버림), 해당 리소스가 없을 시에는 생성
클라이언트가 리소스 위치를 알고 있는 경우
PATCH : 리소스 일부 변경
DELETE : 리소스 삭제
기타 메서드
HEAD: GET과 동일하지만 메시지 부분을 제외하고, 상태 줄과 헤더만 반환
OPTIONS: 대상 리소스에 대한 통신 가능 옵션(메서드)을 설명(주로 CORS에서 사용) CORS란?
CONNECT: 대상 리소스로 식별되는 서버에 대한 터널을 설정
TRACE: 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행
HTTP Method
요청에 바디 존재 여부
응답에 바디 존재 여부
안전
멱등
캐시 가능
GET
X
O
O
O
O
POST
O
O
X
X
Conditional (조건부)
PUT
O
O
X
O
X
PATCH
O
O
X
X
Conditional (조건부)
DELETE
X
가능하지만 권장 X
X
O
X
안전(Safe)
호출해도 리소스를 변경하지 않음을 의미. 장애와 같은 외적인 요소는 배제
멱등
한 번 호출하나 여러 번 호출하나 결과가 똑같음을 의미
GET: 몇 번 조회해도 같은 결과가 조회된다. (중간에 값이 변경되는 상황은 배제)
PUT: 결과를 대체하므로 같은 요청을 여러 번 해도 최종 결과는 같음.
DELETE: 결과를 삭제하므로 같은 요청을 여러 번 해도 삭제된 결과는 같음.
POST: 멱등이 아님 → 두 번 호출 시 결제가 중복해서 발생할 수 있음.
캐시 가능(Cacheable)
응답 결과 리소스를 캐싱해도 되는가?
GET, HEAD, POST, PATCH가 캐시 가능하지만, Message Body 부분의 캐시 키의 복잡성 문제로 실제로는 GET, HEAD만 사용
POST 요청은 본문 데이터 때문에 캐싱이 복잡하여 일반적으로 캐싱되지 않지만 특수한 경우에 캐싱을 해야한다면, URL과 본문 데이터를 조합하여 고유한 캐시 키를 생성해야 함. (Conditional 조건부)
데이터의 변경 여부를 보고 결정하면 된다. 캐싱은 데이터가 자주 바뀌는 경우 사용에 적합하지 않다.
GET은 단순 조회이므로 데이터를 변경하지 않으므로 캐싱에 적합.
(요청 파라미터 - 결과 페이지를 키-밸류 형태로 캐시로 저장하고 일정 기간 뒤에 캐시를 갱신하도록 구현함)
(갱신하는 이유 → 외적인 요소로 정보가 바뀔 수도 있음)
데이터를 변경할 가능성이 있는 POST, PUT, DELETE, PATCH는 대부분 캐시를 유지하지 않는다.
→ 멱등해야 캐싱에 적합함
HTTP 상태 코드
클라이언트가 보낸 요청의 처리 상태를 응답에서 코드로 알려주는 기능으로 다음과 같이 구분된다.
1xx (Informational): 요청이 수신되어 처리 중
2xx (Successful): 요청이 정상적으로 처리200 OK 요청 성공
201 Created
요청이 성공해서 새로운 리소스가 생성되었음
202 Accepted
요청이 성공적으로 접수되었으나 처리가 완료되지 않음 (배치 처리 등에서 사용)
204 No Content
서버가 요청을 성공적으로 수행했지만 응답 페이로드 본문에 보낼 데이터가 없음
3xx (Redirection): 요청을 완료하려면 추가 행동이 필요
3xx 응답 결과에 Location 헤더가 있으면 Location 위치로 자동 이동
영구 리다이렉션 (특정 리소스의 URI가 영구적으로 이동함)
301 Moved Permanently
리다이렉트 시 요청 메서드가 GET으로 바뀌고, 본문이 제거될 수 있다.
308 Permanent Redirect
301과 기능은 동일하지만 리다이렉트 시 요청 메서드와 본문을 유지함 (처음 POST를 보내면 리다이렉트도 POST를 유지한다는 의미)
301을 주로 사용함 308의 스펙에 따라 사용하는 경우는 드물다고 함.
why? 리소스 위치가 영구적으로 바뀌었다는 이야기는 요청 시 필요한 데이터도 바뀔 가능성이 높으므로
일시 리다이렉션 (일시적인 변경으로 PRG 패턴이 이에 해당)
302 Found
리다이렉트 시 요청 메서드가 GET으로 바뀌고, 본문이 제거될 수 있다.
307 Temporary Redirect
302와 기능은 동일하지만 리다이렉트 시 요청 메서드와 본문을 유지함 (요청 메서드를 변경하면 안된다)
303 See Other
302와 기능은 같지만 리다이렉트 시 요청 메서드가 GET으로 변경됨
처음 302 스펙의 의도는 HTTP 메서드를 유지하는 것이지만 웹 브라우저들이 대부분 GET으로 바꾸어버렸다. (아닌 경우도 존재)
따라서 302를 대신하는 명확한 307과 303이 등장함 (301 대응으로 308도 등장)
307, 303을 권장하지만 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용한다. 따라서 자동 리다이렉션 시 GET으로 변해도 되면 그냥 302를 사용해도 문제 없다.
특수 리다이렉션 (결과 대신 캐시를 사용)
304 Not Modified
캐시를 목적으로 사용하는 코드 클라이언트에게 리소스가 수정되지 않았다는 것을 알려주어서 클라이언트는 로컬 PC에 저장된 캐시를 재사용함. (캐시로 리다이렉트) 304 응답은 응답에 메시지 바디를 포함하면 안된다. (로컬 캐시를 사용해야 하므로) 조건부 GET, HEAD 요청 시에 사용함 로컬 PC에 저장된 캐시를 아직 지우지 않고 남겨둔 상태
4xx (Client Error): 클라이언트 오류나 잘못된 문법 등으로 서버가 요청을 수행할 수 없음
400 Bad Request
클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없음
401 Unauthorized
클라이언트가 해당 리소스에 대한 인증이 필요함
403 Forbidden
서버가 요청을 이해했지만 승인을 거부함, 권한 불충분
404 Not Found
요청 리소스를 찾을 수 없음
5xx (Server Error): 서버 오류, 서버가 정상 요청을 처리하지 못함
500 Internal Server Error
서버 내부 문제로 오류 발생
503 Service Unavailable
서비스 이용 불가 서버가 일시적인 과부하, 예정된 작업으로 잠시 요청을 처리할 수 없는 상태로 Retry-After 헤더 필드를 통해 얼마 뒤에 복구되는지 보낼 수 있음
HTTP 헤더
HTTP 헤더는 클라이언트와 서버가 HTTP 전송에 필요한 부가적인 정보를 요청이나 응답에 전송할 수 있도록 함
Representation 헤더: 표현 메타데이터(표현과 관련된 헤더) + 표현 데이터(메세지 본문)
기존 Entity 헤더 → Representation 헤더로 변경
표현은 요청이나 응답에서 전달할 실제 데이터(응답, 요청 둘 다 사용될 수 있음)이며, 이러한 표현 헤더는 표현 데이터를 해석할 수 있는 정보
예) Content-Type: text/html, Content-Length: 3423
1. General Header
Date - HTTP 메시지가 만들어진 시각으로 자동으로 생성 (Date: Thu, 12 Jul 2018 03:12:27 GMT)
Connection - HTTP 1.1에서 Persistent Connect 설정을 위한 것으로 기본적으로 아래와 같이 되어있으며 HTTP/2 부터는 사라졌다. (Connection: keep-alive)
Cache-Control - 캐시 관련 헤더
Cache-Conrtrol: max-age=초 → 해당 초만큼 캐시가 유효함
해당 시간이 지나면 서버를 통해 다시 데이터를 조회하고 캐시를 갱신
클라이언트에서 요청 시 Cache-Control : 클라이언트가 요청 시 서버에 원하는 캐시 정책을 전달 Cache-Control: no-cache: 클라이언트는 서버에 요청할 때 항상 최신 버전을 확인하고 싶다는 의미서버는 캐시된 리소스를 사용하기 전에 클라이언트의 요청에 따라 유효성을 검사해야 함. Cache-Control: max-age=0: 클라이언트는 캐시된 리소스를 사용하지 않고 서버에서 항상 최신 데이터를 요청하겠다는 의미.
서버에서 응답 시 Cache-Control : 클라이언트가 리소스를 어떻게 캐시하고 사용할 지를 지시
만약 캐시 유효 시간이 초과해서 데이터를 조회할 때 두 가지 경우가 있음
1. 서버에서 기존 데이터를 변경한 경우
2. 서버에서 기존 데이터를 변경하지 않은 경우
여기서 기존 데이터의 변경 여부를 아는 방법 → Last-Modified, ETag
Last-Modified가 왔을 때 :
캐시에 이 응답을 함께 저장하고 캐시 유효 시간이 초과되었을 때 요청에 if-modified-since 헤더에 캐시로 가지고 있는 데이터의 최종 수정일을 보냄.
아직 수정되지 않았다면 캐시된 데이터가 바뀌지 않은 상태이므로 HTTP Body를 전송하지 않고 304 Not Modified + 가벼운 헤더 메타 정보만 전송함. 여기서 얻은 max-age로 캐시를 다시 갱신시키고 캐시된 정보를 재사용함.
ETag가 왔을 때 :
응답에서 ETag 헤더의 값을 캐시로 저장하고 있는다. 그리고 요청에 If-None-Match 헤더에 해당 문자열을 담아서 전송한다.
아직 수정되지 않았다면 캐시된 데이터가 바뀌지 않은 상태이므로 HTTP Body를 전송하지 않고 304 Not Modified + 가벼운 헤더 메타 정보만 전송함. 여기서 얻은 max-age로 캐시를 다시 갱신시키고 캐시된 정보를 재사용 함.
Last-Modified의 경우에는 날짜 기반의 로직을 사용하며 1초 미만의 단위로 캐시 조정이 불가능.
클라이언트가 서버의 캐시 매커니즘을 알 수 있음 → 파일 수정 시 운영체제가 수정 날짜를 기록하는데 이를 가져와 사용하기 때문
ETag 경우에는 파일 수정 시에도 클라이언트에게 동일한 ETag를 제공할 수 있음.
즉 클라이언트가 서버의 캐시 매커니즘을 알 수 없음. 서버가 캐시 로직을 관리함.
따라서 스페이스, 주석과 같이 데이터 수정을 했지만 결과가 똑같은 경우와 같은 상황에서 용이.
위와 같은 헤더를 조건부 요청 헤더라 하며
조건이 만족하면 200 OK에 HTTP Body에 데이터 전송, 조건이 만족하지 않으면 304 Not Modified 응답에 캐시를 갱신시켜 재활용한다.