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 트랜잭션을 병렬로 처리할 수 있게 한다.
- 클라이언트 입장에서의 객체당 한 서버와 커넥션을 맺어 각 커넥션의 지연시간을 겹치게 해 처리 속도를 빠르게 하는 것
항상 빠르지는 않다.
- 대역폭이 좁으면 각 객체를 전송하는게 느리기 때문에 성능상 장점이 없다.
- 실제로 연결하는 병렬 커넥션 수에는 제한이 있다. (커넥션을 늘릴 수록 성능 문제)
- 한 사용자 당 100개의 커넥션을 허용하고 100명의 사용자가 사용하면 서버는 10000개의 커넥션을 맺어야 하는데, 이 것은 큰 부하가 될 수 있다.
- 따라서, 최신 브라우저는 대부분 6~8개 라고 함
- 연결을 맺고 끊음을 반복하다보니 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를 요청한 것으로 알기때문에 끊지 않는다.
- 클라이언트가 유지하고 있는 커넥션으로 요청을 보내면 같은 커넥션 상 다른 요청이 오는 경우를 예상하지 못하기 때문에 무시해버린다.
- a에서의
해결방안 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 완벽가이드