TIL(2020.10.23)

Awesome·2020년 10월 23일
0

TIL

목록 보기
34/46

HTTP 완벽가이드 내용 정리

1부. HTTP:웹의 기초

4장. 커넥션 관리

4-1. TCP 커넥션

전 세계 모든 HTTP 통신은 패킷 교환 네트워크 프로토콜들의 계층화된 집합인 TCP/IP를 통해 이루어진다.
자세한 참고 사이트 - 브런치

이미지 출처

4.1.1 TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다.

이미지출처

TCP는 세그먼트라는 단위로 데이터 스트림을 잘게 나누고, IP 패킷이라는 봉투에 담아서 인터넷을 통해 데이터를 전달한다. 그리고 IP 패킷에는 다음의 정보가 담긴다.

  • IP 패킷 헤더(보통 20바이트)
  • TCP 세그먼트 헤더(보통 20바이트)
  • TCP 데이터 조각(0 혹은 그 이상의 바이트)

IP 헤더는 발신지오 목적지 IP주소, 크기, 기타 플래그를 가진다. TCP 세그먼트 헤더는 TCP 포트 번호, TCP 제어 플래그, 그리고 데이터의 순서와 무결성을 검사하기 위해 사용되는 숫자 값을 포함한다.

4.1.2 TCP 커넥션 유지하기

컴퓨터는 여러 개의 TCP 커넥션을 가지고 있다. TCP 포트는 회사 내선번호와 같다. IP 주소는 컴퓨터에 연결되고 포트 번호는 애플리케이션으로 연결된다. TCP 커넥션은 네 가지 값으로 식별한다.

<발신지 IP주소, 발신지 포트, 수신지 IP주소, 수신지 포트>

서로 다른 두 개의 TCP 커넥션은 네 가지 커넥션 구성요소의 값이 모두 같을 수 없다.

4.1.3 TCP 소켓 프로그래밍

운영체제는 TCP 커넥션의 생성과 관련된 여러 기능을 제공하며, 그 중에서도 소켓 API를 활용하여 데이터를 주고 받는 과정을 구현하는 소켓 프로그래밍이 있다.

이미지출처

위와 같이 클라이언트와 서버 각각 소켓을 가지고 있으며, 각 주체와 상황에 따라서 소켓의 동작이 달라진다.

4.2 TCP의 성능에 대한 고려

HTTP는 TCP 바로 위에 있는 계층이기 때문에, TCP의 성능에 영향을 많이 받는다. 따라서 TCP 성능의 특성을 이해함으로써 최적화된 HTTP 애플리케이션을 구현할 수 있다.

4.2.1 HTTP 트랜잭션 지연

클라이언트나 서버거 너무 많은 데이터를 내려받거나 복잡하고 동적인 자원들을 실행하지 않는 한, 대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생한다. 그 원인으로는 아래와 같은 예를 들 수 있다.

  1. 과거에는 클라이언트가 호스트에 방문한 적이 없는 경우, DNS를 통해서 URI의 호스트 명을 IP주소로 변환하는데 수십 초의 시간이 걸렸다. 현재는 인터넷 인프라의 발전으로 밀리초 단위로 해결된다. 또한 대부분의 HTTP 클라이언트는 최근 접속한 사이트에 대한 IP주소의 DNS를 가지고 있으며, 따라서 순간적으로 처리된다.
  2. 클라이언트가 TCP 커넥션 요청을 서버에게 보내고, 서버가 커넥션 허가 응답을 회신할 때까지 기다린다. 마찬가지로 과거에는 이러한 커넥션 설정 시간이 1~2초의 시간이 소요되었기 때문에 새로운 수백 개의 HTTP 트랜잭션이 만들어지면 소요시간이 크게 증가하였다. 현재에는 1초 미만으로 끝난다.
  3. 커넥션이 맺어지면 클라이언트는 HTTP 요청을 새로 생성된 TCP 파이프를 통해 전송한다. 웹 서버는 데이터가 도착하는대로 TCP 커넥션에서 요청 메시지를 읽고 처리한다. 이러한 과정에서 요청 메시지가 서버로 전달되고 처리하는 데까지의 시간이 소요된다.
  4. 웹 서버가 HTTP 응답을 보내는 것 역시도 시간이 소요된다.

