HTTP란?
Hypertext Transfer Protocol의 의 줄임말로 웹 상에서 브라우저와 서버가 데이터를 주고 받을때 사용하는 프로토콜입니다. 애플리케이션 계층에서 작동됩니다.
HTTP 1.X 버전
Persistent connection과 Parallel connection
Persistent Connection이란 TCP Connection을 요청마다 Close하지 않고 재사용하여 통신하는 방법을 말한다. 보통 클라이언트는 웹페이지 내의 여러 데이터를 가져오기 위해 같은 서버에 여러 번 요청을 하게 되는 경우가 많다. (Site Locality)
parallel connection이란 병렬적으로 동시에 여러 connection을 맺는 것을 의미한다. 물론 parallel connection은 throughput을 늘려주긴 하지만 아래 3가지 문제가 존재한다.
1. 매번 매 요청때마다 새로운 connection을 open하고 close해야 하기 때문에 네트워크 연결에 더 많은 시간과 bandwidth가 소모된다.
2. TCP slow start로 인해한 성능 저하.
※ TCP Slow Start란? : TCP 커넥션은, 시간이 지나면서 점차 데이터 전송 속도 제한을 풀어간다는 특징이 있음. (TCP 커넥션 튜닝) 따라서, TCP 커넥션은 처음 맺어졌을 떄 속도가 가장 느리다는 뜻
3. parallel connection을 맺을 수 있는 수의 제한
따라서, Persistent Connection + Parallel Connection을 합쳐서 쓰면 굉장히 효과적임
HTTP 1.0
기본적으로 한 Connection 당, 하나의 Request를 처리하도록 디자인됨 → RTT 증가 초래
※ RTT란? : 패킷이 목적지에 도달하고 나서 다시 출발지로 돌아오기 까지 걸리는 패킷 왕복 시간
기본적으로 HTTP/1.1은 persistent connection을 지원하는 반면에 HTTP/1.0 connection은 하나의 request에 응답할 때마다 connection을 close하도록 설정됨 → 이에 따라 연속적으로 여러 request를 보낼 때마다 계속해서 TCP-3way-handshake를 맺어야 됨 →. 부하 발생
keep-alive옵션을 활용하면 persistent하게 connection을 유지할 수 있도록하여 불필요한 연결의 맺고 끊음을 최소화시켜 네트워크 부하를 아래 사진처럼 줄일 수 있음 (Http 1.0+ 부터 지원)
하지만, Keep-Alive는 Http 표준에 포함되지 못하였다. 호완성 문제가 발생했기 때문이다.
※ Dumb Proxy 문제 : keep-alive옵션을 지원하지 않는 proxy는 Connection header를 이해하지 못하고 그냥 extension header로 인식하는 경우에는 제대로 동작하지 않는 문제 (https://datatracker.ietf.org/doc/html/rfc2616#section-8.1.2)
HTTP 1.1
일반적으로 우리가 제일 많이 쓰는 버전이다. 정적 파일 가져오기, REST, GraphQL, Websocket Connection 수립 등에 사용된다. (REST, GraphQL은 반드시 HTTP 1.1을 써야되는 것은 아닙니다! HTTP/2를 이용할 수도 있어요)
기본적으로 HTTP/1.1은 persistent connection을 지원한다. 즉, Keep-Alive Header를 포함하지 않더라도 기본적으로 모든 Connection이 Keep-Alive 상태인 것이다! 즉, Connection : Keep-Alive Header가 불필요하다.
HTTP/1.1 어플리케이션들은 connection을 close하기 위해 명시적으로 Connection: close 헤더를 입력해야 한다. Client는 별도로 Connection: close 헤더가 있지 않는한 서버가 응답한 뒤에 계속해서 재사용할 수 있다고 가정한다. 물론 별도로 보내지 않더라도 서버나 클라이언트측에서 connection을 종료할 수는 있다. 이때, connection이 계속해서 persistent하게 유지되기 위해서는 모든 요청에 오류가 없어야 함. content-length가 다르다던지, encoding이 되지 않았다던지 하면 유지되지 않는다.
하지만, HTTP/1.1을 사용하더라도 특정 Request는 keep-alive Header를 사용한다. 그 이유는 간혹 Http/1.0을 사용하는 브라우저, 클라이언트 등이 존재해서 하위 호완성을 보장해주기 위함이다. 즉, 혹시 몰라서 보내는 것이다.
파이프라인 지속 커넥션
하나의 TCP 커넥션을 통하여 병렬적으로 HTTP 요청을 하는 것이 HTTP/1.1의 파이프라인 커넥션이다. 즉, Persistent Connection + Parallel Connection임.
Parallel Connection은 다수의 커넥션을 시도했을 때, 메모리를 많이 소모하고 자체적인 성능 문제를 발생 시킨다. 따라서 브라우저가 수백개의 커넥션을 허용하지 않는다. 왜냐하면 서버는 다른 여러 사용자의 요청도 처리해야 되고, 인터넷 대역폭의 한계가 있기 때문이다. 일반적으로 Chrome, Firefox, Microsoft Edge등은 6개까지 허용한다.
파이프라인 커넥션에는 몇가지 제약이 있다.
- HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안됨 → 파이프라이닝은 지속 커넥션을 전제로 하기 때문
- HTTP 응답은 요청 순서와 같게 와야함 → 순서에 맞게 정렬시킬 방법이 없기 때문. 아니면 요청에 따른 응답의 일관성이 깨짐
- HTTP 클라이언트는 언제 연결이 끊어지더라도 완료되지 않은 요청이 파이프라인에 있으면 언제든 다시 요청을 보낼 준비가 되어 있어야 함.
- 비멱등한 요청은 파이프라인으로 보내서는 안됨. (ex : POST) → 에러가 발생하면 파이프라인을 통한 요청 중 어떤 것이 서버에서 처리되었는지 클라이언트는 알 수 없고, POST 같은 비멱등 요청을 재차 보내면 문제가 생길 수 있기 때문 ex) 클라이언트가 POST 요청 A, B, C를 순서대로 서버에 보낸다고 가정 → B가 실패했는데 클라이언트는 뭐가 처리 됐는지 알 수 없음. 그래서 A,B,C를 전부 다시 보낸다면 동일한 작업이 두번 실행될 수 있음
HTTP/1.1의 단점
1. HOL Blocking (Head Of Line Blocking) : 네트워크에서 같은 큐에 있는 패킷이 첫번째 패킷에 의해 지연될 때 발생하는 현상
2. 무거운 헤더 구조 : HTTP/1.1는 여러 발전을 해오면서 헤더에 많은 메타 정보들을 저장하게 되었다. 사용자가 방문한 웹페이지는 다수의 http요청이 발생하게 되는데, 이 경우 매 요청시 마다 중복된 헤더값을 전송하게 됨. 또한 해당 domain에 설정된 cookie 정보도 매 요청시 마다 헤더에 포함되어 전송되어 성능을 저하시킴.
※ TCP의 Keep-Alive VS Http의 Keep-Alive
- TCP Keep-Alive 패킷은 실제 데이터가 아닌, 연결이 여전히 활성 상태인지 확인하기 위한 신호임. 일정 시간 동안 아무 데이터도 전송되지 않으면, TCP는 상대방에게 Keep-Alive 패킷을 보냄. 상대방이 응답하지 않으면, 연결이 끊어졌다고 간주하고, 해당 TCP 연결을 종료할 수 있음.
- HTTP Keep-Alive 헤더(Connection: keep-alive)를 통해 클라이언트는 서버에게 TCP 연결을 일정 시간 동안 유지하도록 요청합니다. 클라이언트는 해당 연결을 통해 여러 HTTP 요청을 보내고, 서버는 동일한 연결을 사용하여 응답을 반환함. 특정 시간 동안
- 즉, TCP Keep-Alive은 죽은 peer(유령 세션, 좀비 Connection)등을 걸러내기 위함임. 한쪽 시스템이 다운 됐는데, 반대쪽 시스템은 정상 작동하는줄 알고 계속 tcp 소캣을 열어둘 수 있음. 이를 방지하기 위한 것이 TCp Keep-Alive
- TCP keep-alive는 OS단에서, Http Keep-Alive는 서버 단에서 설정함.
실제 개발에서 HTTP 1.1의 활용 프로토콜
GraphQL : 아래 사진처럼 한 화면에서 다양한 정보를 보내주기 위해서 REST는 각각 API를 만들어야 했지만, SQL Query처럼 클라이언트가 필요한 정보만 받을 수 있도록 고완된 통신 방식
Websocket : 양방향 프로토콜 : 클라이언트 ↔ 서버간 양방향으로 통신할 수 있도록 고안된 프로토콜 → Websocket은 처음 Connection을 수립하기 위하여 Http 1.1을 이용함 → 이외에도 양방향이 필요 없을 경우 서버 → 클라이언트로 단방향으로 정보를 전송할 수 있는 SSE 방법도 있음
REST(Representational State Transfer): HTTP URI를 통해 자원을 식별하고, HTTP 메서드를 통해 자원에 대한 조작을 수행하는 방식 (Stateless)
- GET, POST, PATCH, PUT: CRUD에 관련된 HTTP Method
- HEAD: GET Method에서 응답 본문을 제외한 헤더만 가져오는 Method
- TRACE: 클라이언트가 보낸 요청이 서버로 전달되는 동안 어떻게 변형되었는지 확인하기 위한 디버깅 목적으로 사용되며, 보안 문제로 인해 대부분의 서버에서 비활성화된 Method
- OPTIONS: 서버가 특정 URI에 대해 허용하는 HTTP 메서드를 확인할 때 사용하는 매서드, 주로 Preflight Reqeust를 보내서 CORS를 허용하는지 확인할 때 사용함
※ Preflight Request: 실제 요청을 보내도 안전한지 판단하기 위해 사전에 보내는 요청, OPTIONS 메서드를 사용함. 아래 조건들 중 하나라도 만족하지 않으면 Preflight 요청을 보냄.
1. GET, HEAD, POST 요청 중 하나
2. user agent에 의해 자동으로 설정되는(Connection, User-Agent, Fetch 스펙상 forbidden header로 정의되어 있는) 헤더외에 CORS-safelisted request-header로 명시된 헤더들만 포함된 경우(Accept, Accept-Language, Content-Language, Content-Type 등)
3. Content-Type은 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용
※ CORS: 웹 페이지가 자신이 로드된 출처(origin)와 다른 출처의 리소스에 안전하게 접근할 수 있도록 브라우저 단에서 이를 제어하고 막는 웹 보안 메커니즘
※ HATEOAS란? : 동적인 API를 제공할 수 있게됨.(모든 관련된 동작을 URI를 통해 알려줍니다.) 즉, 클라이언트가 API의 변화에 일일이 대응하지 않아도 된다는 장점 → 현재 요청한 API에서 다음으로 취할 수 있는 행동들의 링크를 넣어주는 형태 → 이를 통해서 상태 전이가 가능하다.
HTTP 2.0
SPDY
Latency 문제의 해결을 집중하며 구글이 개발한 HTTP를 고속화한 새로운 프로토콜 → 추후 HTTP/2 초안의 참고 규격이 됨
HTTP 2.0의 특징
1. Text로 전송되던 HTTP/1.1과 달리, Binary Frame로 인코딩되어 전송 : 바이너리 프로토콜(Binary Protocol)은 텍스트 기반 프로토콜과 달리 데이터를 이진 형태로 전송하는 방식. 데이터 파싱이 더 빠르고, 오류 발생 가능성이 낮아짐 → 텍스트 기반 데이터는 공백, 줄바꿈, 구두점 등을 포함하기 때문에 사람이 읽기에는 효율적일지라도, 컴퓨터가 처리하기에는 좋지 않음. 바이너리 프로토콜을 통하여, 데이터를 이진 형식으로 직렬화하여 전송하기 때문에 매우 빠르고, 오류 발생 가능성이 매우 낮아짐.
2. 헤더 압축 : HTTP/1.1과 달리 헤더를 압축해서 보냄. 이는 허프만 코딩 압축 알고리즘을 사용하는 HPACK 압축 형식으로 헤더를 압축함. 점점 헤더의 양은 커지고 있어서, 전송해야되는 데이터 양을 줄이기 때문에 매우 효율적임
※ 허프만 코딩 : 문자열을 문자 단위로 쪼개 빈도수를 세어 빈도가 높은 정보는 적은 비트 수를 사용하여 표현하고, 빈도가 낮은 정보는 비트 수를 많이 사용하여 표현해서 전체 데이터의 표현에 필요한 비트양을 줄이는 원리
3. Muliplexed Streams : 스트림이란 연결된 Connection 내에서 양방향으로 Message를 주고 받는 하나의 흐름임. 하나의 TCP 연결을 통하여 여러 데이터 요청을 병렬로 전송 가능 → 여러개의 스트림이 동시에 열리는 것. 스트림은 우선순위를 가질 수 있음 (ex: 이미지보다는 html 파일에 더 높은 우선순위 부여). 응답 순서에 상관 없이 Stream으로 주고 받음 → 모든 스트림은 31비트의 무부호 정수로 된 고유 식별자를 갖기 때문임
※ HTTP/1.1 PipeLining이란 차이가 무엇인가? : 파이프라인은 응답 순서가 보장되야함. 또한, 파이프라인을 지원하지 않는 브라우저도 많고, 에러처리도 복잡함. 이러한 단점이 있어서 널리 쓰이지 않음, 결국 HOLB 문제가 있기 때문
4. HTTPS 위에서 작동 : HTTP/2는 기본적으로 HTTPS 위에서 작동함
5. Stream Prioritization : 클라이언트가 선호하는 응답 수신 방식을 지정해서 응답을 받을 수 있음. 문서 내에 CSS 파일 1개와 이미지 파일 2개가 존재하고 이를 클라이언트가 요청한다고 가정→ 이미지 파일보다 CSS 파일의 수신이 늦어진다면 브라우저 렌더링에 문제가 생기게 될 수 있는데, HTTP/2에서는 이러한 상황을 고려하여 리소스 간의 의존관계에 따른 우선순위를 설정하여 리소스 로드 문제를 해결 가능
HTTP 2.0을 활용하는 GRPC에 대해서는 아래 글 참고!!
HTTP/2.0 의 단점
1. 긴 커넥션 유지로 인한 개인정보 누출 우려 : HTTP/2.0은 1.1보다 훨씬 긴 시간 커넥션을 유지한다. 어떤 사용자가 브라우저를 사용할 때 그 사용자는 이전에 브라우저를 사용했던 사용자가 무엇을 했는지 알아낼 가능성이 있음.
2.중개자 캡슐화 공격(Intermediary Encapsulation Attacks) : HTTP/2.0은 헤더 필드로 어떤 문자열이든 허용한다. (HTTP/2.0은 헤더 필드의 이름과 값을 바이너리로 인코딩 하기 때문) -> 따라서 HTTP/2.0 메시지를 중간의 프록시가 HTTP/1.1로 변환할 때 메시지가 위조될 가능성이 있다 (반대로 HTTP/1.1 -> 2.0으로 변환할 때는 이런 문제가 없음)
3.여전한 RTT : 여전히 TCP하에서 사용하기 때문에 Latency가 발생함.
HTTP 3.0
TCP가 아닌 UDP 사용함. 정확히 말하면 UDP를 개조한 QUIC라는 프로토콜을 사용함. 기존 TCP는 클라이언트와 서버 간에 세션을 설정하기 위해 핸드쉐이크가 필요하며, 인증서인 TLS도 세션이 보호되도록 자체 핸드셰이크도 필요함. 하지만 QUIC는 보안 세션을 설정하기 위해 한 번의 핸드셰이크만 필요함.
최신 기술이라 아직 많이 정보가 없고 많이 사용하지 않음
HTTPS
HTTPS는 애플리케이션 계층과 전송 계층 사이에 신뢰 계층인 SSL/TLS 계층을 넣은 신뢰할 수 있는 HTTP 요청을 말함. 즉, 통신의 암호화. HTTPS가 적용된 웹을 들어가보면 인증서 확인 가능.