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()