kakasoo

[HTTP] 12. 파이프라인과 커넥션 끊기 본문

프로그래밍/HTTP

[HTTP] 12. 파이프라인과 커넥션 끊기

카카수(kakasoo) 2020. 10. 24. 15:49
반응형

4.6 파이프라인 커넥션

HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝할 수 있다. 이는 keep-alive 커넥션의 성능을 더 높여준다.

파이프라인은 응답을 기다리지 않고 요청을 보내는(...?) 방식이다. 파이프라인에는 제약이 있다.

  • HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안 된다. (지속 커넥션이 아니면 뒤의 요청이 무시 당한다.)
  • HTTP 응답은 요청 순서와 같아야 한다. HTTP 메시지는 순번이 없기 때문에 응답이 순서 없이 오면 정렬할 방법이 없다.
  • HTTP 클라이언트는 도중에 커넥션이 끊기더라도 다시 요청을 보낼 수 있어야 한다. 이 경우에는, 몇 개의 요청까지 성공했는지 확인할 방법이 있어야 한다.
  • HTTP 클라이언트는 POST 요청같이 반복해서 사용할 경우 문제가 생기는 요청은 파이프라인을 통해서 보내서는 안 된다. (멱등성에 대해서 알아보자.)
    • 어떤 것이 처리되었는지 클라이언트가 알 방법이 없다. 비 멱등한 메서드, 즉 위험한 메서드를 파이프라인으로 요청을 보내서는 안 된다.

4.7 커넥션 끊기에 대한 미스터리

커넥션 관리 (커넥션을 언제 어떻게 끊을 것인가)에 대한 기준은 없다. 개발자들이 알고 있는 것보다 더 미묘하며, 그에 관한 기술 문서도 별로 없다.

4.7.1 '마음대로' 커넥션 끊기

서버가 임의로 클라이언트와의 커넥션을 끊을 때, 서버는 사실 클라이언트가 요청을 보내지 않을 거라고 확신을 하고 끊는 것은 아니다. 그저 유휴 상태가 길어졌기 때문에 끊는 것이다.

이 때 클라이언트가 요청을 보내 버린다면 당연히 문제가 될 수 밖에 없다. (이런 타이밍이 생길 수 있다는 것이 놀랍다.)

4.7.2 Content-Length와 Truncation

각 HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다.

다만 일부 오래된 HTTP 서버는 자신이 커넥션을 끊으면 전송을 끝났음을 의미하는 형태로 개발되었기 때문에, Content-Length 헤더를 생략하거나 잘못된 길이 정보로 응답할 수도 있다.

따라서 클라이언트나 프락시는 커넥션이 끊어졌다는 HTTP 응답을 받은 후, 실제 전달된 엔터티의 길이와 Content-Length의 값이 일치하지 않거나 Content-Length 자체가 존재하지 않을 경우,

다시 서버에게 요청을 해볼 필요가 생긴다.

만약 수신자가 캐시 프락시일 경우 응답을 캐시해서는 안 된다. 프락시는 Content-Length를 정정하려 하지 말고 메시지를 받은 그대로 전달해야 한다.

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

수시로 말하지만 클라이언트는, 커넥션이 끊긴 경우 재요청을 할 수 있어야 한다. 문제는 파이프라인이다.

클라이언트는 여러 요청을 큐에 쌓아두고 전송해서 더 빨라질 수는 있지만, 도중에 커넥션이 끊어진다면 어디까지 전송되었는지 알 방법이 없다.

만일 GET 요청처럼 페이지를 띄우는 요청이었다면 다시 보내면 되겠지만, POST처럼 멱등하지 않은 요청을 반복한다면, 예컨대 구매하기 요청을 잘못 해석 해 중복 구매를 요청하게 될 수도 있다.

따라서 파이프라인은, 원칙적으로 안전한 메서드에 한하여 사용해야 한다.

비멱등인 요청을 다시 보내야 한다면, 이전 요청에 대한 응답이 돌아올 때까지 일단 기다려야 한다.

4.7.4 우아한 커넥션 끊기

TCP 커넥션은 양방향 Queue 구조이다. 클라이언트는 입력과 출력을 가지고 있고, 서버도 입력과 출력을 가지고 있다. 서로의 출력은 상대의 입력 방향으로 보내질 것이다.

전체 끊기와 절반 끊기

애플리케이션은 TCP 입력 채널과 출력 채널 중 한 개만 끊거나 둘 다 끊을 수 있다. close()로 둘 다 끊을 때를 전체 끊기, shutdown()으로 한 개만 끊을 때를 절반 끊기라고 한다.

TCP 끊기와 리셋 에러

HTTP 애플리케이션들 중 일부는 예상치 못한 쓰기 에러를 방지하기 위해 절반 끊기를 사용한다. 보통 커넥션의 출력 채널을 끊는 것이 안전하다. (자신이 말하지 않는다는 의미)

그러면 커넥션 반대 쪽의 기기는 버퍼를 모두 읽고 난 다음에 커넥션이 끊겼음을 인지한다.

클라이언트에서 더 데이터를 보내지 않을 거라고 확신할 수 없는 이상, 커넥션의 입력 채널을 끊는 것은 위험하다. 입력이 끊긴 채널에 데이터를 보내면 운영체제는 TCP 'connection reset by peer'를 보낸다.

운영체제는 이것을 심각한 에러로 취급하여 아직 읽히지 않은 데이터를 모두 삭제한다.

우아하게 커넥션 끊기

HTTP 명세에서는 우아하게 커넥션을 끊으라고 되어 있지만 방법은 서술되어 있지 않다.

일반적으로 우아한 방법은, 자신의 출력 채널을 먼저 끊고, 상대방이 출력 채널을 끊기를 기다리는 것이다. 그러면 자동적으로 자신의 양쪽 모두 데이터 전송이 중단되었음을 알 수 있다.

이 때 커넥션은 리셋의 위험 없이 온전히 종료된다. 그러나 안타깝게도 자신이 먼저 끊어도 상대방은 그것에 대한 대응 로직이 없을 수도 있다. 그래서 기다린다고 해도 소용 없을 수 있다.

따라서 커넥션을 우아하게 끊고자 한다면, 자신의 출력 채널을 끊은 다음에 상대방이 일정 시간 끊지 않으면 스스로 끊게끔 해야 한다.

반응형