alt
Home HTTP 완벽가이드 4장 - 2. HTTP 커넥션
Post
Cancel

HTTP 완벽가이드 4장 - 2. HTTP 커넥션


Connection 헤더란?

  • 헤더 보호하기 : 한 Connection 작업에서만 사용되어야 하는 토큰 값들을 쉼표로 구분해 갖고 있는다.

    • 다른 커넥션(다른 홉)에 전달되는 것 방지

      1
      2
      3
      
      HTTP/1.1 200 OK
      Cache-control: max-age=3600
      Connection: meter, close, bill-my-credit-card
      
      • 위의 메시지에서 Connection: 부분에 명시한 것
  • Hop-by-Hop: 두 홉(노드) 사이에서만 전달되고 그 이후엔 없어져야 한다.(클라 - 프락시 or 프락시 - 서버 등)


병렬 커넥션

: HTTP 클라이언트가 여러 개의 TCP 커넥션을 맺어 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다.

  • 클라이언트 입장에서의 객체당 한 서버와 커넥션을 맺어 각 커넥션의 지연시간을 겹치게 해 처리 속도를 빠르게 하는 것

항상 빠르지는 않다.

  1. 대역폭이 좁으면 각 객체를 전송하는게 느리기 때문에 성능상 장점이 없다.
  2. 실제로 연결하는 병렬 커넥션 수에는 제한이 있다. (커넥션을 늘릴 수록 성능 문제)
    • 한 사용자 당 100개의 커넥션을 허용하고 100명의 사용자가 사용하면 서버는 10000개의 커넥션을 맺어야 하는데, 이 것은 큰 부하가 될 수 있다.
    • 따라서, 최신 브라우저는 대부분 6~8개 라고 함
  3. 연결을 맺고 끊음을 반복하다보니 TCP의 느린 시작을 피할 수 없다.


지속 커넥션

: HTTP 트랜잭션이 처리된 이후에도 커넥션을 끊지 않고 재사용하는 것

  • 사이트 지역성(site locality): 서버에 HTTP 요청을 하기 시작한 애플리케이션은 웹 페이지 내의 이미지 등을 가져오기 위해 그 서버에 또 요청을 할 것
  • 커넥션을 맺기위한 준비작업을 줄일 수 있고, TCP 느린 시작으로 인한 지연을 피할 수 있다.
    • 튜닝된 커넥션 : 느린시작에서 수차례 성공해서 다수의 패킷을 전송할 권한을 얻은 상태
  • HTTP/1.0의 Keep-Alive, HTTP/1.1의 지속 커넥션
  • 지속적으로 커넥션을 유지하는 상황이더라도, 양 측에선 언제건 끊을 수 있긴 하다.


HTTP/1.0의 Keep-alive 커넥션

  • TCP 핸드셰이크와 느린시작으로 인한 지연을 줄인다.
  • Keep-alive가 HTTP/1.1에서 사용하지 않도록 되어 빠졌지만, 많은 브라우저에서 여전히 Keep-alive를 사용하기 때문에 개발자는 keep-alive가 가능하도록 개발해야 한다.
  • Connection:Keep-Alive 헤더
    • 요청Connection:Keep-Alive 헤더 : “이 방식으로 통신하고 싶다” 의미
    • 응답Connection:Keep-Alive 헤더 : “이 방식을 지원할 것”(없다면 연결 끊을 것임을 의미)
    • HTTP/1.0을 따르는 기기로부터 받는 모든 Connection 헤더 필드(Connection: Keep-Alive와 같은)는 무시되어야 한다.
      • 멍청한 프락시: 오래된 프락시로 전달해서 Hang 걸릴 수 있기 때문
    • 이 방식을 사용하려면 정확한 Content-Length 값이 있어야 한다. 기존 메시지 끝과 새로운 메시지 시작점을 정확히 알수 어렵기 떄문


Keep-alive와 멍청한 프락시(Dumb Proxy)

  • 오래된 프락시들은 Connection:Keep-Alive 헤더를 해석하지 못하고 그대로 전달한다.

  • Connection 헤더는 기본적으로 홉별(Hop-by-Hop) 헤더이기 때문에 한개의 전송 링크에만 적용되며 다음 서버로 전송되면 안된다.

    • a에서의 Connection:Keep-Alive 는 b까지 전파되지 않아야 하는데, 멍청한 프락시 문제로 서버에선 proxy가 커넥션을 유지하자고 요청하는 것으로 잘못 판단하게 된다.
    • 프락시는 서버가 커넥션을 끊기를 기다리지만 서버는 프락시가 Keep-alive를 요청한 것으로 알기때문에 끊지 않는다.
    • 클라이언트가 유지하고 있는 커넥션으로 요청을 보내면 같은 커넥션 상 다른 요청이 오는 경우를 예상하지 못하기 때문에 무시해버린다.
  • 해결방안 1 : Proxy는Connection: Keep-Alive 헤더를 전달하면 안되고, 당연히 Keep-Alive: ~~ 와 같은 Keep-Alive 헤더도 전달하면 안된다.

    • 같은 이유로, Proxy는 다른 홉별(Hop by Hop) 헤더를 전달 혹은 캐싱하면 안된다.
  • 해결방안 2: 프락시에 커넥션 관련 메시지를 보낼 때는Proxy-Connection 이라는 확장 헤더를 사용

    • 오래된 프락시는 이 것을 똑같이 무조건 전달해도, 서버에선 뭔지 모르니 괜찮다.
    • 영리한 프락시는 이 것을 Connection 헤더로 바꾸고 요청한 의도대로 동작할 것
    • 그러나, 멍청한 프락시가 중간에 하나라도 끼어있다면 다시 문제가 발생한다.
  • 프락시의 경우 보이지 않는 경우가 많기 때문에 지속 커넥션을 명확히 구현하는 것이 중요하다.