이러한 TCP 네트워크 지연은 하드웨어의 성능, 네트워크와 서버의 전송 속도, 요청/응답 메시지의 크기, 클라이언트와 서버 간의 거리 등에 따라 크게 달라진다. 또한 TCP 프로토콜의 기술적인 복잡성도 지연에 영향을 끼친다.

4.2.2 성능 관련 중요 요소

TCP 관련 지연에 영향을 주는 요소는 다음과 같다.

  • TCP 커넥션의 핸드셰이크 설정
  • 인터넷의 혼잡을 제어하기 위한 TCP의 slow-start
  • 데이터를 한데 모아 한 번에 전송하기 위한 네이글(nagle) 알고리즘
  • TCP의 편승확인응답을 위한 확인응답 지연 알고리즘
  • TIME_WAIT 지연과 포트 고갈

4.2.3 TCP 커넥션 핸드셰이크 지연

TCP 핸드셰이크: 클라이언트와 서버 사이의 논리적인 접속을 성립하기 위하여 three-way handshake 라는 것을 사용한다. 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 전송 조건을 맞춰가는 과정을 의미한다.

이미지 출처

위의 그림을 순서대로 풀면 다음과 같다.

  1. 클라이언트는 새로운 TCP 커넥션 생성을 위해 작은 TCP 패킷을 서버로 보낸다. 해당 패킷은 'SYN(synchronize sequence numbers)' 이라는 특별한 플래그를 가지며, 이는 해당 요청이 커넥션 생성 요청임을 뜻한다. 동시에 클라이언트는 응답을 기다리는 SYN-SENT 상태가 된다.
  2. 서버는 커넥션 요청이 받아들여졌음을 의미하는 'SYN' + 'ACK(acknowledgment)' 플래그를 포함한 TCP 패킷을 클라이언트로 회신한다. 동시에 서버는 SYN_RECEIVED 상태가 된다.
  3. 클라이언트는 커넥션이 잘 맺어졌음을 알리기 위해 'ACK' 플래그를 포함한 TCP 패킷을 응답 신호로 보내고 ESTALESHED 상태가 된다.

크기가 작은 HTTP 트랜잭션은 50% 이상의 시간을 TCP 구성(1,2번)에 사용한다. 추후에는 이러한 지연을 제거하기 위해 HTTP가 이미 존재하는 커넥션을 어떻게 재활용하는지 알아본다.

4.2.4 확인응답 지연

인터넷 자체는 완벽한 패킷 전송을 보장하지 않기 때문에 TCP가 자체적인 확인 체계를 갖는다.
각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 갖는다. 수신자는 세그먼트를 온전히 받았을 경우 확인응답(ACK) 패킷을 송신자에게 반환한다. 이 때, 확인응답은 그 크기가 작기 때문에 별도의 패킷을 만들어 보내기 보다는 동일 방향으로 송출되는 데이터 패킷에 편승시킨다. 확인응답과 데이터 패킷을 하나로 묶어 보다 효율적으로 네트워크를 사용하기 위함이다. 이러한 편승을 늘리기 위해 TCP 내부적으로 확인응답 지연 알고리즘을 구현한다. 보통 0.1~0.2초 동안 버퍼에 저장해두고, 데이터 패킷을 찾는 것이다. 확인응답의 송출을 지연하여 최대한 데이터 패킷에 많이 편승시키고자 한다.
그러나 요청과 응답 단 두가지의 형식으로만 이루어지는 HTTP 동작 방식은, 송출되는 데이터 패킷이 많지 않기 때문에 오히려 잦은 지연을 발생시킨다.

4.2.5 TCP slow start

