|
import hpack.hpack |
|
from hyperframe.frame import ( |
|
SettingsFrame, HeadersFrame, Frame, DataFrame, GoAwayFrame |
|
) |
|
|
|
from twisted.internet import ssl, protocol, defer, endpoints, reactor, task |
|
|
|
|
|
def decodeFrame(frame_data): |
|
f, length = Frame.parse_frame_header(frame_data[:9]) |
|
f.parse_body(memoryview(frame_data[9:9 + length])) |
|
return f, length+9 |
|
|
|
|
|
def getFrames(data): |
|
while data: |
|
f, consumed = decodeFrame(data) |
|
data = data[consumed:] |
|
yield f |
|
|
|
|
|
def main(reactor): |
|
e = hpack.hpack.Encoder() |
|
d = hpack.hpack.Decoder() |
|
|
|
options = ssl.optionsForClientTLS( |
|
hostname=u'http2bin.org', |
|
extraCertificateOptions={'nextProtocols': [b'h2', b'http/1.1']} |
|
) |
|
|
|
class BasicH2Request(protocol.Protocol): |
|
def connectionMade(self): |
|
self.initial_read = False |
|
self.complete = defer.Deferred() |
|
|
|
# Write the HTTP/2 preamble. |
|
self.transport.write(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n') |
|
f = SettingsFrame(0) |
|
f.settings[SettingsFrame.ENABLE_PUSH] = 0 |
|
self.transport.write(f.serialize()) |
|
|
|
def dataReceived(self, data): |
|
assert self.transport.getNextProtocol() == 'h2' |
|
|
|
for f in getFrames(data): |
|
# If this is a settings frame with no ACK, send back an ACK. |
|
if isinstance(f, SettingsFrame): |
|
if 'ACK' not in f.flags: |
|
print "Received settings frame" |
|
f = SettingsFrame(0) |
|
f.flags.add('ACK') |
|
self.transport.write(f.serialize()) |
|
|
|
# Send the request. |
|
self.sendRequest() |
|
elif isinstance(f, HeadersFrame): |
|
headers = d.decode(f.data) |
|
print "Received headers: %s" % headers |
|
elif isinstance(f, DataFrame): |
|
print "Received data: %s" % f.data |
|
elif isinstance(f, GoAwayFrame): |
|
print "Go away: error code %s, data %s" % ( |
|
f.error_code, f.additional_data |
|
) |
|
|
|
if 'END_STREAM' in f.flags: |
|
self.transport.loseConnection() |
|
self.complete.callback(None) |
|
self.complete = None |
|
break |
|
|
|
def connectionLost(self, reason): |
|
if self.complete is not None: |
|
self.complete.callback(None) |
|
|
|
def sendRequest(self): |
|
headers = [ |
|
(':method', 'GET'), |
|
(':authority', 'http2bin.org'), |
|
(':path', '/ip'), |
|
(':scheme', 'https'), |
|
('user-agent', 'Twisted/15.2.0') |
|
] |
|
f = HeadersFrame(1) |
|
f.data = e.encode(headers) |
|
f.flags = set(['END_HEADERS', 'END_STREAM']) |
|
self.transport.write(f.serialize()) |
|
|
|
return endpoints.connectProtocol( |
|
endpoints.SSL4ClientEndpoint(reactor, 'http2bin.org', 443, options), |
|
BasicH2Request() |
|
).addCallback(lambda protocol: protocol.complete) |
|
|
|
task.react(main) |