HTTP/2 RFC를 응용한 HTTP/2 Checker의 구현

25457
2020-01-15

몇 년 전, 프로젝트에 HTTP/2를 도입할 무렵, KeyCDN에서 만든 HTTP/2-Test 서비스를 보고,

이것을 어떻게 만들었을까 궁금했습니다. 아마도 직접 도메인 주소로 테스트 에이전트가 접속해서

HTTP/2 지원 여부에 대한 요청을 하고, 뭔가 받은 응답 값을 사용했겠지 예상이 되더군요.

원리의 파악..

유사하게 작동하는 HTTP/2 Checker를 구현하기 위해 HTTP/2 RFC(Request for Comments) 내용을 찾아보았습니다.

모든 통신 프로토콜이 그렇듯 HTTP/2의 RFC-7540에서의 정의를 다시 한번 빌리자면,

HTTP/2를 사용하기 위해서는 클라이언트와 서버 양측 모두 HTTP/2를 지원해야 합니다.

대부분의 최근 브라우저 버전들은 TLS 기반의 HTTP/2를 지원하며, 일부는 HTTP/3까지도 지원합니다.

<https://caniuse.com/#search=http2>

HTTP/2 RFC-7540에 따르면, 클라이언트(웹 브라우저)는 웹 서버와 HTTP/2 통신을 하기 위해

서버에게 HTTP/2를 지원하는지를 질의해야 하고, 그에 대한 응답 여부에 따라 HTTP/2를 사용할지,

HTTP1.1(혹은 이하 버전)으로 통신을 해야 할지 결정합니다.

아마도 이 과정에서 도메인의 HTTP/2 지원 여부 또한 파악이 가능해지는 단계입니다.

그렇다면 핵심 부분은 바로 클라이언트가 어떻게 서버에게 그런 질의를 하는지에 대한 세분화된 절차인데,

관련 내용을 RFC에서 찾아보니, HTTP 통신과 HTTPS 통신 각각 다른 방법이 정의되어 있었습니다.

HTTP(non-secure) 통신에서의 HTTP/2 연결

이 부분은 3.2. Starting HTTP/2 for "http" URIs 섹션에 정의되어 있습니다. 클라이언트가 최초 서버 접속 시,

Upgrade, HTTP-2 Settings 헤더를 첫 요청 헤더에 실어 보냅니다.

HTTP2-Settings 커스텀 헤더는 각종 연결에 필요한 설정 정보를 base64url 인코딩한 값입니다.

1
2
3
4
5
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

만약 서버가 HTTP/2를 지원하지 않는다면, 요청에 대한 응답을 HTTP1.1 버전의 200 OK로 응답합니다.

1
2
3
4
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
...

서버가 HTTP/2를 지원한다면, 101 Switching Protocols 응답을 하며, 프로토콜이 업데이트 가능함을 Connection 헤더로 알려주고, Upgrade 헤더에 HTTP/2 지시자인 h2c로 응답합니다. 그 후, 클라이언트는 이후 연결부터 HTTP/2를 사용합니다.

참고로 101 응답은 HTTP에서 WebSocket으로 전환할 때도 사용되는 응답 코드입니다.

1
2
3
4
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection]

빠른 이해를 위해 지금까지의 내용을 다이어그램으로 표현해보았습니다.

<HTTP(non-secure) 환경에서의 HTTP/2 연결 과정>

그렇지만 RFC와는 달리, 실제 현존하는 대부분의 브라우저들이나 WebView 컴포넌트 등은 HTTPS에서만 HTTP/2를 지원합니다. 따라서 웹 서버에서 HTTP/2를 지원하려면 먼저 Let's Encrypt와 같은  인증서를 준비하고, 서버에서 HTTPS를 지원할 수 있도록 해야 합니다.

HTTPS(secure) 통신에서의 HTTP/2 연결

이 부분은 3.3. Starting HTTP/2 for "https" URIs 섹션에 정의되어 있습니다.

클라이언트는 TLS 기반의 https URI로 접속하면서 ALPN(Application-Layer Protocol Negotiation)을 사용합니다.

ALPN 이란 단어 뜻 그대로 Layer 7에서 클라이언트와 서버가 상호 협의(negotiation) 하에 사용할 프로토콜을 결정하는 방식입니다.

클라이언트가 후보 프로토콜 집합을 ALPN extension 값으로 전달하면,

