|
import hpack.hpack |
|
from OpenSSL import SSL |
|
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']} |
|
) |
|
|
|
def info_callback(conn, where, ret): |
|
# conn is a OpenSSL.SSL.Connection |
|
# where is a set of flags telling where in the handshake we are |
|
# http://www.openssl.org/docs/ssl/SSL_CTX_set_info_callback.html |
|
if where & SSL.SSL_CB_HANDSHAKE_START: |
|
conn.set_app_data({'handshake_done': False}) |
|
print('handshake start') |
|
if where & SSL.SSL_CB_HANDSHAKE_DONE: |
|
conn.set_app_data({'handshake_done': True}) |
|
print('handshake done') |
|
|
|
options._ctx.set_info_callback(info_callback) |
|
|
|
class BasicH2Request(protocol.Protocol): |
|
def connectionMade(self): |
|
print("connection made") |
|
self.initial_read = False |
|
self.complete = defer.Deferred() |
|
|
|
# Write the HTTP/2 preamble. |
|
self.transport.write(b'SOMETHING\r\n\r\n') |
|
#f = SettingsFrame(0) |
|
#f.settings[SettingsFrame.ENABLE_PUSH] = 0 |
|
#self.transport.write(f.serialize()) |
|
|
|
def dataReceived(self, data): |
|
print('Next protocol is: %s' % (self.transport.getNextProtocol(),)) |
|
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): |
|
print("connection lost %s" % (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) |