## 4.1 TCP 커넥션

- 모든 HTTP 통신은 TCP/IP를 통해 이루어지며, 손실, 손상, 순서바뀜없이 안전하게 전달됨
- URL을 입력 받은 브라우저가 수행하는 단계
    1. 브라우저가 URL에서 호스트명을 추출한다
    2. 브라우저가 호스트명에 대한 IP주소를 찾는다
    3. 브라우저가 포트번호(80)를 얻는다
    4. 브라우저가 202.43.78.3의 80포트로 TCP 커넥션을 생성한다
    5. 브라우저가 서버로 HTTP GET 요청 메시지를 보낸다
    6. 브라우저가 서버에서 온 HTTP 응답 메시지를 읽는다
    7. 브라우저가 커넥션을 끊는다

### 4.1.1 신뢰할 수 있는 데이터 전송 통로인 TCP

- HTTP 커넥션은 몇 사용 규칙을 제외하고 TCP 커넥션에 불과하므로 TCP의 기초 내용을 알아야 함
- TCP는 인터넷을 안정적으로 연결해주며, HTTP에게 신뢰할 만한 통신 방식 제공.

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

- TCP는 IP 패킷(=IP 데이터그램)이라는 조각을 통해 데이터 전송.
    - TCP는 세그먼트라는 단위로 데이터스트림을 나누고, IP 패킷으로 감싸 인터넷을 통해 전달
    - IP 패킷의 구조
        - IP 패킷 헤더(보통 20byte)
            - 목적지 IP 주소, 크기, 기타 플래그
        - TCP 세그먼트 헤더(보통 20byte)
            - TCP 포트 번호, TCP 제어 플래그, 데이터 순서 및 무결성 검사용 숫자 값
        - TCP 데이터 조각(0혹은 그 이상)

### 4.1.3 TCP 커넥션 유지하기

- 컴퓨터는 항상 TCP 커넥션을 여러 개 가지고 있으며, TCP는 포트 번호를 통해 여러 개의 커넥션을 유지
    - IP주소는 해당 컴퓨터로 연결되고, 포트 번호는 해당 어플리케이션으로 연결
- TCP 커넥션은 4가지 값으로 식별
    - <발신지 IP 주소, 발신 포트, 수신지 IP주소 , 수신지 포트>
    - 서로 다른 두개의 TCP는 4가지 주소 구성 요소 값이 다 같을 수 없음 (일부가 같을 수는 있음)

### 4.1.4 TCP 소켓 프로그래밍

- 운영체제는 TCP 커넥션의 생성과 관련된 기능을 제공.
    - 소켓 API는 유닉스 OS용으로 개발 되었으나, 지금은 대부분의 운영체제 와 프로그램 언어에서 사용 가능
    - TCP 소켓 API의 주요 인터페이스
        - `s = socket(<parameters>)` : 연결이 되지 않은 익명의 새 소켓 생성
        - `bind(s, <local IP:port>)` : 소켓에 로컬 포트 번호와 인터페이스 할당
        - `connect(s, <remote IP:port>)` : 로컬의 소켓과 원격의 호스트, 포트 사이에 TCP 커넥션 생성
        - `listen(s, ...)` : 커넥션을 받아들이기 위해 로컬 소켓에 허용함을 표시
        - `s2 = accept(s)` : 누군가 로컬 포트에 커넥션을 맺기를 기다림
        - `n = read(s, buffer, n)` : 소켓으로부터 버퍼에 n바이트 읽기 시도
        - `n = write(s, buffer, n)` : 소켓으로부터 버퍼에 n바이트 쓰기 시도
        - `close(s)` : TCP 커넥션을 완전히 끊음
        - `shutdown(s, <side>)` : TCP 커넥션의 입출력만 닫음
        - `getsockopt(s, ...)` : 내부 소켓 설정 옵션 값을 읽음
        - `setsockopt(s, ...)` : 내부 소켓 설정 옵션 값을 변경
- 소켓 API를 사용하면 TCP 종단(endpoint)데이터 구조 생성, 원격 서버의 TCP 종단에 그 종단 데이터 구조를 연결하여 데이터 스트림 읽고 쓰기 가능.
- TCP API는 기본적인 네트워크 프로토콜의 핸드 셰이킹, TCP 데이터 스트림, IP 패킷간의 분할 및 재조립에 대한 세부사항을 숨김
- 클라이언트-서버 간의 HTTP 트랜잭션을 수행하는 소켓 API 슈도코드. C(Client), S(Server)
    1. [S1] 새 소켓 만들기(`socket`)
    2. [S2] 80포트로 소켓을 묶기
    3. [S3] 소켓 커넥션 허가(`listen`)
    4. [S4] 커넥션 대기
    5. [C1] IP 주소와 포트 얻기
    6. [C2] 새로운 소켓 생성(`socket`)
    7. [C3] 서버의 IP:포트로 연결(`connect`)
    8. [S5] 애플리케이션 커넥션 통지
    9. [C4] 성공적으로 연결 
    10. [C5] HTTP 요청을 보내기(`write`) / [S6] 요청 읽기 시작(`read`)
    11. [C6] HTTP 응답 대기(`read`) 
    12. [S7] HTTP 요청 메세지 처리
    13. [S8] HTTP 응답 보내기(`write`) / [C7] HTTP 응답 처리
    14. [S9] 커넥션 닫기(`close`) / [C8] 커넥션 닫기(`close`)