TCP는 인터넷의 급작스러운 부하와 혼란을 방지하기 위해 초기에는 데이터 전송 속도를 제한한다. TCP가 한 번에 전송할 수 있는 패킷의 수를 제한하며 이를 TCP slow start 라고 한다. 데이터가 성공적으로 전송되어 확인응답을 받으면 더 많은 패킷을 전송할 수 있는 권한을 얻게된다. 이처럼 시간에 따라 전송 속도가 높아지는 것을 튜닝이라고 하며, 새로운 커넥션은 튜닝된 커넥션보다 전송 속도가 당연힌 느리다. 따라서 HTTP는 튜닝된 커넥션을 재사용하는 기능이 있으며 이는 '지속 커넥션'을 통해서 알 수 있다.

4.2.6 네이글(Nagle) 알고리즘과 TCP_NODELAY

TCP는 데이터 스트림 인터페이스라는 것을 통해서 매우 작은 데이터도 TCP 스택으로 전송할 수 있도록 한다. 그러나 앞서 살펴본 TCP 세그먼트는 플래그와 헤더를 포함하여 약 40바이트 정도 된다. 정작 데이터는 매우 작은데(예를 들어 1바이트) 40바이트에 해당하는 플래그와 헤더를 포함한 수 많은 패킷을 전송한다면 이는 네트워크의 성능 저하로 이어진다.
네이글 알고리즘은 세그먼트가 최대 크기가 되지 않으면 전송하지 않는다. 여러 TCP 데이터를 한 개의 덩어리로 만든 후에 패킷으로 전송한다. 모든 패킷이 확인응답을 받은 경우에만 최대 크기가 되지 않아도 패킷을 전송하며, 다른 패킷들이 아직 전송 중인 경우에는 버퍼에 저장했다가 다른 패킷들이 확인응답을 모두 받았거나 패킷이 충분히 쌓인 경우에 버퍼에 저장된 데이터가 전송된다.

네이글 알고리즘은 HTTP 성능과 관련해 문제를 발생시킨다.

  1. 크기가 작은 HTTP 메시지는 패킷을 채우지 못하므로, 언제 생길지 모르는 추가 데이터를 기다린다.
  2. 네이글 알고리즘은 확인응답이 도착할 때까지 전송을 멈추는 반면, 확인응답 지연 알고리즘은 송출 데이터 패킷을 찾을 때까지 100~200밀리초 가량을 지연시키므로 양방향에서 지연이 발생한다.

이러한 문제를 해결하기 위해 HTTP 스택에 TCP_NODELAY 파라미터를 설정하여 네이글 알고리즘을 비활성화시킬 수 있다.

4.2.7 TIME_WAIT의 누적과 포트 고갈

TCP 커넥션을 끊으면 커넥션의 IP주소와 포트 번호를 메모리의 작은 제어영역(control block)에 저장한다. 그리고 같은 주소와 포트 번호를 사용하는 새로운 TCP커넥션이 일정 시간 동안에는 생성되지 않게 하기 위한 것으로 2MLS(2분) 동안 유지된다.
TIME_WAIT을 두는 이유는 지연 패킷이 있을 경우, 도달하기 전에 다른 연결이 진행되었다면 데이터의 무결성에 영향을 주는 등의 상황이 발생할 수 있기 때문이다.

TIME_WAIT 로 인한 문제는 일반적인 상황보다는 성능 시험을 하는 경우에 주로 발생한다. 테스트를 위한 서버의 포트 80번으로 클라이언트가 TCP 커넥션을 시도하는 경우, 클라이언트 포트 수가 60,000개로 가정하면 초당 500개의 커넥션까지만 가능하다. 따라서 이러한 포트 고갈로 인하여 네트워크 성능 저하가 발생할 수 있다.

4.3 HTTP 커넥션 관리

HTTP는 Connection 헤더 필드를 통해 특정 커넥션에만 적용될 옵션을 지정할 수 있으며, 다양한 기술들을 통해 커넥션 성능을 향상시킬 수 있다.

4.3.1 Connection 헤더

Connection 헤더를 통해 두 개의 인접한 HTTP 애플리케이션이 현재 맺고 있는 커넥션에만 적용될 옵션을 지정할 수 있다.

  • HTTP 헤더 필드 명은 이 커넥션에만 해당되는 헤더들을 나열한다.
  • 임시적인 토큰 값은 커넥션에 대한 비표준 옵션을 의미한다.(추가 옵션)
  • close 값은 커넥션이 작업이 완료되면 종료되어야 함을 의미한다.

