본 글은 HTTP 완벽 가이드를 읽고 정리한 글입니다.
여러 서버들을 하나하나 거치면서 어느 파이프라인은 커넥션을 유지하며, 어느 파이프라인은 끊어줘야 하는 여부를 Connection 헤더
에 대한 정보로 판단한다.
커넥션 토큰의 HTTP 헤더 필드 들은 현재 커넥션만을 위한 정보임으로 다음 커넥션에 전달되면 안된다.
Connection 헤더
의 홉별(hop-by-hop)
헤더명들을 기술하는데 현재 커넥션의 헤더정보가 다음 커넥션에 전달되지 않도록 방지하는 역할을 한다.
*홉별(특정 두 서버간에만 영향을 미치고 다른 서버간에는 영향을 미치지 않음을 뜻함)
[Connection 헤더]
각 트랜잭션이 새로운 커넥션을 필요로 하면, 커넥션을 맺는 과정에서 발생하는 지연과, 느린 시작 지연이 발생한다.
클라이언트가 여러개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다.
더 빠르게 내려받을 수 있다.
총 지연 시간을 줄일 수 있다.
더 빠르게 느껴
질 수 있다.대역폭이 좁을땐
, 병렬 커넥션의 성능상의 장점은 거의 없어진다.메모리를 많이 소모
하고 자체적인 성능 문제를 발생시킨다.
6~8개
의 병렬커넥션을 지원한다.HTTP/1.1을 지원하는 기기는 처리가 완료된 후에도 TCP 커넥션을 유지하며 앞으로 있을 HTTP 요청에 재사용할 수 있다.
처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션을 지속 커넥션이라고 한다.
클라이언트나 서버가 커넥션을 끊기 전까진 트랜잭션 간에도 커넥션을 유지한다.
HTTP/1.1 명세에서 부터는 제외되었다.
HTTP/1.0 에서 클라이언트는 Connection:Keep-Alive 헤더를 요청에 포함시켜 보내고 서버로 부터의 응답에
Connection:Keep-Alive 헤더가 없으면 클라이언트는 서버가 Keep-Alive를 지원하지 않고, 응답 메시지가 전송된뒤 서버 커넥션을 끊을 것이라 추정한다.
옵션
클라이언트나 서버는 Keep-Alive 요청을 받았다고 해서 무조건 그것을 따를 필요는 없음
timeout
커넥션이 얼마나 유지될것인지 의미, 그러나 이대로 동작한다는 보장은 없음
max
커넥션이 몇개의 HTTP 트랜잭션을 처리할 때까지 유지될것인지 의미, 그러나 이대로 동작한다는 보장은 없음
keep-alive는 HTTP/1.0에서 기본으로 사용되지 않는다.
커넥션을 계속 유지하려면 모든 메시지에 Connection:Keep-Alive 헤더
를 포함해 보내야 한다.
Connection:Keep-Alive 헤더
를 보내지 않으면 서버는 요청 처리후 커넥션을 끊는다.클라이언트는 응답에 Connection:Keep-Alive 헤더
가 없는 걸 확인하여 응답 후 서버가 커넥션을 끊을것임을 알 수 있다.
엔티티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
프락시/게이트웨이
는 Connection 헤더 규칙을 철저히 지켜야 한다.(추후 복잡한 flow에서 터지는 Connection 문제)
Connection 헤더
에 명시된 모든 헤더 필드와 Connection 헤더를 제거
해야한다.Connection 헤더를 인식하지 못하는 프락시 서버와는 맺어지면 안된다.(멍청한 프락시 예방. 그러나 현실적으로 힘듬)
HTTP/1.0을 따르는 기기로부터 받는 모든 Connection 헤더
는 무시
클라이언트
는 응답 전체를 받기전에 커넥션이 끊어졌을 경우, 요청을 다시 보낼 수 있게 준비되어 있어야 한다
Connection 헤더가 관련없는 다음 프락시로 전달되어 생기는 문제점
프락시는Connection 헤더
를 이해하지 못해서 해당 헤더들을 삭제하지 않고 요청 그대로를 다음 프락시에게 전달
(그림출처:https://beomy.github.io/tech/etc/http-version/ )
위 문제로 인하여 해당 요청은 프락시로 부터 무시되고 브라우저는 아무런 응답 없이 로드중이라는 표시만 나옴
브라우저는 자신이나 서버가 타임아웃이 나서 커넥션이 끊길때 까지 기다린다.
위의 잘못된 통신을 피하려면 프락시는 Connection 헤더
와 Connection 헤더
에 명시된 헤더들을 절대 전달하면 안된다.
Keep-Alive, Proxy-Authenticate, Proxy-Connection, Transfer-Encoding, Upgrade와 같은 홉별헤더
들도 전달하면 안된다.
모든 웹 애플리케이션이 HTTP 최신 버전을 지원하지 않아도 모든 헤더를 무조건 전달하는 문제를 해결할 수 있는 기발한 차선책 Proxy-Connection 헤더 사용하기
(모든 상황에서 동작하지 않음)
Proxy-Connection 확장 헤더
를 프락시에게 전달Proxy-Connection 헤더
를 무시하지만 영리한 프락시(지속 커넥션 핸드쉐이킹)
이면 의미없는 Proxy-Connection 헤더를 Connection 헤더로 바꿈으로서 원하는 효과를 얻는다.위 방식은 클라이언트와 서버 사이에 한 개의 프락시만 있는 경우에서만 동작한다.
그러나 주변에 멍청한 프락시가 있다면 잘못된 헤더를 만들어내는 문제가 다시 발생
HTTP/1.1에서는 keep-alive 커넥션을 지원하지 않는 대신 설계가 더 개선된 지속커넥션을 지원한다.
HTTP/1.1의 keep-alive 지속 커넥션은 기본으로 활성화 되어있다.
HTTP/1.1에서 트랜잭션이 끝난 뒤 다음 커넥션을 끊을려면 Connection: close 헤더
를 명시해야한다.
HTTP/1.1 클라이언트는 응답에 Connection: close 헤더가 없으면 응답후에도 커넥션을 계속 유지하는것으로 추정한다.
그러나 서버든 클라이언트든 커넥션을 언제든 끊을 수 있기때문에 Connection: close를 보내지 않는것이 서버가 커넥션을 영원히 유지하겠다는 의미는 아니다.
Connection: close 헤더
를 보냈으면 클라이언트는 그 커넥션으로 추가적인 요청을 보낼 수 없다.Connection: close 헤더
를 보내야한다.자신의 길이 정보를 정확히 갖고있을때만
커넥션을 지속할 수 있다.클라이언트와 서버 각각
에 대해 별도로 지속 커넥션
을 맺고 있어야한다.클라이언트의 지원 범위
를 알고 있지 않는한 지속 커넥션을 맺으면 안된다.(현실적으로 쉽지않음, 많은 벤더가 이 규칙을 지키지 않음)서버 과부하 방지를 위해
하나의 사용자 클라이언트는 넉넉잡아 2개의 지속 커넥션만을 유지해야한다.
지속 커넥션을 통해 요청을 파이프라이닝 할 수 있으며, 이는 keep-alive 커넥션의 성능을 높여준다.
언제 어떻게 커넥션을 끊는가에 대한 명확한 기준이 없다.
클라이언트,서버,프록시는 언제든지 TCP 전송 커넥션을 끊을 수 있다.
지속커넥션
이 일정 시간동안 요청을 전송하지 않고 유휴상태
에 있으면 서버는 그 커넥션을 끊을 수 있다.문제발생
HTTP 응답
은 본문의 정확한 크기 값을 가지는 Content-length 헤더를 가지고 있어야 한다.
오래된 HTTP 서버
는 자신이 커넥션 종료시 Content-length 헤더
를 생략하거나 잘못된 길이로 응답하는 경우가 있음HTTP 응답
을 받은 후 실제 전달한 엔티티 길이와 Content-Length
의 값이 일치 하는지 또는 존재하는지 정확한 길이를 서버에게 물어봐야 한다. 커넥션은 에러가 없더라도 언제든 끊을 수 있다. HTTP 애플리케이션은 예상치 못한 커넥션 종료에 대응이 되어있어야 한다.
클라이언트가 트랜잭션 도충 전송 커넥션이 끊기면, 그 트랜잭션을 재시도 하더라도 문제가 없는경우 커넥션을 다시 맺고 한번 더 전송을 시도해야한다.
파이프라인 커넥션에서 클라이언트는 여러 요청을 큐에 쌓아 놓을 수 있지만
, 서버는 그 요청들을 남겨둔 채로 커넥션을 끊어버릴 수도 있다.
반복요청 될경우 주문 같은것이 여러번 반복되는 심한 문제가 발생한다
. 때문에 비멱등한 요청들은 파이프라인을 통해 요청하면 안된다
.비멱등인 요청을 다시 보내야 한다면, 이전 요청에 대한 응답을 받을때까지 기다려야한다.
애플리케이션은 TCP 입력채널/출력채널 중 한개만 또는 전체를 끊을 수 있다.
close()
를 호출하면, TCP 커넥션의 입출력 채널의 커넥션을 모두 끊는다.(전체끊기)
shutdown()
을 호출하면, 입출력 채널 중 하나를 개별적으로 끊을 수 있다.
단순한 HTTP 애플리케이션
은 전체 끊기만을 사용
할 수 있다.
그러나
애플리케이션이 각기 다른
HTTP 클라이언트, 서버, 프락시와 통신시 그들 사이에 파이프라인 지속 커넥션을 사용하는 경우
예상치 못한 에러를 대비해 절반 끊기를 사용
해야 한다.
보통 커넥션을 출력 채널을 끊는 것이 안전하다.
입력 채널을 끊는 경우
, 클라이언트가 데이터를 보냈을때 서버의 운영체제는 TCP Connection reset by peer
메시지를 클라이언트에게 보낸다.버퍼에 저장된 아직 읽히지 않는 데이터는 모두 삭제한다.
서버가 커넥션이 충분히 오래 유지 되었다고 판단하고 연결을 끊어버린경우
Connection reset by peer
메시지로 응답하며 입력 버퍼에 있는 데이터는 모두 삭제한다.우아한 커넥션 끊기를 구현하는 것은 애플리케이션 자신의 출력 채널을 먼저 끊고,다른쪽에 있는 기기의 출력채널이 끊기는 것을 기다리는 것이다.
양쪽에서 더는 데이터를 전송하지 않을 것이라고 알려주면(출력채널의 커넥션을 끊는것), 커넥션은 리셋의 위험 없이 온전히 종료된다.
그러나 상대방이 절반 끊기를 구현했다는 보장도 없으며, 절반 끊기를 했는지 검사해준다는 보장도 없다.
따라서 애플리케이션은 절반 끊기를 하고 난 후에도 데이터나 스트림의 끝을 식별하기 위해 입력 채널에 대해 상태 검사를 주기적으로 해야한다.(자바에선 SocketChannel 키워드를 검색하면 채널 상태를 검사할수 있는 다양한 옵션들이 존재)