## 4.2 TCP 성능에 대한 고려

- HTTP 트랜잭션의 성능은 TCP 성능에 영향을 받음

### 4.2.1 HTTP 트랜잭션 지연

- 대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생
    - 트랜잭션 처리 시간은 TCP 커넥션설정/요청, 응답 전송 시간 대비 짧음.
- 트랜잭션을 지연시키는 원인
    1. (방문 히스토리가 없을 때) DNS 이름 분석 인프라를 통해 IP주소로 변환하는데 걸리는 시간
        - 현재는 인터넷 인프라의 발전으로 밀리초 단위 내로 끝남
    2. 커넥션 설정 시간
        - 새로운 TCP 커넥션 마다 1초 미만의 설정 시간이 소요되지만, 수백 개의 HTTP 트랜잭션이 만들어지면 소요 시간이 크게 증가
    3. 커넥션이 맺어지고 요청 메시지가 TCP 파이프를 통해 전달되고 서버에 의해 처리되는 데까지의 시간
    4. 웹 서버가 HTTP 응답을 보내는 시간
- 그 외, 하드웨어 성능, 네트워크와 서버 전송 속도, 요청과 응답메시지의 크기, 클라이언트와 서버간의 거리, TCP 프로토콜의 기술적인 복잡성이 지연에 영향을 미침

### 4.2.2 성능 관련 중요 요소

- HTTP 프로그래머에게 영향을 주는 가장 일반적인 TCP 관련 지연
    - 4.2.3 TCP 커넥션의 핸드 셰이크 설정
    - 4.2.5 인터넷 혼잡 제어용 TCP 의 느린 시작(slow-start)
    - 4.2.6 데이터를 한데 모아 한번에 전송하기 위한 네이글(nagle) 알고리즘
    - 4.2.4 TCP 편승(piggyback) 확인응답(acknowledgment)을 위한 확인응답 지연 알고리즘
    - 4.2.7 TIME_WAIT 지연과 포트 고갈

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

- 새로운 커넥션을 열 때, TCP 소프트웨어는 커넥션을 맺는 조건을 맞추기 위해 연속으로 IP 패킷 교환
    - 작은 크기의 데이터 전송에 커넥션이 사용되면 패킷 교환이 HTTP 성능을 저하
- TCP 핸드셰이크 순서
    1. 클라이언트가 새 TCP 커넥션을 생성하기위해 작은 TCP 패킷(보통 40~60 byte)를 서버에 전송.
        - 커넥션 생성 요청을 의미하는 SYN 플래그를 포함
    2. 해당 커넥션을 받으면 커넥션 매개 변수를 산출하고 커넥션 요청이 받아들여졌다는 뜻의 'SYN'와 'ACK' 플래그를 포함한 TCP 패킷을 클라이언트에 전송 
    3. 클라이언트는 커넥션이 잘 맺여졌음을 알리기 위해 확인 응답신호('ACK')를 전송. 오늘날의 TCP는 이 확인응답과 함께 데이터를 보낼 수 있음
- HTTP 프로그래머는 패킷은 보지 못하고, 새 TCP 커넥션 생성에 대한 지연만 알 수 있다.
- HTTP 트랜잭션이 큰 데이터를 주고 받지 않으면, TCP 구성에 더 많은 시간을 써서 눈에 띄는 지연 발생
    - TCP의 ACK 패킷은 HTTP 요청 메세지 전체를 전달할 수 있을만큼 큼
    - 많은 HTTP 서버 응답 메시지는 하나의 IP 패킷에도 담길 수 있음
- TCP 구성으로 인한 지연을 제거하기 위해 어떻게 HTTP가 기 존재하는 커넥션을 재활용하는지 알아볼 것

### 4.2.4 확인응답 지연

- 인터넷 자체가 패킷 전송을 보장하는 것이 아니므로(인터넷 라우터는 과부하시에 패킷 파기 가능), TCP는 성공적인 데이터 전송 보장을 위해 자체 확인 체계를 갖음
- 각 TCP 세그먼트는 순번, 데이터 무결성 체크섬 포함.
    - 수신자가 세그먼트를 잘 받으면 확인 응답 패킷을 송신자에게 반환
    - 송신자가 확인 응답 메세지를 받지 못하면 패킷이 파기되거나 오류가 있는 것으로 판단하여 재전송
