2장: URL과 리소스
URI
- URI(Uniform Resource Identifier)는 정보 리소스를 고유하게 식별하고 위치를 지정하는 자원 식별자이다. URI에는 URL, URN(Uniform Resource Name)의 두 가지 종류가 있다.
URL
정의
- URL(Uniform Resource Locator)은 인터넷의 리소스를 가리키는 표준이름이다. URL은 브라우저가 정보를 찾는 데 필요한 리소스의 위치를 가리킨다.
URN과의 비교
- URL은 리소스가 어디 있는지 설명해 리소스를 식별한다.
- URN은 리소스가 어디에 존재하든 상관없이 그 이름만으로 리소스를 식별한다.
URL 구조
- http://www.joes-hardware.com/seasonal/index-fall.html를 예시로 들어보자.
- http는 URL 의 스킴(scheme) 부분이다.
- 스킴은 클라이언트가 리소스에 어떻게 접근하는지를 알려준다.
- www.joes-hardware.com은 서버의 위치이다.
- 리소스가 어디에 호스팅되어 있는지를 알려준다.
- /seasonal/index-fall.html은 리소스의 경로이다.
- 서버에 존재하는 로컬 리소스들 중에서 요청받은 리소스가 무엇인지 알려준다.
- 이렇게 URL을 사용하면 리소스를 일관된 방식으로 지칭할 수 있다. 대부분의 URL은 스킴://서버 위치/경로 구조로 이루어진다.
URL 문법
- 대부분의 URL 스킴 문법은 일반적으로 9개 부분으로 나뉜다.
- 스킴://사용자 이름:비밀번호@호스트:포트/경로;파라미터?질의#프래그먼트
- 스킴: 어떤 프로토콜을 사용해 서버에 접근하는지
- 사용자 이름: 몇몇 스킴은 리소스에 접근하기 위해 사용자 이름을 필요로 함(기본 annonymous)
- 비밀번호: 사용자의 비밀번호
- 호스트: 리소스를 호스팅하는 서버의 호스트명이나 IP 주소
- 포트: 리소스를 호스팅하는 서버가 열어놓은 포트 번호(스킴마다 기본 포트 다름, HTTP의 기본 포트는 80)
- 경로: 서버 내 리소스가 서버 어디에 있는지
- 파라미터: 특정 스킴에서 입력 파라미터를 기술하는 용도, 여러 개를 가질 수 있고 이름: 값 형식의 쌍
- 질의: 스킴에서 애플리케이션에 파라미터를 전달하는 데 사용
- 프래그먼트: 리소스 조각/일부분을 가리키는 이름
스킴: 사용할 프로토콜
- URL을 해석하는 애플리케이션이 어떤 프로토콜을 사용해 리소스를 요청해야 하는지 알려준다.
- 스킴은 알파벳으로 시작해야 하고 : 문자로 구분한다. 대소문자를 가리지 않는다.
호스트와 포트
- 호스트와 포트는 리소스를 호스팅하는 장비와 그 장비 내에서 리소스에 접근할 수 있는 서버가 어디에 있는지 알려준다.
- 호스트는 접근하려는 리소스를 가지고 있는 인터넷상의 호스트 장비를 가리킨다. 호스트 명이나 IP 주소로 제공한다.
- 포트는 서버가 열어놓은 네트워크 포트를 가리킨다.
사용자 이름과 비밀번호
- 몇몇 서버는 본인 데이터에 접근을 허용하기 전에 사용자 이름과 비밀번호를 요구한다. FTP와 같이 사용자 이름과 비밀번호를 요구하는 URL 스킴을 사용하고 그 값들을 채우지 않으면 기본 사용자 이름과 기본 비밀번호가 들어간다. 브라우저마다 기본값이 다르다.
경로
- 리소스가 서버의 어디에 있는지 알려준다.
- 계층적 파일 시스템 경로와 유사한 구조이다.
- / 문자를 기준으로 각 경로 조각으로 나뉜다.
- 각 경로 조각은 자체적인 파라미터 컴포넌트를 가질 수 있다.
파라미터
- 애플리케이션이 서버에 정확한 요청을 하기 위해 필요한 입력 파라미터를 받는 데 사용한다.
- 이름-값 쌍의 리스트로 URL 나머지 부분들로부터 ; 문자로 구분하여 기술한다.
- 각 경로 조각이 자체적으로 파라미터를 가질 수 있다.
질의 문자열
- 요청받을 리소스 형식의 범위를 좁하기 위해 질문/질의를 받을 수 있다.
- & 문자로 구분해 나열한다.
프래그먼트
- HTML 등의 리소스 형식은 본래 수준보다 더 작게 나눌 수 있다.
- 리소스 내의 특정 부분을 가리킬 수 있도록 프래그먼트를 제공한다.
- 일반적으로 HTML 서버는 일부가 아닌 전체만 다루기 때문에 프래그먼트를 전달하지 않는다.
단축 URL
- 웹 클라이언트는 몇몇 단축 URL을 인식하고 사용한다.
상대 URL
- URL은 상대 URL과 절대 URL로 나뉜다.
- 절대 URL은 리소스에 접근하는데 필요한 모든 정보를 가진다.
- 상대 URL은 기저 URL을 사용해 URL을 짧게 표현하는 방식이다.
- 상대 URL로 리소스에 접근하기 위해 필요한 모든 정보를 얻기 위해서는 기저 URL을 이용해야 한다.
- 상대 URL은 프래그먼트이거나 URL의 일부이다.
- URL을 처리하는 브라우저 등의 애플리케이션은 상대 URL과 절대 URL간 상호 변환을 할 수 있어야 한다.
3장: HTTP 메시지
메시지의 흐름
- HTTP 메시지는 HTTP 애플리케이션 간에 주고받은 데이터의 블록들이다.
- HTTP는 트랜잭션 방향을 표현하기 위해 인/아웃바운드라는 용어를 사용한다.
- 메시지가 원 서버로 향하는 것은 인바운드로 이동하는 것이다.
- 모든 처리가 끝난 뒤에 메시지가 사용자 에이전트로 돌아오는 것은 아웃바운드로 이동하는 것이다.
- 모든 메시지(요청/응답)는 다운스트림으로 흐른다.
메시지의 각 부분
- HTTP 메시지는 단순하고 데이터의 구조화된 블록이다.
- 메시지는 시작줄, 헤더 블록, 본문의 세 부분으로 이루어진다.
- 시작줄은 해당 메시지가 어떤 메시지인지 서술한다.
- 헤더 블록은 속성을 서술한다.
- 본문은 데이터를 담으며 없을 수도 있다.
부분 구성
- 시작줄과 헤더는 줄 단위로 분리된 아스키 문자열이다.
- 각 줄은 캐리지 리턴(Cariage Return, CR; 맨 처음 칸으로 이동)과 개행 문자(Line Feed, LF; 다음 줄로 이동)로 구성된 두 글자의 줄바꿈 문자열로 끝난다. 이 줄바꿈 문자열은 CRLF(CR + LF)라고 쓴다.
- 견고한 애플리케이션이라면 그냥 개행 문자도 받아들일 수 있어야 한다.
- 본문은 단순히 선택적인 데이터 덩어리이다.
메시지 문법
- 모든 HTTP 메시지는 요청이거나 응답 메시지로 구분된다.
- 요청 메시지는 웹 서버에 어떤 동작을 요구한다.
- 메서드: 클라이언트 측에서 서버가 수행해주길 바라는 동작
- 요청 URL: 요청 대상이 되는 리소스를 지칭하는 완전한 URL 혹은 URL의 경로 구성요소
- 버전: 해당 메시지에서 사용 중인 HTTP 버전, HTTP/<메이저>.<마이너> 형식
- 헤더: 이름: 값CRLF 형식의 0개 이상의 헤더들
- 엔티티 본문: 임의의 데이터 블록 포함
- <메서드> <요청 URL> <버전> <헤더> <엔티티 본문>
- 응답 메시지는 요청의 결과를 클라이언트에게 돌려준다.
- 상태 코드: 요청 중에 무엇이 일어났는지 설명하는 세 자리의 숫자
- 사유 구절: 상태 코드 의미를 설명하는 짧은 문구
- <버전> <상태 코드> <사유 구절> <헤더> <엔티티 본문>
- 헤더나 엔티티 본문이 없더라도 HTTP 헤더 집합은 항상 빈 줄(CRLF)로 끝나야 한다.
시작줄
메서드
- 많이 쓰이는 HTTP 메서드는 아래와 같다.
- GET: 서버에서 어떤 문서를 가져옴
- HEAD: 서버에서 어떤 문서에 대해 헤더만 가져옴
- POST: 서버가 처리해야 할 데이터를 보냄
- PUT: 서버에 요청 메시지의 본문을 저장
- TRACE: 메시지가 프락시를 거쳐 서버에 도달하는 과정 추적
- OPTIONS: 서버가 어떤 메시지를 수행할 수 있는지 확인
- DELETE: 서버에서 문서 제거
- 모든 서버가 위의 메서드를 모두 구현한 것은 아니다.
- 메서드는 대부분 제한적으로 사용된다.
- HTTP는 확장 가능하게 설계되었기 때문에 서버가 본인만의 메서드를 추가로 구현했을 수도 있다.
상태 코드
- 상태 코드의 종류는 아래와 같다. 전체 범위(정의된 범위) 형식이다.
- 100-199(100-101): 정보
- 200-299(200-206): 성공
- 300-399(300-305): 리다이렉션
- 400-499(400-415): 클라이언트 에러
- 500-599(500-505): 서버 에러
- 상태 코드도 현재 프로토콜의 확장으로 정의된 범위를 벗어나는 코드를 정의할 수 있다.
버전 번호
- 어떤 애플리케이션이 지원하는 가장 높은 HTTP 버전을 가리킨다.
- 버전 번호는 분수로 다루어지지 않는다. HTTP/1.22는 HTTP/1.3보다 크다.
헤더
- 일반 헤더: 요청, 응답에 모두 나타날 수 있음
- 요청 헤더: 요청에 대한 부가정보 제공
- 응답 헤더: 응답에 대한 부가정보 제공
- Entity 헤더: 본문 크기와 콘텐츠, 리소스 그 자체를 서술
- 확장 헤더: 명세에 정의되지 않은 새로운 헤더
메서드
안전한 메서드(safe method)
- HTTP 요청의 결과로 서버에 어떤 작용도 없는 메서드(보장은 개발자가 해야 한다)
GET
- 주로 서버에게 리소스를 달라고 요청할 때 쓰인다.
- HTTP/1.1은 서버가 GET을 구현하도록 요구한다.
HEAD
- GET과 유사하지만 헤더만 반환한다.
PUT
- 서버에 문서를 쓴다.
- 서버에 있는 리소스에 데이터를 입력하기 위해 사용한다.
- 서버가 요청의 본문을 가지고 요청 URL의 이름대로 새 문서를 만들거나 이미 URL이 존재한다면 본문을 사용해 교체하는 것이다.
POST
- 서버에 입력 데이터를 전송하기 위해 설계되었다.
TRACE
- 클라이언트에게 자신의 요청이 서버에 도달했을 때 어떻게 보이게 되는지 알려준다.
- 목적지 서버에서 루프백(loopback) 진단을 시작한다. 클라이언트는 자신과 목적지 서버 사이에 있는 모든 HTTP 애플리케이션의 요청/응답 연쇄를 따라가면서 자신이 보낸 메시지가 망가졌거나 수정되었는지, 어떻게 변경되었는지 확인할 수 있다.
OPTIONS
- 웹 서버에게 지원 범위에 대해 물어본다.
- 특정 리소스에 대해 어떤 메서드가 지원되는지 물어본다.
DELETE
- 서버에게 요청 URL로 지정하는 리소스를 삭제할 것을 요청한다.
- 삭제가 수행되는 것은 보장하지 못한다.
- 서버는 클라이언트에게 알리지 않고 요청을 무시할 수 있다.
확장 메서드
- HTTP는 확장 가능하도록 설계되어 있으므로 새로 기능을 추가해도 과거 구현된 소프트웨어들의 오작동을 유발하지 않는다.
- 확장 메서드에 대해 관용적인 태도를 취한다.
- 프락시는 종단간 행위를 망가뜨리지 않을 수 있다면 알려지지 않은 확장 메서드 메시지를 다운스트림 서버로 전달하려고 시도한다. 종단간 행위를 망가뜨리는 경우 프락시는 501 Not Implemented 상태 코드로 응답해야 한다. → 엄격하게 보내고 관대하게 받아들여라 - Postel 법칙
헤더
일반 헤더
- 클라이언트와 서버 둘 다 사용한다.
요청 헤더
- 요청 메시지를 위한 헤더이다.
- 서버에게 클라이언트가 받고자 하는 데이터의 타입이 무엇인지와 같은 부가 정보를 제공한다.
응답 헤더
- 클라이언트에게 정보를 제공하기 위해 응답 메시지가 가지는 헤더이다.
엔티티 헤더
- 엔티티 본문에 대한 헤더이다.
확장 헤더
- 애플리케이션 개발자에 의해 만들어진 비표준 헤더이다.
- HTTP 프로그램은 확장 헤더의 의미를 모르더라도 용인하고 전달해야 할 필요가 있다.
4장: 커넥션 관리
TCP 커넥션
- 모든 HTTP 통신은 TCP/IP를 통해 이루어진다.
- 일단 커넥션이 맺어지면 클라이언트와 서버 컴퓨터 간 손실/손상/순서 뒤섞임 없이 메시지를 전달할 수 있다.
- 일반적인 통신 과정은 아래와 같다.
- 브라우저가 호스트명을 추출한다.
- 브라우저가 호스트명에 대한 IP 주소를 찾는다.
- 브라우저가 포트번호를 얻는다.
- 브라우저가 IP 주소의 포트로 TCP 커넥션을 생성한다.
- 브라우저가 서버로 HTTP 요청 메시지를 보낸다.
- 브라우저가 서버에서 온 HTTP 응답 메시지를 읽는다.
- 브라우저가 커넥션을 끊는다.
신뢰할 수 있는 데이터 전송 통로인 TCP
- HTTP 커넥션은 몇몇 사용 규칙을 제외하고는 TCP 커넥션에 불과하다.
- TCP 커넥션의 한쪽에 있는 바이트들은 반대쪽으로 순서에 맞게 정확히 전달된다.
- TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다.
- HTTP는 IP, TCP, HTTP로 구성된 프로토콜 스택에서 최상위 계층이다.
- HTTP에 보안 기능을 더한 HTTPS는 TLS 또는 SSL이라 불리기도 하며 HTTP와 TCP 사이에 있는 암호화 계층이다.
- TCP는 세그먼트 단위로 데이터 스트림을 잘게 나누고, 세그먼트를 IP 패킷에 담아 인터넷을 통해 데이터를 전달한다. 이 과정은 TCP/IP 소프트웨어에 의해 처리되며 각 과정은 HTTP 프로그래머에게 보이지 않는다.
- 각 TPC 세그먼트는 하나의 IP 주소에서 다른 IP 주소로 IP 패킷에 담겨 전달된다.
- 각 IP 패킷은 다음과 같은 구성요소를 갖는다.
- IP 패킷 헤더(보통 20바이트)
- TCP 세그먼트 헤더(보통 20바이트)
- TCP 데이터 조각(0 이나 그 이상의 바이트)
TCP 커넥션 유지하기
- 컴퓨터는 항상 여러 개의 TCP 커넥션을 가지고 있다. TCP는 포트 번호를 통해 이 여러 개의 커넥션을 유지한다.
- IP 주소는 해당 컴퓨터에 연결되고 포트번호는 해당 애플리케이션으로 연결된다.
- TCP 주소는 네 가지 값으로 식별한다.
- 발신지 IP주소, 발신지 포트, 수신지 IP 주소, 수신지 포트
TCP 소켓 프로그래밍
- 운영체제는 TCP 커넥션 생성과 관련된 여러 기능을 제공한다. 소켓 API는 HTTP 프로그래머에게 TCP, IP 세부사항을 숨긴다.
- socket(파라미터): 연결되지 않은 익명의 소켓 생성
- bind(소켓, 로컬 주소): 소켓에 로컬 포트번호와 인터페이스 할당
- connect(소켓, 원격 주소): 로컬 소켓과 원격 주소 사이에 TCP 커넥션 할당
- listen(소켓): 커넥션을 받아들이기 위해 로컬 소켓에 허용함을 표시
- acceps(소켓): 누군가 로컬 포트에 커넥션을 맺기를 기다림
- read(소켓, 버퍼, n): 소켓으로부터 버퍼에 n바이트 읽기 시도
- write(소켓, 버퍼, n): 소켓으로부터 버퍼에 n바이트 쓰기 시도
- close(소켓): TCP 커넥션을 완전히 끊음
- shutdown(소켓): TCP 커넥션의 입출력만 닫음
- getsocket(소켓): 내부 소켓 설정 옵션값을 읽음
- setsocket(소켓): 내부 소켓 설정 옵션값을 변경
- 클라이언트와 서버 간 HTTP 트랜잭션을 수행하기 위한 소켓 API의 사용 방법은 아래와 같다. 서버는 파랑, 클라이언트는 초록으로 표기한다. 소켓 API는 마지막에 표기한다.
- 새 소켓을 만든다. socket
- 80포트로 소켓을 묶는다.
- 소켓 커넥션을 허용한다. listen
- 커넥션을 기다린다. accept
- IP 주소와 포트를 얻는다.
- 새로운 소켓을 생성한다. socket
- 서버의 IP:포트로 연결한다. connect
- 애플리케이션 커넥션 통지
- 요청을 읽기 시작한다. read
- 성공적으로 연결
- HTTP 요청을 보낸다. write
- HTTP 응답을 기다린다. read
- HTTP 요청을 처리한다.
- HTTP 응답을 보낸다. write
- HTTP 응답을 처리한다.
- 커넥션을 닫는다. close
- 커넥션을 닫는다. close
TCP 성능
HTTP 트랜잭션 지연
- HTTP 트랜잭션 지연 원인은 아래와 같다.
- 웹 서버의 IP 주소, 포트 번호를 알아내는 데에 걸리는 시간
- TCP 커넥션 설정 시간
- 요청 메시지를 전달하고 처리되는 데 걸리는 시간
- 응답 메시지를 보내는 데 걸리는 시간
TCP 커넥션 헨드셰이크 지연
- 핸드셰이크 순서는 아래와 같다.
- 클라이언트가 새로운 TCP 커넥션을 생성하기 위해 TCP 패킷을 서버에게 보낸다. SYN 플래그를 가진다.
- 서버가 몇 가지 커넥션 매개변수를 산출하고 커넥션 요청을 받아들였다는 의미로 SYN, ACK 플래그를 포함한 TCP 패킷을 클라이언트에게 보낸다.
- 클라이언트가 커넥션이 잘 맺어졌음을 알리기 위해 서버에게 다시 확인응답 신호를 보낸다. 이때 데이터를 함께 보낼 수 있다.
- 크기가 작은 HTTP 트랜잭션에서는 SYN/SYN+ACK 핸드셰이크에 50% 이상의 시간을 소요한다.
확인응답 지연
- 각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다. 각 세그먼트의 수신자는 세그먼트를 온전히 받으면 작은 확인응답 패킷을 송신자에게 반환한다.
- 확인응답은 크기가 작기 때문에 TCP는 같은 방향으로 송출되는 데이터 패킷에 확인응답을 편승(piggyback)시킨다.
- 편승을 늘리기 위해 많은 TCP 스택은 확인응답 지연 알고리즘을 구현한다.
- 확인응답 지연은 송출할 확인응답을 특정 시간 동안 버퍼에 저장해 두고, 확인응답을 편승시키기 위한 송출 데이터(같은 방향) 패킷을 찾는다. 일정 시간 안에 해당 패킷을 찾지 못하면 별도 패킷을 만들어 전송시킨다.
- 요청-응답 두 가지 형식으로만 이루어지는 HTTP 동작은 편승 기회를 감소시킨다. 따라서 확인응답 지연 알고리즘에 의한 지연이 자주 발생한다.
TCP 느린 시작(slow start)
- TCP 데이터 전송 속도는 TCP 커넥션이 만들어진 지 얼마나 지났는지에 따라 달라질 수 있다.
- TCP 느린 시작: TCP 커넥션은 시간이 지나면서 자체적으로 튜닝되어 처음에는 커넥션 최대 속도를 제한하다가 데이터가 성공적으로 전송됨에 따라 속도 제한을 높여나간다.
- 인터넷의 급작스러운 부하와 혼잡을 방지하는 데 사용한다.
- TCP가 한 번에 전송할 수 있는 패킷 수를 제한한다. 이를 혼잡 윈도를 연다고 표현한다.
- 혼잡 제어 기능 때문에 새 커넥션은 튜닝된 커넥션보다 느리다.
네이글(Nagle) 알고리즘과 TCP_NODELAY
- TCP 세그먼트는 40바이트 상당의 플래그, 헤더를 포함해 전송하기 때문에 작은 크기의 데이터를 포함한 많은 수의 패킷을 전송한다면 네트워크 성능이 떨어진다.
- 네이글 알고리즘은 네트워크 효율을 위해 패킷 전송 이전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합친다.
- 세그먼트가 최대 크기가 되지 않으면 전송을 하지 않는다.
- 네이글 알고리즘과 확인응답 지연을 함께 사용하면 효율이 많이 떨어진다.
- HTTP 스택에 TCP_NODELAY 파라미터를 설정해 네이글 알고리즘을 비활성화하기도 한다. 이때에는 작은 크기의 패킷이 너무 많이 생기지 않도록 해야 한다.
TIME_WAIT 누적과 포트 고갈
- TCP 커넥션 종단에서 커넥션을 끊으면 종단에서는 커넥션의 IP 주소와 포트 번호를 메모리의 작은 제어 영역(control block)에 기록해 놓는다. 이는 동일한 주소, 포트 번호의 새 TCP 커넥션이 일정 시간 동안 생성되지 않게 하기 위해서이다.
- 이전 커넥션과 관련된 패킷이 그 커넥션과 같은 주소, 포트 번호를 가지는 새로운 커넥션에 삽입되는 문제(패킷이 중복되고 TCP 데이터가 충돌하게 된다)를 방지한다.
- 동일한 패킷이 2분 이내에 생성되지 않도록 막는다.
- 일반적으로 커넥션 종료 지연이 문제되지는 않지만 성능 테스트 시 부하를 발생시킬 컴퓨터 수는 적기 때문에 가능한 연결 조합(발신지 IP, 포트, 수신지 IP, 포트)이 제한된다. 이 때문에 순간순간 포트 재활용이 불가능해진다.
HTTP 커넥션 관리
흔히 잘못 이해하는 Connection 헤더
- HTTP는 클라이언트-서버 사이에 프락시 서버, 캐시 서버 등의 중개 서버를 허용한다. 이때 HTTP 메시지는 중개 서버들을 전부 거치면서 전달된다.
- HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분해 가진다. 이 값들은 다른 커넥션에 전달되지 않는다.
- Connections 헤더에는 다음 세 가지 종류의 토큰이 전달될 수 있다.
- HTTP 헤더 필드명: 현재 커넥션에만 해당되는 헤더를 나열한다. 현재 커넥션만을 위한 정보이므로 다음 커넥션에 전달하면 안 된다.
- 임시적인 토큰 값: 커넥션에 대한 비표준 옵션이다.
- close 값: 현재 커넥션은 작업 완료(메시지 전송 완료) 후 끊어져야 한다.
순차적인 트랜잭션 처리에 의한 지연
- 각 트랜잭션을 순차적으로 처리하면 지연이 발생해 성능이 좋지 않다.
- HTTP 커넥션 성능 향상을 위한 방법에는 아래가 있다.
- 병렬 커넥션
- 지속 커넥션
- 파이프라인 커넥션
- 다중 커넥션
병렬 커넥션Parallel
- 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다.
- 클라이언트의 네트워크 대역폭이 좁을 때는 성능상 장점이 거의 없어지기 때문에 병렬 커넥션이 항상 빠른 것은 아니다.
- 메모리를 더 많이 소모하고 자체적 성능 문제를 발생시킬 수 있다.
- 병렬 커넥션에는 아래와 같은 단점이 있다.
- 각 트랜잭션마다 새로운 커넥션을 끊고 맺는다. 이 과정에서 시간과 대역폭이 소요되고 느린 시작 때문에 성능이 떨어진다.
- 실제 연결 가능한 병렬 커넥션 수에는 제한이 있다.
- 브라우저는 대체로 6~8개의 병렬 커넥션만을 허용한다.
지속 커넥션Persistent
- 웹 클라이언트는 보통 같은 사이트에 여러 개의 커넥션을 맺는다. 이를 사이트 지역성(site locality)이라고 한다.
- HTTP/1.1을 지원하는 기기는 처리 완료 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용할 수 있다. 이를 지속 커넥션이라고 한다. 지속 커넥션은 클라이언트나 서버가 커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지한다.
- 병렬 커넥션에 비해 지속 커넥션은 아래와 같은 장점이 있다.
- 커넥션 작업과 지연을 줄여준다.
- 튜닝된 커넥션을 유지해 느린 시작으로 인한 지연이 적다.
- 커넥션의 수를 줄여준다.
- 하지만 계속 연결된 상태의 커넥션이 쌓이면 로컬 리소스, 원격 클라이언트와 서버의 리소스에 불필요한 소모를 발생시킨다.
- 지속 커넥션과 병렬 커넥션을 함께 사용할 때 가장 효과적이다.
- HTTP/1.0+의 Keep-Alive 커넥션
- 하나의 지속 커넥션으로 요청을 처리한다.
- HTTP/1.1에서는 명세에서 제외되었지만 아직 널리 사용되기 때문에 HTTP 애플리케이션은 keep-Alive를 처리할 수 있게 개발해야 한다.
- 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection: Keep-Alive 헤더를 포함시킨다. 이 요청을 받은 서버는 그 다음 요청도 해당 커넥션을 통해 받고자 하는 경우에만 응답에 동일한 헤더를 포함시킨다.
- Keep-Alive는 커넥션을 유지하기를 바라는 요청이고 보장하지는 않는다.
- 멍청한 프락시(dumb proxy)는 오래되거나 단순해 Connection 헤더를 처리하지 못하고(현재 홉에서 제거하지 못하고) 다음 홉에 요청을 그대로 전달할 수 있다. 이로 인해 Keep-Alive가 잘못 전달되면 브라우저나 서버가 타임아웃이 나서 커넥션이 끊길 때까지 기다릴 수 있다.
- 위와 같은 잘못된 통신을 피하기 위해 프락시는 Connection 헤더와 Connection 헤더에 명시된 헤더들을 절대 전달하면 안 된다.
- Proxy-Connection
- 넷스케이프는 멍청한 프락시 문제를 해결하기 위해 Connection 헤더 대신 비표준인 Proxy-Connection 확장 헤더를 프락시에게 전달한다.
- 프락시가 Proxy-Connection을 무조건 전달해도 웹 서버는 그것을 무시하기 때문에 문제가 발생하지 않는다.
- 영리한 프락시는 의미 없는 Proxy-Connection을 Connection으로 바꿔 원하던 효과를 얻을 수 있다.
- 넷스케이프는 멍청한 프락시 문제를 해결하기 위해 Connection 헤더 대신 비표준인 Proxy-Connection 확장 헤더를 프락시에게 전달한다.
- HTTP/1.1의 지속 커넥션
- HTTP/1.1에서는 Keep-Alive를 지원하지 않는 대신 설계가 더 개선된 지속 커넥션을 지원한다.
- 별도 설정이 없는 경우 모든 커넥션을 지속 커넥션으로 취급한다.
- 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close 헤더를 명시해야 한다.
파이프라인 커넥션Pipelined
- HTTP/1.1은 지속 커넥션을 통해 요청을 파이프라이닝할 수 있다.
- 여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓인다. 첫 요청이 서버로 전달되면 이후 요청이 전달될 수 있다.
- 파이프라인의 제약 사항은 아래와 같다.
- 커넥션이 지속 커넥션인지 확인하기 전에는 파이프라인을 이어서는 안 된다.
- 응답은 요청 순서와 같게 와야 한다.
- 클라이언트는 커넥션이 언제 끊어지더라도 완료되지 않는 요청이 파이프라인에 있으면 언제든 다시 요청을 보낼 준비가 되어 있어야 한다.
- 클라이언트는 비멱등 요청은 파이프라인을 통해 보내면 안 된다.
- 우아한 커넥션 끊기
- 애플리케이션은 TCP 입력 채널과 출력 채널을 끊을 수 있다.
- close()를 호출하면 입출력 채널을 둘 다 끊는다. (전체 끊기)
- shutdown()을 호출하면 채널을 하나만 개별적으로 끊는다. (절반 끊기)
- 단순한 HTTP 애플리케이션은 전체 끊기만을 사용할 수 있다.
- 애플리케이션이 각기 다른 HTTP 클라이언트, 서버, 프락시와 통신하거나 파이프라인 지속 커넥션을 사용한다면 예상치 못한 쓰기 에러를 예방하기 위해 절반 끊기를 사용해야 한다.
- 보통 커넥션의 출력 채널을 끊는 것이 안전하다. 반대편의 기기는 데이터 읽기가 끝남과 동시에 커넥션이 끝났다는 것을 알 수 있다.
- 대부분 운영체제는 연결이 끊긴 입력 채널에 데이터가 도착하면 connection reset by peer 에러가 발생하고, 읽히지 않은 데이터를 모두 삭제한다. 이는 파이프라인 커넥션에서 더 취약하다.
- 우아한 커넥션 끊기란 자신의 출력을 먼저 끊고 다른 쪽에 있는 기기의 출력 채널이 끊기는 것을 기다리는 것이다. 이를 사용하면 커넥션은 리셋 위험 없이 온전히 종료될 수 있다.
- 애플리케이션은 TCP 입력 채널과 출력 채널을 끊을 수 있다.
다중 커넥션Multiplexed
- 요청과 응답들에 대한 중재
'공부 > 책' 카테고리의 다른 글
| [HTTP 완벽 가이드] 5부 학습 (0) | 2025.05.29 |
|---|---|
| [HTTP 완벽 가이드] 3부 학습 (0) | 2025.05.23 |
| [HTTP 완벽 가이드] 2부 학습 (0) | 2025.05.23 |
| [단위 테스트] 학습 (0) | 2025.01.24 |