본 게시글은 "HTTP 완벽 가이드"를 학습하며, 내용 요약 또는 몰랐던 부분을 정리하는 글 입니다.
전 세계 모든 HTTP 통신은, 지구상의 컴퓨터와 네트워크 장비에서 널리 쓰이고 있는 패킷 교환 네트워크 포로토콜들의 계층화된 집합인 TCP/IP를 통해 이루어진다.
URL을 입력받은 브라우저가 수행하는 7단계 과정
URL : http://www.joes-hardware.com:80/power-tools.html
HTTP 커넥션은 몇몇 사용 규칙을 제외하고는 TCP 커넥션에 불과하다. TCP 커넥션은 인터넷을 안정적으로 연결해준다.
HTTP는 프로토콜 스택(레이어)에서 최상위 계층이다. HTTP에 보안 기능을 더한 HTTPS는 TLS 혹은 SSL이라 불리기도 하며 HTTP와 TCP 사이에 있는 암호화 계층이다.
HTTP가 메시지를 전송할 경우, TCP 커넥션을 통해서 메시지 데이터의 내용을 순서대로 보낸다. TCP는 세그먼트라는 단위로 데이터스트림을 잘게 나누고, 세그먼트를 IP 패킷이라 불리는 봉투에 담아서 인터넷을 통해 데이터를 전달한다.
이 모든 것은 TCP/IP 소프트웨어에 의해 처리되며, 그 과정은 HTTP 프로그래머에게 보이지 않는다.
TCP는 IP 패킷(혹은 IP 데이터그램)이라고 불리는 작은 조각을 통해 데이터를 전송한다.
IP 패킷은 다음을 포함한다.
컴퓨터는 항상 TCP 커넥션을 여러 개 가지고 있다. TCP는 포트 번호를 통해서 이런 여러 개의 커넥션을 유지한다.
회사의 대표 전화번호는 안내 데스크로 연결되고 내선전화는 해당 직원으로 연결되듯이 IP 주소는 해당 컴퓨터에 연결되고 포트 번호는 해당 애플리케이션으로 연결된다.
TCP 커넥션은 네 가지 값으로 식별한다.
아래 네 가지 값이 모두 똑같은 중복된 커넥션은 존재할 수 없다.
운영체제는 TCP 커넥션의 생성과 관련된 여러 기능(API)을 제공한다. 이 소켓 API는 HTTP 프로그래머에게 TCP와 IP의 세부사항들을 숨긴다. 소켓 TCP는 유닉스 운영체제용으로 먼저 개발되었지만, 지금은 소켓 API의 다양한 구현체들 덕분에 대부분의 운영체제와 프로그램 언어에서 이를 사용할 수 있게 되었다.
HTTP는 TCP 바로 위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 TCP 성능에 영향을 받는다.
TCP 성능의 특성을 이해함으로써, HTTP 커넥션 최적화 요소들을 더 잘 알게 되고 더 좋은 성능의 HTTP 애플리케이션을 설계하고 구현할 수 있게 될 것이다.
트랜잭션을 처리하는 시간은 TCP 커넥션을 설정하고, 요청을 전송하고, 응답 메시지를 보내는 것에 비하면 상당히 짧다.
즉, HTTP 트랜잭션 지연은 TCP 네트워크 지연 때문에 발생한다.
이 4가지 원인으로 지연이 발생한다. 하지만 현대는 인프라의 발전으로 크게 발생하지 않는다.
크기가 작은 http요청의 경우 50%이상의 시간을 커넥션 연결을 구성하는데 쓰이게되서 지연이라고 볼 수 있다.
인터넷이 패킷전송을 완벽하게는 보장하지 않기 때문에, 데이터 전송 성공을 위해서 자체적인 확인 체계를 가진다.
TCP 세그먼트들은 순번/데이터무결성체크섬 가지는데, 수신자가 그것을 검증하고 확인응답 패킷을 보내줘야한다.
확인 응답은 그 크기가 작아서, 효율적으로 보내려고 같은 방향으로 나가는 패킷에 편승(piggyback)한다.
하지만 이때 편승할 패킷을 찾지못한다면 그것을 찾을때까지 지연이 발생한다.
tcp 데이터 전송속도는 tcp 커넥션이 만들어진지 얼마나 지났는지에 따라서 달라질 수 있다.
tcp 커넥션은 시간이 지나면서 자체적으로 튜닝되어가지고, 처음에는 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도제한을 높여간다. (이게 slow start)
이걸로 인터넷의 갑작스러운 부하/혼잡을 막을 수 있다.
http 트랜잭션에서 전송할게 많으면 모든패킷을 한번에 쏠수는 없고 1개의 패킷을 보내고 확인응답을 기다려야한다.
확인응답받으면 이제 2개의 패킷을보내고 확인응답 받는다. 다음에는 4개, 이러한 방식으로 이렇게 보낸다.
tcp는 헤더의 크기가 작진않기때문에, 작은 데이터를 여러개의 tcp로 보내면 네트워크의 성능은 떨어질 것이다.
nagle알고리즘은 패킷을 전송하기 전에 많은 양의 tcp데이터를 1개의 덩어리로 합친다.
이때 전송하기 충분할 만큼의 패킷이 쌓였을때 버퍼에 저장되어있던 데이터가 전송되는 로직이 있어서, 충분할 만큼의 패킷이 쌓일때까지 지연이 발생할 수 있다.
TCP_NODELAY 플래그로 nagle 알고리즘을 비활성화할 수도 있다.
tcp 커넥션을 끊으며 엔드포인트에서는 ip주소와 포트번호를 작게 기록해놓는데, 이렇게하면 보통 2분정도 내에 같은 주소와 포트번호를 가지는 커넥션이 또 생성되는것을 막아준다. (값을 다르게 해줌)
그래서 커넥션이 닫힌후에 중복되는 패킷이 생기는 경우는 거의 없어진다.
만약 이전 커넥션의 패킷이 그 커넥션과 같은 연결값으로 생성된 커넥션에 삽입되면 패킷은 중복되고 TCP데이터가 충돌난다.
일반적으로 그럴일은 없는데, 테스트하는 상황에서 이런 문제가 생긴다.
포트개수도 제한이 있기 때문에도 더이상 유일한 커넥션을 생성할 수 없어 지연이 발생하기도 한다.
커넥션을 생성하고 최적화하는 HTTP 기술에 대해서 알아보자
Connection 헤더에는 다음 3가지 헤더때문에 헷갈릴 수 있다.
커넥션 관리가 제대로 안되면 tcp 성능이 매우 안좋아진다.
3개의 이미지가 있는 웹페이지가 있으면 4개의 http 트랜잭션을 만들어야함.
html받기용 1개 + 첨부된 이미지를 받기위한 3개.
이때 순차적으로 트랜잭션을 처리하게 되면, 이미지가 동시에 그려지지않아서 심리적으로 느리게 느껴지고
물론 물리적인 지연도 있고 화면에 배치하려면 크기를 알아야해서 객체의 크기를 파악하는 동안 텅빈화면이 보여진다.
이것을 해결하기 위해서 커넥션은 4가지가 존재한다.
브라우저는 순차적으로 객체를 내려받는 식으로 웹페이지를 보여줄 수 있다.
=> 많이 느리다
http는 여러개의 커넥션을 맺음으로써 여러개의 http 트랜잭션을 병렬로 처리할 수 있게한다.
=> 그래서 브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은수(대부분 4개정도)의 병렬커넥션만을 허용함.
그래서 병렬커넥션은 순차적인 것보다 빠르지 않을 수는 있지만, 여러개의 객체가 거의 동시에 로드되기 때문에
사용자가 빠르게 로드된다고 느낄 수 있다.
=> 처리가 완료된 후에도 계속 연결된 상태로 있는 tcp 커넥션을 지속커넥션이라고 부른다.
즉, 비지속 커넥션은 각 처리가 끝날때마다 끊지만 지속 커넥션은 처리가 끝나도 커넥션을 유지한다.
병렬커넥션의 단점
지속커넥션의 장점
지속커넥션의 단점
그래서 병렬커넥션 + 지속커넥션 === Good
오늘날에는 적은 수의 병렬 커넥션만을 맺고 그것을 지속하는 형태가 많이 보이게 되었다.
http 1.0+에는 'keep alive' 커넥션이 있고, http1.1에는 지속커넥션이 있다.
Connection: Keep-Alive
Keep-Alive: max=5, timeout=120
위 헤더는 서버가 약 5개의 추가 트랜잭션이 처리될동안 커넥션을 유지하거나
2분동안 커넥션을 유지하라는 내용의 Keep-Alive 응답헤더이다.
문제: Connection 헤더의 무조건 전달
프록시는 Connection 헤더를 이해하지 못해서 해당 헤더들을 삭제하지 않고 요청 그대로를 다음 프록시에 전달
(공부 좀 더 필요)
넷스케이프는 멍청한 프록시문제를 해결하기 위해서 일반적으로 전달하는 Connection 헤더 대신에, 비표준인 Proxy-Connection [확장헤더]를 프록시에게 전달.
이러면 프록시가 Proxy-Connection 전달해도 비표준이기 때문에 서버는 무시함.
이제 프록시는 정말 "전달만" 할 수 있음.
즉, Proxy-Connection는 멍청한 프록시: 단일 무조건 전달 문제를 해결할 수 있음.
=> 하지만 동시에 keep-alive 도 맺어지지 않음 (Connection헤더 제거했으니까)
해결법: 영리한 프록시를 둔다.
영리한 프록시는 Proxy-Connection를 받고 Connection: Keep-Alive 헤더로 바꿔서 서버에 전달한다.
keep alive 지속커넥션이 가능해진다!
근데 이것도, 멍청한 프록시 양옆에 영리한 프록시 있으면 (프록시가 많은 구조) 문제가 생긴다.
변환된 Connection: Keep-Alive가 다시 멍청한 프록시에 갈 수 있기 때문에..
게다가 문제를 발생시키는 프록시들은 네트워크상에서 보이지 않기 때문에 Proxy-Connection 헤더를 보내지 못한다.
http1.1에서는 keep-alive 커넥션은 지원하지 않고, 더 나은 설계의 지속커넥션을 지원한다.
http1.1의 지속커넥션은 기본적으로 활성화되어있다. (별도 설정없으면 모든 커넥션을 지속커넥션으로 취급하는 것이다.)
http1.1은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close 헤더를 명시해야한다.
http1.1은 지속커넥션을 통해서 요청을 파이프라이닝 가능
커넥션 관리에 대해서는 명확한 기준이 없음.
즉, 멱등하지 않은 요청은 재요청하면 안된다. 대부분 브라우저는 캐시된 Post 요청 페이지를 다시로드하려할때, 요청을 다시 보내기를 원하는지 묻는 대화상자를 보여준다
전체끊기
어플리케이션은 tcp입력채널과 출력채널 중 1개만 끊거나 둘다 끊을 수 있다.
절반끊기
입력채널이나 출력채널 중 하나를 개별적으로 끊을 수 있다.
TCP끊기
단순한 http는 전체끊기만 상요할 수 있는데 어플리케이션이 각기다른 http클라이언트, 서버, 프락시와 통신할때 지속커넥션을 계속할때, 예상치 못한 쓰기에러를 방지하기 위해 절반끊기가 필요할 수도 있다.
우아하게 커넥션 끊기
일반적으로 이상적인 커넥션끊기는 자신의 출력채널을 먼저 끊고 다른 쪽에 있는 기기의 출력 채널이 끊기기를 기다리는 것이다.
양쪽에서 서로 더이상 전송하지 않겠다라고 알려주면 위험없이 종료된다.
안타깝게도 항상 이렇지만은 않기 때문에, 출력채널에 절반끊기를 쓰고 입력채널에 대해서 상태검사를 주기적으로 해야한다.