Connection 헤더에 있는 모든 헤더 필드는 메시지를 다른 곳으로 전달하는 시점에 모두 삭제돼야 한다. Connection 헤더에는 hop(서버)별 헤더 명을 기술하는데, 이것을 '헤더 보호하기' 라고 한다.

4.3.2 순차적인 트랜잭션 처리에 의한 지연

이미지 출처

기본적으로 HTTP 트랜잭션은 요청에 대한 응답이 순차적으로 발생한다. 예컨대 3개의 이미지가 있는 페이지의 경우 총 4번의 트랜잭션이 순차적으로 발생한다. 최초 HTML을 받아오는 트랜잭션과 나머지 3개의 이미지를 순서대로 가져오는 트랜잭션이다. 이 때, 커넥션을 맺는 데 발생하는 지연과 더불어 느린 시작 지연이 발생한다. 클라이언트 입장에서는 하나씩 로딩되는 이미지로 인해 비어있는 공간에 대한 심리적 지연도 느끼게 된다. HTTP 커넥션의 성능을 향상시킬 수 있는 여러 기술들을 통해 이러한 문제점을 해결할 수 있다.

4.4 병렬 커넥션


이미지 출처

위와 같이 병렬 커넥션은 여러 개의 커넥션을 통해 병렬로 처리된다. 앞서 예로 들었던 이미지 3개는 server2 로부터 병렬로 가져옴으로써, 시간을 단축시킬 수 있다. 커넥션들의 지연 시간을 겹치게 하여 총 지연시간을 줄이고, 단일 커넥션일 때 하나의 대역폭을 단일 커넥션이 모두 사용하는 것과는 다르게 여러 개의 커넥션이 대역폭을 나눠 사용할 수 있게 된다.

그러나 항상 병렬 커넥션이 빠른 것은 아니다. 클라이언트의 네트워크 대역폭이 좁은 경우에는 제한된 대역폭 내에서 여러 객체를 전송받는 것은 느리기 때문에 성능상의 장점은 없어진다. 또한 다수의 커넥션은 메모리 소모가 크다. 서버의 입장에서는 너무 많은 커넥션을 감당하는 경우, 서버의 성능을 크게 떨어뜨리는 문제도 발생할 수 있다. 따라서 브라우저는 적은 수의 병렬 커넥션만 허용하며, 서버는 과도한 수의 커넥션이 맺어진 경우 이를 임의로 끊을 수 있다.

마지막으로 클라이언트 입장에서는 페이지 내의 여러 객체가 동시에 보이므로 실제 속도가 더 빠르지는 않더라도 상대적으로 더 빠르다고 느낄 수 있다. 개발자의 측면에서는 저해상도로 시작하여 점차 해상도를 높여가는 형태의 이미지를 사용하여 그 효과를 극대화시킬 수도 있다.

4.5 지속 커넥션

보통 클라이언트는 같은 사이트에서 여러 개의 커넥션을 맺는다. 그리고 동일 서버에 여러 번의 요청을 보낸다. 이를 사이트 지역성(site locality) 라고 한다.

이러한 사이트 지역성을 활용하기 위해 TCP 커넥션을 유지하는 상태를 지속 커네셕이라고 한다. 이를 통해 커넥션 준비 시간과 TCP 느린 시작으로 인한 지연을 피하고 빠르게 데이터를 전송할 수 있다.

4.5.1 지속 커넥션 vs 병렬 커넥션

병렬 커넥션은 단일 커넥션에 비해 페이지를 빠르게 전송한다. 반면 다음과 같은 단점을 가진다.

  • 각 트랜잭션마다 새로운 커넥션을 맺고 끊기 때문에, 시간과 대역폭이 소요된다.
  • 각각의 새로운 커넥션은 TCP 느린 시작 때문에 성능이 떨어진다.
  • 실제로 연결 가능한 커넥션의 수에 제한이 있다.

