Twisted Example Client: Head RequestsΒΆ
This example is a basic HTTP/2 client written for the Twisted asynchronous networking framework.
This client is fairly simple: it makes a hard-coded HEAD request to nghttp2.org/httpbin/ and prints out the response data. Its purpose is to demonstrate how to write a very basic HTTP/2 client implementation.
1# -*- coding: utf-8 -*-
2"""
3head_request.py
4~~~~~~~~~~~~~~~
5
6A short example that demonstrates a client that makes HEAD requests to certain
7websites.
8
9This example is intended as a reproduction of nghttp2 issue 396, for the
10purposes of compatibility testing.
11"""
12from __future__ import print_function
13
14from twisted.internet import reactor
15from twisted.internet.endpoints import connectProtocol, SSL4ClientEndpoint
16from twisted.internet.protocol import Protocol
17from twisted.internet.ssl import optionsForClientTLS
18from hyperframe.frame import SettingsFrame
19from h2.connection import H2Connection
20from h2.events import (
21 ResponseReceived, DataReceived, StreamEnded,
22 StreamReset, SettingsAcknowledged,
23)
24
25
26AUTHORITY = u'nghttp2.org'
27PATH = '/httpbin/'
28SIZE = 4096
29
30
31class H2Protocol(Protocol):
32 def __init__(self):
33 self.conn = H2Connection()
34 self.known_proto = None
35 self.request_made = False
36
37 def connectionMade(self):
38 self.conn.initiate_connection()
39
40 # This reproduces the error in #396, by changing the header table size.
41 self.conn.update_settings({SettingsFrame.HEADER_TABLE_SIZE: SIZE})
42
43 self.transport.write(self.conn.data_to_send())
44
45 def dataReceived(self, data):
46 if not self.known_proto:
47 self.known_proto = self.transport.negotiatedProtocol
48 assert self.known_proto == b'h2'
49
50 events = self.conn.receive_data(data)
51
52 for event in events:
53 if isinstance(event, ResponseReceived):
54 self.handleResponse(event.headers, event.stream_id)
55 elif isinstance(event, DataReceived):
56 self.handleData(event.data, event.stream_id)
57 elif isinstance(event, StreamEnded):
58 self.endStream(event.stream_id)
59 elif isinstance(event, SettingsAcknowledged):
60 self.settingsAcked(event)
61 elif isinstance(event, StreamReset):
62 reactor.stop()
63 raise RuntimeError("Stream reset: %d" % event.error_code)
64 else:
65 print(event)
66
67 data = self.conn.data_to_send()
68 if data:
69 self.transport.write(data)
70
71 def settingsAcked(self, event):
72 # Having received the remote settings change, lets send our request.
73 if not self.request_made:
74 self.sendRequest()
75
76 def handleResponse(self, response_headers, stream_id):
77 for name, value in response_headers:
78 print("%s: %s" % (name.decode('utf-8'), value.decode('utf-8')))
79
80 print("")
81
82 def handleData(self, data, stream_id):
83 print(data, end='')
84
85 def endStream(self, stream_id):
86 self.conn.close_connection()
87 self.transport.write(self.conn.data_to_send())
88 self.transport.loseConnection()
89 reactor.stop()
90
91 def sendRequest(self):
92 request_headers = [
93 (':method', 'HEAD'),
94 (':authority', AUTHORITY),
95 (':scheme', 'https'),
96 (':path', PATH),
97 ]
98 self.conn.send_headers(1, request_headers, end_stream=True)
99 self.request_made = True
100
101options = optionsForClientTLS(
102 hostname=AUTHORITY,
103 acceptableProtocols=[b'h2'],
104)
105
106connectProtocol(
107 SSL4ClientEndpoint(reactor, AUTHORITY, 443, options),
108 H2Protocol()
109)
110reactor.run()