2012-08-16 18:36:30 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# The example SPDY server. You need Python 3.3 or later because we
|
|
|
|
# use TLS NPN. Put private key and certificate file in the current
|
|
|
|
# working directory.
|
|
|
|
|
|
|
|
import socket
|
|
|
|
import threading
|
|
|
|
import socketserver
|
|
|
|
import ssl
|
|
|
|
import io
|
2012-08-22 18:59:21 +02:00
|
|
|
import select
|
2012-08-16 18:36:30 +02:00
|
|
|
|
|
|
|
import spdylay
|
|
|
|
|
|
|
|
# private key file
|
|
|
|
KEY_FILE='server.key'
|
|
|
|
# certificate file
|
|
|
|
CERT_FILE='server.crt'
|
|
|
|
|
|
|
|
def send_cb(session, data):
|
|
|
|
ssctrl = session.user_data
|
2012-08-22 18:59:21 +02:00
|
|
|
wlen = ssctrl.sock.send(data)
|
|
|
|
return wlen
|
2012-08-16 18:36:30 +02:00
|
|
|
|
2012-08-22 14:48:41 +02:00
|
|
|
def read_cb(session, stream_id, length, read_ctrl, source):
|
|
|
|
data = source.read(length)
|
|
|
|
if not data:
|
|
|
|
read_ctrl.flags = spdylay.READ_EOF
|
|
|
|
return data
|
2012-08-16 18:36:30 +02:00
|
|
|
|
|
|
|
def on_ctrl_recv_cb(session, frame):
|
|
|
|
ssctrl = session.user_data
|
|
|
|
if frame.frame_type == spdylay.SYN_STREAM:
|
2012-08-17 16:39:48 +02:00
|
|
|
stctrl = StreamCtrl(frame.stream_id)
|
|
|
|
stctrl.headers.extend(frame.nv)
|
|
|
|
ssctrl.streams[frame.stream_id] = stctrl
|
|
|
|
|
|
|
|
def on_stream_close_cb(session, stream_id, status_code):
|
|
|
|
ssctrl = session.user_data
|
|
|
|
if stream_id in ssctrl.streams:
|
|
|
|
del ssctrl.streams[stream_id]
|
|
|
|
|
|
|
|
def on_request_recv_cb(session, stream_id):
|
|
|
|
ssctrl = session.user_data
|
|
|
|
if stream_id in ssctrl.streams:
|
|
|
|
stctrl = ssctrl.streams[stream_id]
|
|
|
|
for name, value in stctrl.headers:
|
2012-08-23 14:21:13 +02:00
|
|
|
if name == 'user-agent':
|
|
|
|
user_agent = value
|
2012-08-17 16:39:48 +02:00
|
|
|
break
|
2012-08-16 18:36:30 +02:00
|
|
|
else:
|
|
|
|
user_agent = ''
|
|
|
|
html = '''\
|
|
|
|
<html>
|
|
|
|
<head><title>SPDY FTW</title></head>
|
|
|
|
<body>
|
|
|
|
<h1>SPDY FTW</h1>
|
|
|
|
<p>The age of HTTP/1.1 is over. The time of SPDY has come.</p>
|
|
|
|
<p>Your browser {} supports SPDY.</p>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
'''.format(user_agent)
|
|
|
|
data_prd = spdylay.DataProvider(io.BytesIO(bytes(html, 'utf-8')),
|
|
|
|
read_cb)
|
|
|
|
|
2012-08-17 16:39:48 +02:00
|
|
|
stctrl.data_prd = data_prd
|
2012-08-23 14:21:13 +02:00
|
|
|
nv = [(':status', '200 OK'),
|
|
|
|
(':version', 'HTTP/1.1'),
|
|
|
|
('server', 'python-spdylay')]
|
2012-08-17 16:39:48 +02:00
|
|
|
session.submit_response(stream_id, nv, data_prd)
|
2012-08-16 18:36:30 +02:00
|
|
|
|
|
|
|
class StreamCtrl:
|
2012-08-17 16:39:48 +02:00
|
|
|
def __init__(self, stream_id):
|
2012-08-16 18:36:30 +02:00
|
|
|
self.stream_id = stream_id
|
2012-08-17 16:39:48 +02:00
|
|
|
self.data_prd = None
|
|
|
|
self.headers = []
|
2012-08-16 18:36:30 +02:00
|
|
|
|
|
|
|
class SessionCtrl:
|
|
|
|
def __init__(self, sock):
|
|
|
|
self.sock = sock
|
|
|
|
self.streams = {}
|
|
|
|
|
|
|
|
class ThreadedSPDYRequestHandler(socketserver.BaseRequestHandler):
|
|
|
|
|
|
|
|
def handle(self):
|
|
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
|
|
ctx.load_cert_chain(CERT_FILE, KEY_FILE)
|
|
|
|
ctx.set_npn_protocols(['spdy/3', 'spdy/2'])
|
2012-08-22 18:59:21 +02:00
|
|
|
|
|
|
|
sock = ctx.wrap_socket(self.request, server_side=True,
|
|
|
|
do_handshake_on_connect=False)
|
|
|
|
sock.setblocking(False)
|
|
|
|
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
sock.do_handshake()
|
|
|
|
break
|
|
|
|
except ssl.SSLWantReadError as e:
|
|
|
|
select.select([sock], [], [])
|
|
|
|
except ssl.SSLWantWriteError as e:
|
|
|
|
select.select([], [sock], [])
|
|
|
|
|
2012-08-16 18:36:30 +02:00
|
|
|
if sock.selected_npn_protocol() == 'spdy/3':
|
|
|
|
version = spdylay.PROTO_SPDY3
|
|
|
|
elif sock.selected_npn_protocol() == 'spdy/2':
|
|
|
|
version = spdylay.PROTO_SPDY2
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
ssctrl = SessionCtrl(sock)
|
|
|
|
session = spdylay.Session(spdylay.SERVER,
|
|
|
|
version,
|
|
|
|
send_cb=send_cb,
|
|
|
|
on_ctrl_recv_cb=on_ctrl_recv_cb,
|
2012-08-17 16:39:48 +02:00
|
|
|
on_stream_close_cb=on_stream_close_cb,
|
|
|
|
on_request_recv_cb=on_request_recv_cb,
|
2012-08-16 18:36:30 +02:00
|
|
|
user_data=ssctrl)
|
|
|
|
|
|
|
|
session.submit_settings(\
|
|
|
|
spdylay.FLAG_SETTINGS_NONE,
|
|
|
|
[(spdylay.SETTINGS_MAX_CONCURRENT_STREAMS,
|
|
|
|
spdylay.ID_FLAG_SETTINGS_NONE,
|
|
|
|
100)])
|
2012-08-22 18:59:21 +02:00
|
|
|
|
2012-08-16 18:36:30 +02:00
|
|
|
while session.want_read() or session.want_write():
|
2012-08-22 20:15:35 +02:00
|
|
|
want_read = want_write = False
|
2012-08-22 18:59:21 +02:00
|
|
|
try:
|
|
|
|
data = sock.recv(4096)
|
|
|
|
if data:
|
|
|
|
session.recv(data)
|
|
|
|
else:
|
|
|
|
break
|
2012-08-22 20:15:35 +02:00
|
|
|
except ssl.SSLWantReadError:
|
|
|
|
want_read = True
|
|
|
|
except ssl.SSLWantWriteError:
|
|
|
|
want_write = True
|
|
|
|
try:
|
2012-08-16 18:36:30 +02:00
|
|
|
session.send()
|
2012-08-22 18:59:21 +02:00
|
|
|
except ssl.SSLWantReadError:
|
2012-08-22 20:15:35 +02:00
|
|
|
want_read = True
|
2012-08-22 18:59:21 +02:00
|
|
|
except ssl.SSLWantWriteError:
|
2012-08-22 20:15:35 +02:00
|
|
|
want_write = True
|
|
|
|
|
|
|
|
if want_read or want_write:
|
|
|
|
select.select([sock] if want_read else [],
|
|
|
|
[sock] if want_write else [],
|
|
|
|
[])
|
2012-08-16 18:36:30 +02:00
|
|
|
|
|
|
|
class ThreadedSPDYServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
|
|
def __init__(self, svaddr, handler):
|
|
|
|
self.allow_reuse_address = True
|
|
|
|
socketserver.TCPServer.__init__(self, svaddr, handler)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Port 0 means to select an arbitrary unused port
|
|
|
|
HOST, PORT = "localhost", 3000
|
|
|
|
|
|
|
|
server = ThreadedSPDYServer((HOST, PORT), ThreadedSPDYRequestHandler)
|
|
|
|
ip, port = server.server_address
|
|
|
|
|
|
|
|
# Start a thread with the server -- that thread will then start one
|
|
|
|
# more thread for each request
|
|
|
|
server_thread = threading.Thread(target=server.serve_forever)
|
|
|
|
# Exit the server thread when the main thread terminates
|
|
|
|
#server_thread.daemon = True
|
|
|
|
server_thread.start()
|