반면, 상대적으로 지속 커넥션은 몇 가지 장점이 있다. 커넥션을 맺기 위한 사전 작업과 지연을 줄여주고, 튜닝된 커넥션을 유지한다. 또한 커넥션의 수를 줄여준다. 하지만 지속 커넥션을 잘못 관리할 경우, 연결된 상태의 수많은 커넥션이 쌓일 수 있다. 이는 결과적으로 클라이언트와 서버의 리소스에 불필요한 소모를 발생시킨다.

지속 커넥션과 병렬 커넥션이 함께 사용될 때 가장 효과적이며, 많은 웹 애플리케이션이 적은 수의 병렬 커넥션만 맺고 이를 유지한다. 지속 커넥션 타입은 HTTP/1.0+ 에 keep-alive 커넥션과 HTTP/1.1 지속 커넥션이 있다.

4.5.2 HTTP/1.0 Keep-Alive 커넥션

이미지 출처

위의 그림은 4개의 HTTP 트랜잭션에 대하여 연속적으로 4개의 커넥션을 생성하여 처리하는 방식과 하나의 지속 커넥션으로 처리하는 방식을 비교한 시간 차이가 나타나있다.

지속 커넥션은 커넥션을 맺고 끊는 데에 필요한 작업이 없기 때문에 시간이 단축된 것을 알 수 있다.

4.5.3 Keep-Alive 동작

Keep-alive는 사용하지 않게 되어 HTTP/1.1 명세에서는 빠졌으나, 아직까지 keep-alive 핸드셰이크가 널리 사용되고 있으므로 이를 처리할 수 있도록 개발해야 한다. HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 Connection 헤더를 포함한다.

이미지 출처

해당 요청을 받은 서버는 그 다음 요청도 이 커넥션을 통해 받고 싶은 경우 마찬가지로 Connection 헤더를 담아 응답한다.

4.5.4 Keep-Alive 커넥션의 제한과 규칙

  • Keep-alive는 HTTP/1.0에서 기본으로 사용되지 않는다. Keep-alive 커넥션을 사용하기 위해서는 Connection 헤더를 추가해야 한다.
  • 만약 클라이언트가 Connection:Keep-Alive 헤더를 보내지 않을 경우, 서버는 요청을 처리한 뒤 커넥션을 끊는다.
  • 커넥션을 유지하기 위해서는 엔티티 본문의 길이를 알 수 있어야 한다.(Content-Length)
  • 프락시와 게이트웨이는 Connection 헤더의 규칙을 철저히 지켜야 한다.(메시지를 전달하기 전에 반드시 Connection 헤더의 모든 필드와 Connection 헤더를 제거해야 함)
  • 기술적으로 HTTP/1.0 을 따르는 기기로부터 전달된 Connection 헤더 필드는 무시해야 한다. 오래된 프락시 서버로부터 실수로 전달될 수 있기 때문이다.
  • 클라이언트는 응답 전체를 모두 받기 전에 커넥션이 끊어졌을 경우, 특별한 문제가 없다면 요청을 다시 보낼 수 있도록 준비되어 있어야 한다. 그렇지 않으면 요청이 반복될 경우 문제가 발생할 수 있다.

4.5.5 Keep-Alive와 멍청한(dumb) 프락시

이미지 출처

Connection 헤더를 이해하지 못하는 멍청한 프락시가 클라이언트와 서버 사이에 있는 경우에 문제가 발생한다.

  1. 클라이언트는 커넥션 유지를 원하여 Conneciton: Keep-Alive 헤더와 함께 메시지를 보낸다.
  2. 멍청한 프락시는 이를 이해하지 못하고 그대로 서버로 전달한다. 하지만 Connection 헤더는 hop-by-hop 헤더이므로 단 한 단계에만 적용되며 그 다음으로 전달되어서는 안된다. 즉, 클라이언트 - 서버가 프락시 없이 연결되었을 때에 오류가 발생하지 않는다.
  3. 서버는 헤더를 전달받았으니 클라이언트가 아닌 웹 서버(프락시)와의 커넥션을 유지하자는 것으로 잘못 인식하게 된다. 그리고 Connection 헤더를 포함하여 응답한다. 그리고 커넥션을 끊지 않는다.
  4. 멍청한 프락시는 이를 그대로 클라이언트에 다시 전달하고, 서버가 커넥션을 끊기를 기다린다.
  5. 클라이언트는 Connection 헤더를 받았기 때문에 커넥션이 유지되었다고 생각하고 또다른 요청을 보낸다.
  6. 멍청한 프락시는 같은 커넥션에서 다른 요청이 오는 경우 이를 무시하고, 서버의 커넥션이 끊길 때까지 기다린다.
  7. 브라우저에는 아무런 응답이 없으므로 로딩 중이라고 뜬다.