- 확인 응답은 크기가 작아서, TCP는 같은 방향으로 송출되는 데이터 패킷에 확인 응답을 [편승(piggyback)](http://www.ktword.co.kr/abbr_view.php?m_temp1=3242)
    - 송출 데이터 패킷 + 확인 응답을 묶어 보내 효율적으로 사용
    - 편승을 늘리기 위해서 많은 TCP 스택은 '확인 응답 지연' 알고리즘 구현
        - 확인응답을 특정 시간(보통 0.1~0.2초) 동안 버퍼에 저장, 편승시키기 위한 송출 데이터 패킷 탐색
        - 일정 시간내에 못찾으면 별도 패킷으로 전송
- HTTP 동작방식은 요청/응답만 있어서 확인 응답이 송출 데이터 패킷에 편승할 확률이 적다.
    - 확인 응답 지연 알고리즘으로 인해 지연이 자주 발생.
    - 운영체제에 따라 다르지만, 지연관련 기능을 수정/비활성화 할 수 있음
- TCP 내부 알고리즘은 잘못 만들어진 애플리케이션으로부터 인터넷을 보호하도록 설계 되어있음
    - TCP 설정 수정시, 기존 알고리즘이 피하려는 문제를 애플리케이션이 발생시키지 않을 것이라고 확신할 수 있어야 함

### 4.2.5 TCP 느린시작(slow start)

- TCP 커넥션은 시간이 지나면서 자체적으로 '튜닝'된다
    - TCP 커넥션 생성 초기에 최대 속도를 제한하다가 데이터가 잘 전송됨에 따라 속도 제한을 높여감
    - 조율하는 것을 TCP 느린 시작이라고 하며, 갑작스런 인터넷 부하와 혼잡 방지에 사용
- TCP 느린 시작은 TCP가 한번에 전송할 수 있는 패킷 수를 제한
    - HTTP 트랜잭션에서 전송할 데이터가 많아도, 처음부터 한번에 전송할 수 없음
    - 한 개의 패킷 전송 후 확인응답을 받으면 2개의 패킷을 보낼 수 있고, 그 패킷에 대한 확인 응답에 각각 2개의 패킷을 보낼 수 있음. (1 → 2 → 4 → 8 → ...)
        - 혼잡 윈도를 연다(opening the congestion window)고 한다
- 혼잡 제어 기능 때문에, 새 커넥션은 튜닝된 커넥션보다 느림
    - 튜닝된 커넥션이 빠르므로, 이미 존재하는 커넥션을 재사용하는 기능이 있음

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

- 애플리케이션이 어떤 크기의 데이터(1바이트라도)든지 TDP 스택으로 전송할 수 있도록 TCP는 데이터 스트림 인터페이스 제공.
    - 각 TCP 세그먼트는 40바이트 상당의 플래그, 헤더를 포함하므로 작은 데이터를 자주 보내면 성능이 떨어짐
- 네이글 알고리즘(John Nagle 발명)은 효율을 위해, 패킷 전송 전 많은 양의 TCP 데이터를 한 덩어리로 합침
    - 세그먼트 최대 크기(LAN상 1,500byte/인터넷 상 수백byte)가 되지 않으면 미전송.
    - (1) 모든 패킷이 확인 응답 받았거나 (이때는 최대 크기 이하도 전송) (2) 전송하기 충분한 패킷이 쌓였으면 데이터 전송.
- 네이글 알고리즘은 HTTP 성능에 문제를 발생시킴
    1. 작은 HTTP 메시지는 패킷을 못 채워, 앞으로 생길지 모를 데이터를 기다리며 지연 됨.
    2. 네이글 알고리즘은 확인응답 지연과 함께 쓰이면 지연이 길어짐.
        - 확인 응답 도착시까지 데이터 전송 대기(네이글) + 확인 응답을 지연
- 성능 향상을 위해 HTTP 스택에 TPC_NODELAY 파라미터를 이용하여 네이글알고리즘 비활성화 가능
    - 작은 크기의 패킷이 너무 많이 생기지 않도록 유의

### 4.2.7 TIME_WAIT의 누적과 포트 고갈

- TIME_WAIT 포트 고갈은 성능 측정시에 심각한 성능 저하 발생, 하지만 보통 실제 상황에는 문제 없음
- TCP 커넥션 종단에서 TCP 커넥션을 끊으면, 종단에서 커넥션의 IP주소와 포트를 메모리의 작은 제어 영역(control block)에 기록
    - 같은 주소, 포트 번호를 사용하는 새 TCP 커넥션이 일정 시간 동안 생성되지 않게 하기 위함
    - 이전 커넥션과 관련된 패킷이 새 커넥션에 삽임되는 문제 방지.
    - 세그먼트 최대 생명주기의 2배(2MSL, 보통 2분) 정도 유지
- 현대의 빠른 라우터덕에 커넥션이 닫힌 후에 중복되는 패킷이 생기는 경우가 거의 없음.
- 2MSL를 더 짧은 값으로 수정하는 운영체제도 있지만, 값 수정에 조심
    - 이전 커넥션 패킷이 새 커넥션에 삽입되면 패킷 중복 및 TCP 데이터 충돌 발생
- 성능 측정 시험 시에는, 가능한 커넥션 조합이 제한되고, 포트 재활용이 불가능함.
    - 실제 상황에 문제는 없으며 생각지도 못했던 성능상의 문제가 생긴 것으로 오해할 수 있음
    - 성능 측정시 관련 내용은 책을 참조

## 4.3 HTTP 커넥션 관리

- HTTP 커넥션 관리에 중요한 HTTP 커넥션 헤더와 최적화 기술에 대해 알아 보기

### 4.3.1 흔히 잘못 이해하는 Connection 헤더

- HTTP는 클라이언트와 서버 사이에 중개 서버(프락시 서버, 캐시 서버 등)를 허락
    - 두 인접한 HTTP 애플리케이션이 맺고 있는 커넥션에만 적용될 옵션을 지정해야 할 때가 있음.
- HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표(,)로 구분하여 가지며, 다른 커넥션에 전달 되지 않음.

    ```
    HTTP/1.1 200 OK
    Cache-control: max-age=3600
    Connection: meter, close, bill-my-credit-card
    Meter: max-uses=3, max-refues=6, dont-report
    ```

- Connection 헤더에는 다음 세 가지 종류의 토큰이 전달될 수 있음. 혼란 주의
    - HTTP 헤더 필드 명: 이 커넥션에만 해당 되는 헤더 나열. (예: `meter`)
    - 임시적 토큰 값: 커넥션에 대한 비표준 옵션 (예: `bill-my-credit-card`)
    - `close` 값: 작업이 완료되면 커넥션이 종료되어야 함을 의미
- Connection 헤더에 있는 모든 헤더 필드는 메시지를 다른 곳에 전달하는 시점에 삭제 되어야 함.
    - Connection 헤더에는 홉별(hop-by-hop) 헤더 명을 기술. '헤더 보호하기'라고 함
- HTTP 애플리케이션이 Connection 헤더와 메세지를 전달 받으면, 수신자는 요청에 기술되어 있는 모든 옵션 적용.
    - 다음 홉에 메시지 전달 전에 Connection 헤더와 헤더 내에 기술되어있던 모든 헤더 삭제.
    - Connection 헤더에 기술되지 않은 홉별 헤더도 있음.
        - `Proxy-Authenticate`, `Proxy-Connection`, `Transfer-Encoding`, `Upgrade`

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

- 커넥션 관리가 잘 되지 않으면 TCP 성능이 나빠짐.
- 순차적 트랜잭션 처리 예시: 3개의 이미지가 있는 웹 페이지 = 4개(1 HTML + 3 이미지)의 트랜잭션
    - 각 트랜잭션이 새 커넥션을 필요로 하면, 커넥션 맺을 때 발생하는 지연과 느린 시작 지연이 발생
    - 모든 객체를 받기 전까지 빈 화면이므로(개발 사항에 따라 다를 수 있음) 사용자는 심리적 지연을 느낌
- HTTP 커넥션 성능을 향상 시킬 수 있는 기술 4가지.
    1. 병렬(parallel) 커넥션
        - 여러 개의 TCP 커넥션을 통한 동시 HTTP 요청
    2. 지속(persistent) 커넥션
        - 커넥션을 맺고 끊는 데서 발생하는 지연을 제거하기 위한 TCP 커넥션의 재활용
    3. 파이프라인(pipelined) 커넥션
        - 공유 TCP 커넥션을 통한 병렬 HTTP 요청
    4. 다중(multiplexed) 커넥션
        - 요청과 답변들에 대한 중재. 실험적 기술

## 4.4 병렬 커넥션

- HTTP 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리 가능.

### 4.4.1 병렬 커넥션은 페이지를 더 빠르게 내려받는다

- 단일 커넥션의 대역폭 제한과 커넥션이 동작하지 않고 있는 시간을 활용하면, 객체가 여러 개 있는 웹페이지를 더 빠르게 내려받을 수 있음.
    - 각 커넥션의 지연 시간을 겹치게 하면 총 지연 시간을 줄일 수 있음
    - 클라이언트의 인터넷 대역폭을 한 개의 커넥션이 다쓰는게 아니면 남은 대역폭을 다른 객체를 받는데 사용 가능

### 4.4.2 병렬 커넥션이 항상 더 빠르지는 않다

- 일반적으로 더 빠르기는 하지만 항상 빠르지는 않다.
- 클라이언트의 네트워크 대역폭이 좁을 때(예: 28.8Kbps 모뎀으로 연결된 경우)는 대부분의 시간을 데이터 전송에만 사용하게 됨
- 다수의 커넥션은 메모리를 많이 소모하고 자체적인 성능 문제 발생
    - 복잡한 웹 페이지는 수 십~수 백 개의 개체 포함
    - 서버는 다른 여러 사용자의 요청도 처리해야하기 대문에 수백개의 커넥션을 허용하지는 않음
    - 100명의 사용자에 100개의 커넥션을 맺고있다면 10,000개의 커넥션을 떠안아 성능이 떨어짐.
- 실제로는 병렬 커넥션을 사용하지만 적은 수(보통 6~8개)의 커넥션을 허용.
    - 특정 클라이언트가 과도한 커넥션이 맺어진 경우 임의로 서버가 끊을 수 있음

### 4.4.3 병렬 커넥션은 더 빠르게 '느껴질 수' 있다

- 실제로 페이지를 더 빠르게 내려받는 것이 아니더라도, 사용자는 빠르게 느껴질 수 있다
    - 총 다운로드 시간이 더 걸리더라도, 작업이 일어나는 것으로 눈으로 확인할 수 있어서

## 4.5 지속 커넥션

- 웹 클라이언트는 보통 같은 사이트에 여러 개의 커넥션을 맺음
    - 웹 페이지 내에 첨부된 이미지, 하이퍼링크 등도 같은 사이트 내에 있음.
    - 서버에 HTTP 요청을 시작한 애플리케이션이 웹 페이지내 다른 것을 가져오기 위해 그 서버에 다시 요청하는 속성을 사이트 지역성(site locality)라 함.
- HTTP/1.1 을 지원하는 기기는 처리가 완료된 후에 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용 할 수 있음
    - 처리가 완료된 이후에도 계속 연결된 상태로 유지하는 TCP 커넥션을 **지속 커넥션** 이라고 부름
    - 비지속 커넥션은 각 처리가 끝날 때마다 커넥션을 끊지만, 지속 커넥션은 커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지
- 이미 맺어진 지속 커넥션을 재사용함으로써, 커넥션 준비 시간을 절약할 수 있음.
    - TCP 느린 시작으로 인한 지연도 피할 수 있음

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

- 병렬 커넥션의 단점:
    1. 각 트랜잭션마다 새로운 커넥션을 맺고 끊기 때문에 시간 및 대역폭 소모
    2. 각각의 새 커넥션은 TCP 느린 시작 때문에 성능이 떨어짐
    3. 실제로 연결할 수 있는 병렬 커넥션의 수에는 제한이 있음
- 지속 커넥션의 병렬 커넥션 대비 장점
    1. 커넥션을 맺기 위한 사전 작업, 지연을 줄여줌
    2. 튜닝된 커넥션을 유지함
    3. 커넥션수를 줄여줌
- 지속 커넥션을 잘못 관리하는 경우
    - 연결된 상태로 있는 수많은 커넥션이 쌓임.
    - 로컬 리소스, 원격 클라이언트, 서버의 리소스에 불필요한 소모 발생
- 지속 커넥션은 병렬 커넥션과 함께 사용시 가장 효과적. 오늘날은 적은 수의 병렬 커넥션을 맺고 유지함
- 지속 커넥션 타입:
    1. HTTP/1.0+ 에 'keep-alive'
    2. HTTP/1.1 '지속' 커넥션

### 4.5.2 HTTP/1.0+ 의 Keep-Alive 커넥션

- HTTP/1.0 브라우저/서버는 실험적이었던 keep-alive 커넥션을 지원, 초기 버전은 설계에 문제가 있지만 HTTP/1.1 에서 수정됨
- keep-alive 커넥션은 커넥션을 맺고 끊는데 필요한 작업이 없어서 시간이 단축됨. (TCP 느린 시작 발생 X)

### 4.5.3 Keep-Alive 동작

- keep-alive는 HTTP/1.1 명세에서 제거됨.
    - 하지만 아직 널리 사용하기 때문에, 그것을 처리할 수 있게 개발되어야함.
- keep-alive 동작 방식
    - keep-alive 핸드셰이크: [RFC 2068](https://tools.ietf.org/html/rfc2068)(HTTP/1.1 이전 명세) 참조
    - 클라이언트는 커넥션을 유지하기 위해 `Connection: Keep-Alive` 헤더를 포함
    - 서버는 그다음 요청도 이 커넥션에서 받고자 한다면, 같은 헤더를 포함하여 응답
    - 클라이언트는 `Connection: Keep-Alive` 가 없으면, 지원하지 않는다고 판단해 커넥션을 끊을 것이라 추정

### 4.5.4 Keep-Alive 옵션

- Keep-Alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐, 실제로 서버나 클라이언트가 따를 필요는 없음
    - 언제든지 keep-alive 커넥션을 끊을 수 있고, keep-alive 커넥션에서 처리되는 트렌잭션 수 제한 가능
- keep-alive 의 동작은 Keep-Alive 헤더의 쉼표로 구분된 옵션으로 제어 가능
    - `timeout` 파라미터. Keep-Alive 응답 헤더를 통해 보냄. 커넥션이 얼마간 유지될 것인지 의미. 보장X
    - `max` 파라미터. Keep-Alive 응답헤더를 통해 보냄. 커넥션이 몇개의 HTTP 트랜잭션을 처리할 때까지 유지될 것인지 의미
    - Keep-Alive 헤더는 진단이나 디버깅을 주 목적으로 하는, 처리되지 않은 임의의 속성도 지원. `이름[=값]`
- Keep-Alive 헤더 사용은 선택 사항이지만, Connection: Keep-Alive 헤더가 있을 때만 사용 가능
- 5개의 추가 트랜잭션이 처리 될동안 커넥션을 유지하거나, 2분동안 커넥션을 유지하라는 내용의 Keep-Alive 응답 헤더

    ```
    Connection: Keep-Alive
    Keep-Alive: max=5, timeout=120
    ```

### 4.5.5 Keep-Alive 커넥션 제한과 규칙

- keep-alive 는 HTTP/1.0에서 기본적으로 사용되지 않음
    - keep-alive 커넥션을 사용하려면 Connection: Keep-Alive 요청 헤더 필요
- 커넥션을 계속 유지하려면 모든 메시지에 Connection: Keep-Alive 요청 헤더 필요
    - 클라이언트가 해당 요청 헤더를 보내지 않으면 서버는 요청을 처리 후 커넥션을 끊음
    - 클라이언트는 Connection: Keep-Alive 응답 헤더가 없는 것을 보고 서버가 응답 후 커넥션 끊을 것음을 알 수 있음
- 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알아야 커넥션 유지 가능
    - 엔터티 본문이 정확한 Content-Length 값, 멀티파트 미디어 형식(multipart media type)을 갖거나 청크 전송 인코딩(chunked transfer encoding)으로 인코드 되어야 함을 뜻함
    - keep-alive 커넥션에서 잘못된 Content-Length 값을 보내면, 트랜잭션이 끝나는 시점에 기존 메시지의 끝과 새 메시지의 시작점을 정확히 알 수 없어서 좋지 않음
- 프락시와 게이트웨이는 Connection 헤더의 규칙을 철저히 지켜야함
    - 프락시와 게이트웨이는 메시지 전달하거나 캐시 넣기 전에 Connection 헤더에 명시된 모든 헤더 필드와 Connection 헤더를 제거해야함
- 정석대로라면, keep-alive 커넥션은 Connection 헤더를 인식하지 못하는 프락시 서버와 맺어지면 안됨
    - 4.5.6의 멍청한(dumb) 프락시로 인해 발생할 문제를 예방하기 위함이지만, 현실적으로 쉽지 않음
- 기술적으로 HTTP/1.0을 따르는 기기로부터 받는 모든 Connection 헤더 필드(Connection: Keep-Alive 포함)은 무시해야 한다. 오래된 프락시 서버로부터 실수로 전달 될 수 있기 때문.
    - 어떤 클라이언트나 서버는 오래된 프락시에 행(hang)이 걸릴 수 있는 위험이 있음에도 이 규칙을 지키지 않기도 함.
- 클라이언트는, 응답 전체를 받기 전에 커넥션이 끊어졌을 경우, 별다른 문제가 없으면 요청을 다시 보낼 수 있게 준비 되어있어야 함.
    - 그렇지 않으면 요청이 반복될 경우 문제가 발생할 수 있음

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

- 클라이언트 요청에 Connection: Keep-Alive 헤더가 있으면, 클라이언트가 현재 연결하고 있는 TCP 커넥션을 끊지 않으려고 유지한다는 의미
- 서버가 keep-alive 를 지원한다면 헤더를 응답에 포함하고, 지원하지 않는다면 포함하지 않을 것이다.

***Connection 헤더의 무조건 전달***

- 프락시는 Connection 헤더를 이해하지 못하고 해당 헤더를 삭제하지 않고다음 프락시에 전달 함.
    1. 웹 클라이언트가 프락시에 Connection: Keep-Alive 헤더와 함께 메시지 보내고 응답 대기
    2. 멍청한 프락시가 요청받은 HTTP 의 Connection 헤더(단순 확장 헤더로 취급)을 이해하지 못해, 그대로 다음 서버에 메시지 전달.
        - 하지만 Connection 헤더는 홉별(hop-by-hop) 헤더(한개의 전송링크에 적용되며 다음 서버로 전달이 안됨). 여기에서 문제 시작
    3. 전달된 HTTP 요청이 서버에 도착. 웹 서버는 Connection: Keep-Alive 헤더를 받아서 프락시가 커넥션을 유지하자가 요청하는 것으로 판단. 
        - 서버는 프락시와의 커넥션 유지에 동의하고 Connection: Keep-Alive 헤더를 포함하여 응답.
    4. 멍청한 프락시는 서버로부터의 Connection: Keep-Alive 헤더를 포함한 응답 메시지를 클라이언트에 전달
        - 클라이언트는 프락시가 커넥션 유지에 동의했다고 추정.
    5. 프락시는 받은 모든 데이터를 클라이언트에 전송 후, 서버가 커넥션을 끊기를 대기. 하지만 서버는 커넥션 유지를 요청한 것으로 알아서 커넥션을 끊지 않음. 프락시는 커넥션이 끊어지기 전까지 계속 커낵션 끊기를 대기 
    6. 클라이언트가 응답 메시지를 받은 이후, 커넥션이 유지되고 있는 프락시에 다음 요청을 보내기 시작. 프락시는 같은 커넥션 상에서 다른 요청이 오는 경우는 예상하지 못하기 때문에 요청 무시
        - 브라우저는 아무 응답없이 로드 중으로 표시
    7. 이런 잘못된 통신 때문에, 브라우저 자신이나 서버가 타임 아웃 나서 커넥션이 끊길 때까지 대기.
- 그 외에도 무조건 전달(blind relay)과 핸드셰이크의 전달(forwarded handshaking)으로 문제 발생하는 경우도 있음

***프락시와 홉별 헤더***

- 이런 잘못된 통신을 피하려면 프락시는 Connection헤더 및 Connection 헤더에 명시된 헤더를 전달하면 안됨.
    - Connection: Keep-Alive 헤더를 받으면 Connection헤더와 Keep-Alive헤더도 전달하면 안됨.
- Connection 헤더 값으로 명시되지 않는 홉별 헤더도 전달하면 안됨
    - `Proxy-Authenticate`, `Proxy-Connection`, `Transfer-Encoding`, `Upgrade` 등

### 4.5.7 Proxy-Connection 살펴보기

- 넷스케이프 브라우저 및 프락시 개발자는 모든 웹 애플리케이션이 HTTP 최신버전을 지원하지 않아도 모든  헤더를 전달하는 문제를 해결할 수 있는 차선책 제시
    - 클라이언트의 요청이 중개서버를 통해 이어지는 경우에 모든 헤더를 전달하는 문제를 해결하기 위해 Proxy-Connection이라는 헤더를 사용하는 것(모든 상황에서 동작하지는 않음)
    - Proxy-Connection은 프락시를 별도로 설정할 수 있는 현대의 브라우저들에서 지원하며, 많은 프락시들도 인식
- 멍청한 프락시 문제 해결을 위해 브라우저에서 일반적으로 전달하는 Connection헤더 대신 비표준인 Proxy-Connection 확장 헤더를 프락시에 전달
    - 프락시가 Proxy-Connection 헤더를 무조건 전달하더라도 웹 서버가 무시하므로 문제가 없음.
    - (지속 커넥션 핸드 셰이킹을 이해할 수 있는) 영리한 프락시라면, 의미없는 Proxy-Connection 헤더를 Connection 헤더로 변경하여 원하던 효과를 얻게 됨
- 이 방식은 클라이언트와 서버 사이에 한개의 프락시만 있는 경우에만 동작
    - 멍청한 프락시 양옆에 영리한 프락시가 잇다면, 동일하게 잘못된 헤더를 만들어 내는 문제가 발생
- 문제를 일으키는 프락시들은 방화벽, 캐시 서버, 리버스 프락시 서버 가속기와 같이 네트워크상 '보이지 않는' 경우게 많음.
    - 브라우저는 이러한 기기를 볼 수 없으므로 Proxy-Connection 헤더를 보낼 수 없음.
    - 보이지 않는 웹 애플리케이션들이 지속 커넥션을 명확히 구현하는 것이 중요

### 4.5.8 HTTP/1.1의 지속 커넥션

- HTTP/1.1 에서는 keep-alive 커넥션을 지원하지 않는대신, 설계가 개선된 지속 커넥션을 지원
    - 목적은 keep-alive와 같지만 더 잘 동작함
- HTTP/1.0의 keep-alive와 달리 HTTP/1.1 지속 커넥션은 기본으로 활성화
    - HTTP/1.1은 별도 설정이 없으면 모든 커넥션을 지속 커넥션으로 취급
    - HTTP/1.1 애플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connect: close 헤더를 명시해야 함
    - keep-alive 커넥션이 선택 사항이 아닐 뿐 아니라 지원자체를 하지 않는 점에서 HTTP.0과 크게 다름
- HTTP/1.1 클라이언트는 응답에 Connection: close 헤더가 없으면 커넥션을 유지하는 것으로 추정
    - 하지만 언제든 클라이언트/서버는 언제든지 커넥션을 끊을 수 있음
    - = Connection: close 를 보내지 않는 것이 커넥션을 영원히 유지하겠다는 뜻은 아님

### 4.5.9 지속 커넥션의 제한과 규칙

- 클라이언트가 요청에 Connection: close 헤더를 포함해 보냈다면, 클라이언트는 그 커넥션으로 추가적인 요청을 보낼 수 없음
- 클라이언트가 해당 커넥션으로 추가적인 요청을 보낼 것이 아니라면 마지막 요청에 Connection: close 헤더를 추가해야함
- 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있을 때에만 커넥션 지속 가능
    - 엔티티 본문은 정확한 Content-Length 값을 갖거나 청크 전송 인코딩(chunked transfer encoding)으로 인코드 되어야 함
- HTTP/1.1 프락시는 클라이언트와 서버 각각에 대해 별도 지속 커넥션을 맺고 관리해야함
- HTTP/1.1 프락시 서버는 클라이언트가 커넥션 관련 기능에 대한 클라이언트의 지원 범위를 알고 있지 않은 한 지속 커넥션을 맺으면 안됨
    - 오래된 프락시가 Connection 헤더를 전달하는 문제. 현실적으로 쉽지 않고, 많은 벤더가 지키지 않고 있음
- 서버는 메시지 전송 중간에 커넥션을 끊지 않을 것이고, 커넥션이 끊기기 전에 최소 한개의 요청에 대해 응답할 것이지만, HTTP/1.1 기기는 Connection 헤더의 값과 별개로 언제든지 커넥션을 끊을 수 있음
- HTTP/1.1 애플리케이션은 중간에 끊어지는 커넥션을 복구 할 수 있어야 함.
    - 클라이언트는 다시 보내도 문제가 없는 요청이라면 가능한 한 다시 보내야 함
- 클라이언트는 전체 응답을 받기 전에 커넥션이 끊어지면, 요청을 반복해서 보내도 문제가 없는 경우에 요청을 다시 보낼 준비가 되어 있어야함.
- 하나의 사용자 클라이언트는 서버 과부하 방지를 위해 넉넉잡아 두 개의 지속 커넥션 만을 유지해야 함.
    - N명의 사용자 → 2N개의 커넥션

## 4.6 파이프라인 커넥션

- HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝 할 수 있음. keep-alive 커넥션의 성능을 더 높임
    - 여러개의 요청은 응답이 도착하기 전까지 큐에 쌓임
    - 첫번째 요청이 서버에 전달되면, 이어서 두번째와 세번째 요청이 전달됨
    - 대기 시간이 긴 네트워크 상황에서 네트워크상의 왕복으로 인한 시간을 줄여 성능을 높임.
- 파이프라인 제약 사항
    - HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인 전까지 파이프라인을 이어서는 안됨
    - HTTP 응답은 요청 순서와 같게 와야함.
        - HTTP는 메시지 순번이 매겨져 있지 않아서, 응답이 순서없이 오면 정렬시킬 방법이 없음
    - 클라이언트는 커넥션이 언제 끊어지더라도 완료되지 않은 요청이 파이프라인에 있으면 언제든지 다시 보낼 수 있어야함
        - 지속 커넥션 맺고 나서 바로 여러 개의 요청을 보낸다하더라도 서버는 일부 요청만 처리하고 커넥션을 임의로 끊을 수 있음. 남은 실채한 요청을 다시 커넥션을 맺고 보낼 수 있어야 함
    - HTTP 클라이언트는 POST 요청과 같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안됨.
        - 에러 발생시에 파이프라인을 통한 요청 중에 어떤 것이 서버에서 처리됐는지 알 수 없음.
        - POST와 같은 비멱등(nonidempotent) 요청을 재차 보내면 문제가 생길 수 있으므로 보내면 안됨.

![https://mdn.mozillademos.org/files/13727/HTTP1_x_Connections.png](https://mdn.mozillademos.org/files/13727/HTTP1_x_Connections.png)

순차 커넥션 vs 지속 커넥션 vs 파이프라인 지속 커넥션

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

- 커넥션 관리(특히 끊기)에는 명확한 기준이 없음. 관련 기술 문서도 별로 없음

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

- 어떠한 HTTP 클라이언트, 서버, 프락시든 언제든지 TCP 전송 커넥션을 끊을 수 있음
- 보통은 메시지를 다 보낸 다음에 끊지만, 에러가 있는 상황에서는 엉뚱한 곳에서 끊길 수 있음.
- 파이프라인 지속 커넥션도 동일한게 언제든지 임의로 끊을 수 있음
    - 지속 커넥션이 일정 시간 동안 요청을 전송하지 않고 유휴상태라 서버가 커넥션을 끊을 수 있음
    - 서버가 커넥션을 끊는 시점에 클라이언트가 데이터를 전송하지 않을 것이라고 확신할 수 없음

### 4.7.2 Content-Length와 Truncation

- 각 HTTP 응답 본문은 정확한 Content-Length 값을 가져야함.
    - 일부 오래된 서버는 자신이 커넥션을 끊으면 데이터 전송이 끝났다고 의미해서 Content-Length 값이 없거나 틀리게 응답하기도 함
- 클라이언트나 프락시가 커넥션이 끊어졌다는 응답 이후, 실제 전달된 엔터티 길이와 Content-Length 값이 일치하지 않거나, 존재하지 않으면 서버에 물어봐야 함
- 만약 수신자가 캐시 프락시일 경우 잠재적인 에러가 복합적으로 발생하는 것을 최소화하기 위해 응답을 캐시하면 안됨
- 프락시는 Content-Length 정정하려고 하지말고 바로 전달해야함

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

- 커넥션은 에러가 없어도 언제든지 끊을 수 있음. 예상치 못하게 끊겼을 때에 대한 대응이 되어있어야함.
    - 클라이언트가 트랜잭션 수행 중에 끊기면, 재시도하더라도 문제가 없으면 커넥션을 다시 맺고 재전송.
- 멱등한(idempotent) 요청(GET, HEAD, PUT, DELETE, TRACE)은 결과에 영향을 미치지 않으므로 재전송
- 비멱등한(nonidempotent) 요청(POST)는 끊겼을때에 결과를 알 수 없으므로 파이프라인을 통해 요청하면 안됨.
    - 비멱등 메서드나 순서에 대해 에이전트가 요청을 다시 보낼 수 있는 기능을 제공하더라도, 자동으로 재시도 하면 안됨
    - 대부분의 브라우저는 캐시된 POST 요청 페이지를 다시 로드하려 할 떄, 재 요청을 원하는지 묻는 대화상자 노출

### 4.7.4 우아한 커넥션 끊기

- TCP 는 양방향. 양쪽에 데이터 입력 큐/출력 큐가 있음.

***전체 끊기와 절반 끊기***

- 애플리케이션은 TCP 입력/출력 채널 중 한 개 이상 끊을 수 있다.
- `close()`  를 호출하면 TCP 커넥션의 입출력 채널의 커넥션을 모두 끊음.
