nghttp2/python/spdyclient.py

188 lines
5.6 KiB
Python

#!/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 == 'content-encoding' and \
(v.lower() == 'gzip' or v.lower() == '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, [(':method', 'GET'),
(':scheme', 'https'),
(':path', path),
(':version', 'HTTP/1.1'),
(':host', hostport),
('accept', '*/*'),
('user-agent', '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)