총체적인 난국이다. 클라이언트는 커넥션 유지가 된다고 착각하고, 서버는 프락시와 커넥션이 유지된다고 또다시 착각한다.

4.5.6 Proxy-Connection

위의 멍청한 프락시 문제를 해결하기 위해 Proxy-Connection 이라는 헤더를 사용한다. 대부분의 현대 브라우저에서 지원하고 있으며, 많은 프락시도 이를 인식한다.

이미지 출처

Proxy-Connection 은 비표준 확장 헤더이므로 멍청한 프락시가 이를 무조건 서버에 전달하더라도 서버는 이를 무시한다. 따라서 문제가 생기지 않는다.
반면, 이를 이해하는 영리한 프락시의 경우에는 기존에 원하던 커넥션 유지를 가능케 한다.
영리한 프락시는 Proxy-connection 헤더를 인식하고 Connection 헤더로 바꿔서 전송한다.
영리한 프락시와 서버가 지속 커넥션을 맺고, 클라이언트가 같은 커넥션에 다른 요청을 보내더라도 프락시가 이를 무시하지 않고 서버로 전달한다.

추가로, 멍청한 프락시와 영리한 프락시가 공존하는 구조에서는 여전히 문제가 발생할 수 있다.

4.5.7 HTTP/1.1의 지속 커넥션

HTTP/1.1 에서는 keep-alive 커넥션을 지원하지 않는 대신, 기본적으로 지속 커넥션이 활성화 되어 있다. 별도 설정하지 않는 한, 모든 커넥션을 지속 커넥션으로 취급한다. 커넥션을 끊으려면 Connection: close 헤더를 명시해야 한다.

4.5.8 지속 커넥션의 제한과 규칙

  • 클라이언트가 요청에 Connection:close 헤더를 포함해 보냈으면, 그 커넥션으로 추가 요청을 보낼 수 없다.
  • 클라이언트가 추가 요청을 보내지 않을 것이라면, Connection:close 헤더를 보내야 한다.
  • 엔티티 본문은 정확한 Content-Length 값을 가져야 한다.
  • HTTP/1.1 프락시는 클라이언트와 서버 각각에 대해 별도의 지속 커넥션을 맺고 있어야 한다.
  • HTTP/1.1 프락시는 커넥션 관련 기능에 대한 클라이언트의 지원 범위를 알고 있지 않는 한 지속 커넥션을 맺으면 안된다.
  • HTTP/1.1 기기는 Connection 헤더와는 상관없이 언제든 커넥션을 끊을 수 있다.
  • HTTP/1.1 애플리케이션은 중간에 끊어지는 커넥션을 복구할 수 있어야 한다. 클라이언트는 다시 보내도 문제가 없는 요청이라면 다시 보내야 한다.
  • 클라이언트는 전체 응답을 받기 전에 커넥션이 끊어지면, 다시 보내도 문제가 없는 경우 요청을 다시 보낼 준비가 되어 있어야 한다.
  • 서버 과부하에 대비하여 클라이언트는 최대 2개의 지속 커넥션만 유지해야 한다. 프락시는 최대 2N 개의 커넥션을 유지해야 한다.

4.6 파이프라인 커넥션

이미지 출처