서버는 그중에서 자신이 지원하는 버전 하나를 고릅니다. HTTPS를 사용하는 HTTP/2의 ALPN extension 값은 h2입니다.

<HTTPS(secure) 환경에서의 HTTP/2 연결 과정>

이제 어느 정도 HTTP/2의 연결 방식에 대한 지식은 충분한 것 같습니다.

RFC의 내용을 좀 더 정확하게 이해하는 방법 중 하나는 프로토타입을 직접 코딩해보는 것이죠.

웹 서버 주소(도메인)을 입력하면, HTTP/2 지원 여부를 HTTP와 HTTPS 두 가지 방법으로 테스트하는 함수를

위에서 설명한 RFC 내용대로 파이썬으로 구현하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sys
import requests
import socket
import ssl
def checkh2():
	#Define headers list for HTTP test
	headers = {'Accept': '*/*', 'user-agent': 'h2-check/1.0.1', 'Connection': 'Upgrade, HTTP2-Settings', 'Upgrade': 'h2c', 'HTTP2-Settings': '<base64url encoding of HTTP/2 SETTINGS payload>'}
	while True:
		domain = raw_input("\nInput domain: ")
		if domain == 'bye':
			break
		try:
			#Send HTTP Request with Upgrade, Connection headers
			r = requests.get('http://' + domain, headers=headers, allow_redirects=True)
			#If status code is 101, the domain supports h2c for HTTP/2
			if r.status_code == 101:
				print 'HTTP/2 is supported using h2c indicator.'
			else:
				print 'HTTP/2 is not supported using HTTP.'
			#Initiate an SSL contenxt
			ctx = ssl.create_default_context()
			#Add ALPN candidates to the SSL context
			ctx.set_alpn_protocols(['h2', 'spdy/3', 'http/1.1'])
			#Open a socket using the SSL context and connect to the domain using 443 ports
			conn = ctx.wrap_socket(socket.socket(), server_hostname=domain)
			conn.connect((domain, 443))
			#If server choose h2 extension, it supports ALPN supported HTTP/2
			if conn.selected_alpn_protocol() == 'h2':
				print 'HTTP/2 is supported using ALPN extension.'
			#If no h2 extensoin, Inform which protocol server choose
			else:
				print 'HTTP/2 is not supported but ' + str(conn.selected_alpn_protocol()) + ' is supported.'
			r.close()
		except:
			print 'Connection errror. Please check the domain name.'
if __name__ == '__main__':
    checkh2()

코드를 실행하여, 몇 개의 도메인을 대상으로 테스트해본 결과입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$python h2checker.py
Input domain: nghttp2.org
HTTP/2 is supported using h2c indicator.
HTTP/2 is supported using ALPN extension.
..
Input domain: www.google.com
HTTP/2 is not supported using HTTP.
HTTP/2 is supported using ALPN extension.
..
Input domain: facebook.com
HTTP/2 is not supported using HTTP.
HTTP/2 is supported using ALPN extension.
..
Input domain: www.naver.com
HTTP/2 is not supported using HTTP.
HTTP/2 is supported using ALPN extension.
..
Input domain: www.daum.net
HTTP/2 is not supported using HTTP.
HTTP/2 is not supported but http/1.1 is supported.
..
Input domain: bye

국내의 양대 포털 사이트 중 하나만 HTTP/2를 지원하는 것은 좀 의아하긴 하지만,

그 서비스가 대륙별 혹은 국가별로 CDN(Contents Delivery Network)을 사용한다면,

이 결과는 테스트 위치마다 다를 수도 있습니다.

맺으며..

만약, RFC 내용을 스터디하는 것보다, HTTP/2 지원 여부만 조사하는 것이 중요하다면

아래 openssl 명령문 하나로도 웹 서버가 ALPN h2 extension과 HTTP/2를 지원하고 있는지 바로 알 수 있습니다.

1
2
3
4
$openssl s_client -alpn h2 -connect google.com:443 -status | grep ALPN
...
ALPN protocol: h2
...

그렇지만 배운 내용 그대로 코드로 구현해보며, 프로토콜의 RFC 내용을 좀 더 빠르게 이해할 수 있었기 때문에 이 글을 공유합니다.

https://http3check.net/ 서비스와 유사하게 구현한 HTTP/3 Checker에 대한 글도 준비가 되면, 공유할 예정입니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.