HTTP/1.1의 지속 커넥션

  • 디폴트로 활성화 되어있고, 커넥션을 끊으려면 Connection: close 헤더를 명시하는 방식으로 사용
  • 마찬가지로 정확한 Content-Length 필요
  • HTTP/1.1 프락시는 클라이언트, 서버 양측 별도의 지속 커넥션을 맺고 관리해야한다.
  • HTTP/1.1 애플리케이션은 중간에 끊어지는 커넥션을 복구할 수 있어야 한다. 이 때 클라이언트는 언제든 재요청 할 준비를 해야 한다.
  • 클라이언트는 과부하 방지를 위해 넉넉잡아 두개의 지속 커넥션만 유지해야한다.


파이프라인 커넥션

: 지속 커넥션을 통해 요청을 파이프라이닝해 keep-alive 효과를 더 높인다.

  • 요청은 Queue에 쌓여 파이프라인 방식으로 전달된다.
  • 지속 커넥션일 때만 파이프라인을 이을 수 있다.
  • 순서가 유지되어야 한다.(그런데, 이미 TCP 통신이면 ACK로 순서가 보장이 될 것 같은데 왜 그런지는 모르겠다)
  • 끊김에 대한 준비: 커넥션이 끊어지는 상황에서 클라이언트는 언제든 재요청할 준비가 되어있어야 하는데, 클라이언트는 파이프라인 중 어느게 성공하고 어느게 실패한지 알 방법이 없기 때문에 비멱등 요청은 파이프라인으로 보내면 안된다.


커넥션 끊기와 관련된 것들

: 커넥션 끊는 것에 명확한 기준은 없다.

  • 마음대로 커넥션 끊기: 메시지를 다 보낸다음 끊는다.
    • e.g) 지속 커넥션에서 일정 timeout 이후에 끊어버리기.(그러나 더이상 필요 없다고 확신은 못함)
  • Content-Length: 정확한 값을 가져야 비교를 통해 끝났는지를 판단할 수 있다. - 없다면 데이터 길이를 서버에게 물어봐야 한다.
  • 끊기 허용, 재시도, 멱등성: HTTP 애플리케이션은 커넥션이 끊겼을 때 적절히 대응할 준비가 되어 있어야 한다.
    • 파이프라인 커넥션의 경우 더 어려워진다. 위에서 이야기했듯, 비멱등 요청의 경우 다시 보내기를 피해야한다.
    • 해결 예시) 캐시된 Post 요청 페이지를 다시 로드할 때, 요청을 다시 보내기를 원하는지 alert를 띄워준다 등


우아한 커넥션 끊기

  • TCP커넥션에선 두 채널(입력 큐와 출력 큐) 이 존재한다.
    • 한쪽의 입력 큐는 반대쪽의 출력 큐가 된다.
  • 전체끊기, 절반끊기 : 두 채널 중 하나만 끊는 것은 shutdown() 을 통한 절반 끊기, 둘다 끊는 것은 close() 를 통한 전체 끊기
  • 보통은 출력 채널을 끊어버리는게 이상적이다. -> 상대방은 받을 것을 다 받고나서 내가 커넥션을 끊었다는 것을 알게 되기 때문
  • 만약 입력 채널을 끊어버리면? (내 입장에선 출력 채널이 상대방에 의해 끊기면)
    • 파이프라인 커넥션 상황에서 계속 요청을 보내다가 상대방이 끊게 되면 어느 순간 connection rest by peer 메시지를 받게 된다. 이 리셋 메시지는 입력 버퍼에 있는 (아직 읽히지 않은)데이터를 지우게 된다.

우아하게 커넥션 끊기

: 일반적으로 좋은 방법은 자신의 출력채널을 먼저 끊고 상대방의 출력채널이 끊기기를 기다리는 것. 만약 timeout 내에 상대방이 끊지 않으면, 나의 리소스 보호를 위해 커넥션을 강제로 끊을 수도 있다.



Reference)

HTTP 완벽가이드

This post is licensed under CC BY 4.0 by the author.