HTTP 지속 연결(Persistent Connection)은 여러 HTTP 요청 및 응답을 처리할 때 단일 연결을 사용하므로 통신의 효율성을 향상시키는 메커니즘이다. 이렇게 설명하면 이해하기 어려운데, 결국 header의 Connection 항목의 value가 'keep-alive'인 것을 뜻한다.
그런데 http 1.1은 Connection이 기본적으로 keep-alive로 설정되어 있다. 그런데 대체 keep-alive 상태라는 것은 무엇을 의미할까? 반대로, close 상태라면 어떤 일이 일어날까?
본격적으로 들어가기 전에 우선 HTTP와 TCP에 대해 알아보자. HTTP는 웹 브라우저와 웹 서버 간 HTML, JS 등과 같은 데이터를 주고 받는 데 사용된다. TCP는 이런 데이터들을 패킷으로 나누어 전송하고, 다시 재조립하는 역할을 한다. TCP의 데이터 패킷의 예시는 다음과 같다.
0000 02 00 00 00 45 00 00 40 00 00 40 00 40 06 00 00 ....E..@..@.@...
0010 7f 00 00 01 7f 00 00 01 d0 88 0b b8 57 4a 03 60 ............WJ.`
0020 00 00 00 00 b0 02 ff ff fe 34 00 00 02 04 3f d8 .........4....?.
0030 01 03 03 06 01 01 08 0a bb f9 72 40 00 00 00 00 ..........r@....
0040 04 02 00 00 ....
위와 같은 데이터 패킷을 해독해보면 다음과 같은 데이터들이 들어있다.
Frame 1: 68 bytes on wire (544 bits), 68 bytes captured (544 bits) on interface lo0, id 0
Section number: 1
Interface id: 0 (lo0)
Encapsulation type: NULL/Loopback (15)
Arrival Time: Aug 21, 2023 01:47:55.602152000 KST
[Time shift for this packet: 0.000000000 seconds]
Epoch Time: 1692550075.602152000 seconds
[Time delta from previous captured frame: 0.000000000 seconds]
[Time delta from previous displayed frame: 0.000000000 seconds]
[Time since reference or first frame: 0.000000000 seconds]
Frame Number: 1
Frame Length: 68 bytes (544 bits)
Capture Length: 68 bytes (544 bits)
[Frame is marked: False]
[Frame is ignored: False]
[Protocols in frame: null:ip:tcp]
[Coloring Rule Name: TCP SYN/FIN]
[Coloring Rule String: tcp.flags & 0x02 || tcp.flags.fin == 1]
Null/Loopback
Family: IP (2)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
0100 .... = Version: 4
.... 0101 = Header Length: 20 bytes (5)
Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
Total Length: 64
Identification: 0x0000 (0)
010. .... = Flags: 0x2, Don't fragment
0... .... = Reserved bit: Not set
.1.. .... = Don't fragment: Set
..0. .... = More fragments: Not set
...0 0000 0000 0000 = Fragment Offset: 0
Time to Live: 64
Protocol: TCP (6)
Header Checksum: 0x0000 [validation disabled]
[Header checksum status: Unverified]
Source Address: 127.0.0.1
Destination Address: 127.0.0.1
Transmission Control Protocol, Src Port: 53384, Dst Port: 3000, Seq: 0, Len: 0
Source Port: 53384
Destination Port: 3000
[Stream index: 0]
[Conversation completeness: Complete, WITH_DATA (31)]
[TCP Segment Len: 0]
Sequence Number: 0 (relative sequence number)
Sequence Number (raw): 1464468320
[Next Sequence Number: 1 (relative sequence number)]
Acknowledgment Number: 0
Acknowledgment number (raw): 0
1011 .... = Header Length: 44 bytes (11)
Flags: 0x002 (SYN)
000. .... .... = Reserved: Not set
...0 .... .... = Accurate ECN: Not set
.... 0... .... = Congestion Window Reduced: Not set
.... .0.. .... = ECN-Echo: Not set
.... ..0. .... = Urgent: Not set
.... ...0 .... = Acknowledgment: Not set
.... .... 0... = Push: Not set
.... .... .0.. = Reset: Not set
.... .... ..1. = Syn: Set
[Expert Info (Chat/Sequence): Connection establish request (SYN): server port 3000]
.... .... ...0 = Fin: Not set
[TCP Flags: ··········S·]
Window: 65535
[Calculated window size: 65535]
Checksum: 0xfe34 [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
Options: (24 bytes), Maximum segment size, No-Operation (NOP), Window scale, No-Operation (NOP), No-Operation (NOP), Timestamps, SACK permitted, End of Option List (EOL), End of Option List (EOL)
[Timestamps]
이와 같은 데이터 패킷은 여러 개로 구성되어 Three-hand shaking을 거쳐 전송된다.
먼저, 간단한 Flask 애플리케이션을 통해 Connection close와 지속 연결의 차이를 알아보자.
from flask import Flask, Response
app = Flask(__name__)
@app.route('/close')
def close_connection():
response = Response('Connection will be closed after this response')
response.headers['Connection'] = 'close'
return response
@app.route('/keep')
def keep_connection():
return 'Connection will be kept alive after this response'
if __name__ == '__main__':
app.run(port=3000)
이 예제에서는 두 개의 경로가 있다. /close는 연결을 종료하고, /keep은 연결을 유지한다.
그러나 코드만으로는 이 차이를 명확하게 관찰하기 어렵다. 이를 위해서는 네트워크 패킷을 캡처하고 분석해야 한다. Wireshark 같은 패킷 분석 도구를 사용하면 이를 직접 볼 수 있다.
패킷을 참고하기에 앞서 TCP Flag의 종류를 살펴보자.
아래는 close 상태일 때 보여지는 패킷 목록이다. 해당 패킷 목록을 관찰하면 TCP 연결을 종료하고(RST Flag) 다시 새로운 TCP를 연결하는 모습을 확인할 수 있다.
반대로 아래는 keep-alive 상태일 때 보여지는 패킷 목록이다. SYN - SYN-ACK - SCK 를 거쳐 성공적으로 HTTP GET 요청이 완료된 것을 확인할 수 있다.
한편 TCP 의 keep-alive와 HTTP의 keep-alive의 차이는 무엇일까? TCP의 keep-alive는 TCP 연결 자체를 유지하도록 하는 매커니즘이고, HTTP의 keep-alive는 하나의 TCP 연결에서 여러 개의 HTTP 요청 및 응답을 처리하는 매커니즘이다.
HTTP Persistence Connection 는 HTTP header에 'Connection' 항목을 'keep-alive'로 둠으로써 통신을 종료시키지 않고 유지시키는 것을 의미한다. 이를 통해 TCP 연결 요청 수를 줄이고 네트워크를 효율적으로 사용할 수 있다.