파이프라인 커넥션은 여러 개의 요청을 응답이 도착하기 전에 큐에 쌓는 것이다. 이를 통해서 대기 시간이 긴 네트워크 상황에서 왕복으로 인한 시간을 줄이고 성능을 향상시킨다.
파이프라인에는 여러 가지 제약 사항이 있다.

  • HTTP 클라이언트는 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안된다.
  • HTTP 응답은 요청 순서와 같게 와야 한다. HTTP 메시지에는 순번이 없기 때문에 순서가 바뀌면 정렬시킬 방법이 없다.
  • HTTP 클라이언트는 커넥션이 중간에 끊어지더라도, 완료되지 않은 요청이 파이프라인 안에 있으면 언제든 다시 보낼 준비가 되어 있어야 한다.
  • HTTP 클라이언트는 POST 요청과 같이 반복해서 보낼 경우 문제가 발생하는 경우에는 파이프라인을 통해서 보내면 안된다. 에러가 발생하면 파이프라인 안의 요청 중에 어떤 것들이 서버에서 처리가 되었는지 클라이언트가 알 수 없기 때문이다.

4.7 커넥션 끊기

커넥션을 언제 끊는 가에 대해서는 명확한 기준이 없다.

4.7.1 마음대로 커넥션 끊기

어떠한 HTTP 클라이언트, 서버, 프라시든 언제든지 TCP 전송 커넥션을 끊을 수 있다.

4.7.2 Content-Length와 Truncation

각 HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다. 그리고 클라이언트나 프락시가 커넥션이 끊어졌다는 응답을 받은 후, 실제 전달된 데이터와 Content-Length 값이 일치하지 않는 경우 데이터의 정확한 길이를 서버에 물어봐야 한다.

4.7.3 커넥션 끊기의 허용, 재시도, 멱등성

앞서 언급되었듯이, 커넥션은 에러가 없더라도 언제든 끊을 수 있다. 클라이언트는 실제로 서버에서 얼만큼 요청이 처리되었는지 전혀 알 수 없다. 그로 인한 재시도를 했을 때, 데이터의 변동이 있을 만한 경우에는 이를 파이프라인을 통해서 요청해서는 안된다. 실행 횟수와 상관없이 같은 결과를 반환할 때 이를 멱등(idempotent)하다고 한다. 비멱등인 메서드나 순서에 대해 에이전트가 요청을 다시 보낼 수 있도록 기능을 제공한다고 할 지라도, 자동으로 재시도 하면 안된다. 예컨대 브라우저는 캐시된 POST 요청 페이지를 다시 로드하려고 할 때, 요청을 다시 보내기를 원하는지 묻는 대화상자를 보여준다.

4.7.4 우아한 커넥션 끊기

이미지 출처

  • TCP 커넥션은 양쪽에 입력 큐와 출력 큐를 동시에 갖는 양방향이다.
  • close() 를 호출하면 전체 끊기이다.
  • shutdown() 을 호출하면 절반 끊기이다.
  • 단순한 애플리케이션은 전체 끊기만 사용할 수 있으나, 예상치 못한 에러를 예방하기 위해 절반 끊기를 사용해야 한다.
  • 일반적으로 절반 끊기는 출력 채널을 끊는 것이 안전하다.
  • 출력 채널에서 데이터 전송이 끝남과 동시에 반대편에서 커넥션을 끊었다는 사실을 알게 된다.
  • 클라이언트에서 이미 끊긴 입력 채널에 데이터를 전송하면 오류가 발생하고, 클라이언트 운영체제에서 이를 심각한 에러로 취급한다.
  • 만약 파이프라인을 통해 10개의 요청을 보냈고, 해당 응답이 클라이언트의 운영체제 버퍼에는 쌓여있으나 아직 애플리케이션이 읽지 않았다고 가정해보자. 커넥션이 종료된 상태에서 11번째 요청을 보내고 connection reset by peer 응답을 받게 되면 제대로 도착한 10개의 요청에 대한 응답 데이터를 삭제시켜버린다.
  • HTTP 명세에 따라서 '우아하게 커넥션을 끊는 방법'은 자신의 출력 채널을 먼저 끊고 다른 쪽의 출력 채널이 끊기는 것을 기다리는 것이다. 양쪽에서 더는 데이터를 전송하지 않는다고 알려주면 커넥션이 온전히 종료된다.

5장 웹 서버: 환일님 블로그
6장 프록시: 준수님 노션

profile
keep calm and carry on

0개의 댓글