Skip to content

Instantly share code, notes, and snippets.

@Lukasa
Created April 14, 2015 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lukasa/a8b392f3f41433a9a473 to your computer and use it in GitHub Desktop.
Save Lukasa/a8b392f3f41433a9a473 to your computer and use it in GitHub Desktop.
Twisted HTTP/2 spike
Twisted==15.1.0
hpack==1.0.0
hyperframe==1.0.0
# -*- coding: utf-8 -*-
"""
A brief spike to test a Twisted implementation of HTTP/2.
"""
import json
import time
from twisted.internet import protocol, reactor
from hyperframe import frame as h2
from hpack.hpack import Encoder, Decoder
class HTTP2Protocol(protocol.Protocol):
"""
A Twisted HTTP/2 server protocol (maybe).
"""
def __init__(self, *args, **kwargs):
self.buffer = b''
self.streams = {}
self.encoder = Encoder()
self.decoder = Decoder()
def dataReceived(self, data):
"""
HTTP/2 data has arrived.
"""
# COPY ALL THE DATA
self.buffer += data
buffer = memoryview(self.buffer)
while True:
if len(buffer) < 9:
break
f, length = h2.Frame.parse_frame_header(buffer[:9])
if len(buffer) < (length + 9):
break
f.parse_body(buffer[9:9+length])
print f
buffer = buffer[9+length:]
self.handle_frame(f)
self.buffer = buffer.tobytes()
def handle_frame(self, frame):
if isinstance(frame, h2.HeadersFrame):
# A new stream!
self.streams[frame.stream_id] = RequestHandler(self)
headers = self.decoder.decode(frame.data)
self.streams[frame.stream_id].requestBegin(
frame.stream_id,
headers,
)
if 'END_STREAM' in frame.flags:
self.streams[frame.stream_id].requestComplete()
elif isinstance(frame, h2.DataFrame):
self.streams[frame.stream_id].dataReceived(frame.data)
if 'END_STREAM' in frame.flags:
self.streams[frame.stream_id].requestComplete()
elif isinstance(frame, h2.WindowUpdateFrame):
# TODO: maybe some flow control maybe?
pass
elif isinstance(frame, h2.GoAwayFrame):
# TODO: handle connection teardown
pass
elif isinstance(frame, h2.PingFrame):
# Send it back.
frame.flags.add('ACK')
self.transport.write(frame.serialize())
elif isinstance(frame, h2.SettingsFrame):
# Echo back the frame.
frame.flags.add('ACK')
self.transport.write(frame.serialize())
elif isinstance(frame, h2.RstStreamFrame):
# TODO: handle stream teardown.
pass
def beginResponse(self, stream_id, headers):
f = h2.HeadersFrame(stream_id)
f.data = self.encoder.encode(headers)
f.flags = set(['END_HEADERS'])
self.transport.write(f.serialize())
def sendData(self, stream_id, data):
f = h2.DataFrame(stream_id)
f.data = data
self.transport.write(f.serialize())
def endResponse(self, stream_id, data=b''):
f = h2.DataFrame(stream_id)
f.data = data
f.flags = set(['END_STREAM'])
self.transport.write(f.serialize())
del self.streams[stream_id]
class RequestHandler(object):
"""
A simple object that returns basic 200 responses to requests.
"""
def __init__(self, protocol):
self.protocol = protocol
def requestBegin(self, stream_id, headers):
self.stream_id = stream_id
self.headers = headers
self.data = []
print headers
def requestComplete(self):
response_headers = [
(':status', '200'),
('server', 'hyper-twisted/0.0.1 twisted/15.1.0'),
('content-type', 'application/json'),
]
self.protocol.beginResponse(self.stream_id, response_headers)
response = {
'headers': self.headers,
'data': b''.join(self.data),
'time': time.time(),
}
self.protocol.sendData(self.stream_id, json.dumps(response))
self.protocol.endResponse(self.stream_id)
def dataReceived(self, data):
self.data.append(data)
print data
factory = protocol.Factory()
factory.protocol = HTTP2Protocol
reactor.listenTCP(8888, factory)
reactor.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment