diff --git a/python/spdyclient.py b/python/spdyclient.py new file mode 100644 index 0000000..6065ee4 --- /dev/null +++ b/python/spdyclient.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# The example SPDY client. You need Python 3.3 or later because we +# use TLS NPN. +# +# Usage: spdyclient.py URI +# +import socket +import sys +import ssl +import select +import zlib +from urllib.parse import urlsplit +import spdylay + +def connect(hostname, port): + s = None + for res in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, + socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + try: + s = socket.socket(af, socktype, proto) + except OSError as msg: + s = None + continue + try: + s.connect(sa) + except OSError as msg: + s.close() + s = None + continue + break + return s + +class Request: + def __init__(self, uri): + self.uri = uri + self.stream_id = 0 + self.decomp = None + +class SessionCtrl: + def __init__(self, sock): + self.sock = sock + self.requests = set() + self.streams = {} + self.finish = False + +def send_cb(session, data): + ssctrl = session.user_data + wlen = ssctrl.sock.send(data) + return wlen + +def before_ctrl_send_cb(session, frame): + if frame.frame_type == spdylay.SYN_STREAM: + req = session.get_stream_user_data(frame.stream_id) + if req: + req.stream_id = frame.stream_id + session.user_data.streams[req.stream_id] = req + +def on_ctrl_recv_cb(session, frame): + if frame.frame_type == spdylay.SYN_REPLY or\ + frame.frame_type == spdylay.HEADERS: + if frame.stream_id in session.user_data.streams: + req = session.user_data.streams[frame.stream_id] + if req.decomp: + return + for k, v in frame.nv: + if k == b'content-encoding' and \ + (v.lower() == b'gzip' or v.lower() == b'deflate'): + req.decomp = zlib.decompressobj() + +def on_data_chunk_recv_cb(session, flags, stream_id, data): + if stream_id in session.user_data.streams: + req = session.user_data.streams[stream_id] + if req.decomp: + sys.stdout.buffer.write(req.decomp.decompress(data)) + else: + sys.stdout.buffer.write(data) + +def on_stream_close_cb(session, stream_id, status_code): + if stream_id in session.user_data.streams: + del session.user_data.streams[stream_id] + session.user_data.finish = True + +def get(uri): + uricomps = urlsplit(uri) + if uricomps.scheme != 'https': + print('Unsupported scheme') + sys.exit(1) + hostname = uricomps.hostname + port = uricomps.port if uricomps.port else 443 + + rawsock = connect(hostname, port) + if rawsock is None: + print('Could not open socket') + sys.exit(1) + + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx.set_npn_protocols(['spdy/3', 'spdy/2']) + + sock = ctx.wrap_socket(rawsock, server_side=False, + 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], []) + + if sock.selected_npn_protocol() == 'spdy/3': + version = spdylay.PROTO_SPDY3 + elif sock.selected_npn_protocol() == 'spdy/2': + version = spdylay.PROTO_SPDY2 + else: + return + + sessionctrl = SessionCtrl(sock) + req = Request(uri) + sessionctrl.requests.add(req) + + session = spdylay.Session(spdylay.CLIENT, + version, + send_cb=send_cb, + on_ctrl_recv_cb=on_ctrl_recv_cb, + on_data_chunk_recv_cb=on_data_chunk_recv_cb, + before_ctrl_send_cb=before_ctrl_send_cb, + on_stream_close_cb=on_stream_close_cb, + user_data=sessionctrl) + + session.submit_settings(\ + spdylay.FLAG_SETTINGS_NONE, + [(spdylay.SETTINGS_MAX_CONCURRENT_STREAMS, + spdylay.ID_FLAG_SETTINGS_NONE, + 100)]) + + if uricomps.port != 443: + hostport = uricomps.netloc + else: + hostport = uricomps.hostname + if uricomps.path: + path = uricomps.path + else: + path = '/' + if uricomps.query: + path = '?'.join([path, uricomps.query]) + + session.submit_request(0, [(b':method', b'GET'), + (b':scheme', b'https'), + (b':path', path.encode('utf-8')), + (b':version', b'HTTP/1.1'), + (b':host', hostport.encode('utf-8')), + (b'accept', b'*/*'), + (b'user-agent', b'python-spdylay')], + stream_user_data=req) + + while (session.want_read() or session.want_write()) \ + and not sessionctrl.finish: + want_read = want_write = False + try: + data = sock.recv(4096) + if data: + session.recv(data) + else: + break + except ssl.SSLWantReadError: + want_read = True + except ssl.SSLWantWriteError: + want_write = True + try: + session.send() + except ssl.SSLWantReadError: + want_read = True + except ssl.SSLWantWriteError: + want_write = True + + if want_read or want_write: + select.select([sock] if want_read else [], + [sock] if want_write else [], + []) + +if __name__ == '__main__': + uri = sys.argv[1] + get(uri) diff --git a/python/spdyserv.py b/python/spdyserv.py index f4d009e..9d31a1d 100644 --- a/python/spdyserv.py +++ b/python/spdyserv.py @@ -124,17 +124,28 @@ class ThreadedSPDYRequestHandler(socketserver.BaseRequestHandler): 100)]) while session.want_read() or session.want_write(): + want_read = want_write = False try: data = sock.recv(4096) if data: session.recv(data) else: break + except ssl.SSLWantReadError: + want_read = True + except ssl.SSLWantWriteError: + want_write = True + try: session.send() except ssl.SSLWantReadError: - select.select([sock], [], []) + want_read = True except ssl.SSLWantWriteError: - select.select([], [sock], []) + want_write = True + + if want_read or want_write: + select.select([sock] if want_read else [], + [sock] if want_write else [], + []) class ThreadedSPDYServer(socketserver.ThreadingMixIn, socketserver.TCPServer): def __init__(self, svaddr, handler):