mirror of
https://github.com/moparisthebest/spdylay
synced 2024-11-13 13:05:04 -05:00
c1b130acd0
In practice, Session.resume_data() will be used without checking there is deferred data or not. Actually, there is no API to check this. So it is better not to raise exception. Instead return False to notify error. If the method succeeds, it returns True.
1685 lines
61 KiB
Cython
1685 lines
61 KiB
Cython
cimport cspdylay
|
|
|
|
from libc.stdlib cimport malloc, free
|
|
from libc.string cimport memcpy, memset
|
|
from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t
|
|
|
|
# Also update version in setup.py
|
|
__version__ = '0.1.1'
|
|
|
|
class EOFError(Exception):
|
|
pass
|
|
|
|
class CallbackFailureError(Exception):
|
|
pass
|
|
|
|
class TemporalCallbackFailureError(Exception):
|
|
pass
|
|
|
|
class InvalidArgumentError(Exception):
|
|
pass
|
|
|
|
class ZlibError(Exception):
|
|
pass
|
|
|
|
class UnsupportedVersionError(Exception):
|
|
pass
|
|
|
|
class StreamClosedError(Exception):
|
|
pass
|
|
|
|
class DataProvider:
|
|
def __init__(self, source, read_cb):
|
|
self.source = source
|
|
self.read_cb = read_cb
|
|
|
|
cdef class CtrlFrame:
|
|
cdef uint16_t version
|
|
cdef uint16_t frame_type
|
|
cdef uint8_t flags
|
|
cdef int32_t length
|
|
|
|
cdef void fillhd(self, cspdylay.spdylay_ctrl_hd *hd):
|
|
self.version = hd.version
|
|
self.frame_type = hd.type
|
|
self.flags = hd.flags
|
|
self.length = hd.length
|
|
|
|
property version:
|
|
def __get__(self):
|
|
return self.version
|
|
|
|
property frame_type:
|
|
def __get__(self):
|
|
return self.frame_type
|
|
|
|
property flags:
|
|
def __get__(self):
|
|
return self.flags
|
|
|
|
property length:
|
|
def __get__(self):
|
|
return self.length
|
|
|
|
cdef class SynStreamFrame(CtrlFrame):
|
|
cdef int32_t stream_id
|
|
cdef int32_t assoc_stream_id
|
|
cdef uint8_t pri
|
|
cdef uint8_t slot
|
|
cdef object nv
|
|
|
|
cdef fill(self, cspdylay.spdylay_syn_stream *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.stream_id = frame.stream_id
|
|
self.assoc_stream_id = frame.assoc_stream_id
|
|
self.pri = frame.pri
|
|
self.slot = frame.slot
|
|
self.nv = cnv2pynv(frame.nv)
|
|
|
|
property stream_id:
|
|
def __get__(self):
|
|
return self.stream_id
|
|
|
|
property assoc_stream_id:
|
|
def __get__(self):
|
|
return self.assoc_stream_id
|
|
|
|
property pri:
|
|
def __get__(self):
|
|
return self.pri
|
|
|
|
property nv:
|
|
def __get__(self):
|
|
return self.nv
|
|
|
|
cdef class SynReplyFrame(CtrlFrame):
|
|
cdef int32_t stream_id
|
|
cdef object nv
|
|
|
|
cdef fill(self, cspdylay.spdylay_syn_reply *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.stream_id = frame.stream_id
|
|
self.nv = cnv2pynv(frame.nv)
|
|
|
|
property stream_id:
|
|
def __get__(self):
|
|
return self.stream_id
|
|
property nv:
|
|
def __get__(self):
|
|
return self.nv
|
|
|
|
cdef class HeadersFrame(CtrlFrame):
|
|
cdef int32_t stream_id
|
|
cdef object nv
|
|
|
|
cdef fill(self, cspdylay.spdylay_headers *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.stream_id = frame.stream_id
|
|
self.nv = cnv2pynv(frame.nv)
|
|
|
|
property stream_id:
|
|
def __get__(self):
|
|
return self.stream_id
|
|
|
|
property nv:
|
|
def __get__(self):
|
|
return self.nv
|
|
|
|
cdef class RstStreamFrame(CtrlFrame):
|
|
cdef int32_t stream_id
|
|
cdef uint32_t status_code
|
|
|
|
cdef fill(self, cspdylay.spdylay_rst_stream *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.stream_id = frame.stream_id
|
|
self.status_code = frame.status_code
|
|
|
|
property stream_id:
|
|
def __get__(self):
|
|
return self.stream_id
|
|
|
|
property status_code:
|
|
def __get__(self):
|
|
return self.status_code
|
|
|
|
cdef class SettingsFrame(CtrlFrame):
|
|
cdef object iv
|
|
|
|
cdef fill(self, cspdylay.spdylay_settings *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.iv = csettings2pysettings(frame.niv, frame.iv)
|
|
|
|
|
|
property iv:
|
|
def __get__(self):
|
|
return self.iv
|
|
|
|
cdef class PingFrame(CtrlFrame):
|
|
cdef uint32_t unique_id
|
|
|
|
cdef fill(self, cspdylay.spdylay_ping *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.unique_id = frame.unique_id
|
|
|
|
property unique_id:
|
|
def __get__(self):
|
|
return self.unique_id
|
|
|
|
cdef class GoawayFrame(CtrlFrame):
|
|
cdef int32_t last_good_stream_id
|
|
cdef uint32_t status_code
|
|
|
|
cdef fill(self, cspdylay.spdylay_goaway *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.last_good_stream_id = frame.last_good_stream_id
|
|
self.status_code = frame.status_code
|
|
|
|
property last_good_stream_id:
|
|
def __get__(self):
|
|
return self.last_good_stream_id
|
|
|
|
property status_code:
|
|
def __get__(self):
|
|
return self.status_code
|
|
|
|
cdef class WindowUpdateFrame(CtrlFrame):
|
|
cdef int32_t stream_id
|
|
cdef int32_t delta_window_size
|
|
|
|
cdef fill(self, cspdylay.spdylay_window_update *frame):
|
|
self.fillhd(&frame.hd)
|
|
|
|
self.stream_id = frame.stream_id
|
|
self.delta_window_size = frame.delta_window_size
|
|
|
|
property stream_id:
|
|
def __get__(self):
|
|
return self.stream_id
|
|
|
|
property delta_window_size:
|
|
def __get__(self):
|
|
return self.delta_window_size
|
|
|
|
cdef cnv2pynv(char **nv):
|
|
''' Convert C-style name/value pairs ``nv`` to Python style
|
|
pairs. We assume that strings in nv is UTF-8 encoded as per SPDY
|
|
spec. In Python pairs, we use unicode string.'''
|
|
cdef size_t i
|
|
pynv = []
|
|
i = 0
|
|
while nv[i] != NULL:
|
|
pynv.append((nv[i].decode('UTF-8'), nv[i+1].decode('UTF-8')))
|
|
i += 2
|
|
return pynv
|
|
|
|
cdef char** pynv2cnv(object nv) except *:
|
|
''' Convert Python style UTF-8 name/value pairs ``nv`` to C-style
|
|
pairs. Python style name/value pairs are list of tuple (key,
|
|
value).'''
|
|
cdef char **cnv = <char**>malloc((len(nv)*2+1)*sizeof(char*))
|
|
cdef size_t i
|
|
if cnv == NULL:
|
|
raise MemoryError()
|
|
i = 0
|
|
for n, v in nv:
|
|
cnv[i] = n
|
|
i += 1
|
|
cnv[i] = v
|
|
i += 1
|
|
cnv[i] = NULL
|
|
return cnv
|
|
|
|
cdef pynv_encode(nv):
|
|
res = []
|
|
for k, v in nv:
|
|
res.append((k.encode('UTF-8'), v.encode('UTF-8')))
|
|
return res
|
|
|
|
cdef object csettings2pysettings(size_t niv,
|
|
cspdylay.spdylay_settings_entry *iv):
|
|
cdef size_t i = 0
|
|
cdef cspdylay.spdylay_settings_entry *ent
|
|
res = []
|
|
while i < niv:
|
|
ent = &iv[i]
|
|
res.append((ent.settings_id, ent.flags, ent.value))
|
|
i += 1
|
|
return res
|
|
|
|
cdef cspdylay.spdylay_settings_entry* pysettings2csettings(object iv) except *:
|
|
cdef size_t i
|
|
cdef cspdylay.spdylay_settings_entry *civ =\
|
|
<cspdylay.spdylay_settings_entry*>malloc(\
|
|
len(iv)*sizeof(cspdylay.spdylay_settings_entry))
|
|
if civ == NULL:
|
|
raise MemoryError()
|
|
i = 0
|
|
for settings_id, flags, value in iv:
|
|
civ[i].settings_id = settings_id
|
|
civ[i].flags = flags
|
|
civ[i].value = value
|
|
i += 1
|
|
return civ
|
|
|
|
cdef cspdylay.spdylay_data_provider create_c_data_prd\
|
|
(cspdylay.spdylay_data_provider *cdata_prd, object pydata_prd):
|
|
cdata_prd.source.ptr = <void*>pydata_prd
|
|
cdata_prd.read_callback = read_callback
|
|
|
|
|
|
cdef object cframe2pyframe(cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame):
|
|
cdef SynStreamFrame syn_stream
|
|
cdef SynReplyFrame syn_reply
|
|
cdef HeadersFrame headers
|
|
cdef RstStreamFrame rst_stream
|
|
cdef SettingsFrame settings
|
|
cdef PingFrame ping
|
|
cdef GoawayFrame goaway
|
|
cdef WindowUpdateFrame window_update
|
|
cdef object pyframe = None
|
|
if frame_type == cspdylay.SPDYLAY_SYN_STREAM:
|
|
syn_stream = SynStreamFrame()
|
|
syn_stream.fill(&frame.syn_stream)
|
|
pyframe = syn_stream
|
|
elif frame_type == cspdylay.SPDYLAY_SYN_REPLY:
|
|
syn_reply = SynReplyFrame()
|
|
syn_reply.fill(&frame.syn_reply)
|
|
pyframe = syn_reply
|
|
elif frame_type == cspdylay.SPDYLAY_HEADERS:
|
|
headers = HeadersFrame()
|
|
headers.fill(&frame.headers)
|
|
pyframe = headers
|
|
elif frame_type == cspdylay.SPDYLAY_RST_STREAM:
|
|
rst_stream = RstStreamFrame()
|
|
rst_stream.fill(&frame.rst_stream)
|
|
pyframe = rst_stream
|
|
elif frame_type == cspdylay.SPDYLAY_SETTINGS:
|
|
settings = SettingsFrame()
|
|
settings.fill(&frame.settings)
|
|
pyframe = settings
|
|
elif frame_type == cspdylay.SPDYLAY_PING:
|
|
ping = PingFrame()
|
|
ping.fill(&frame.ping)
|
|
pyframe = ping
|
|
elif frame_type == cspdylay.SPDYLAY_GOAWAY:
|
|
goaway = GoawayFrame()
|
|
goaway.fill(&frame.goaway)
|
|
pyframe = goaway
|
|
elif frame_type == cspdylay.SPDYLAY_WINDOW_UPDATE:
|
|
window_update = WindowUpdateFrame()
|
|
window_update.fill(&frame.window_update)
|
|
pyframe = window_update
|
|
return pyframe
|
|
|
|
cdef void _call_frame_callback(Session pysession,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
object callback):
|
|
if not callback:
|
|
return
|
|
try:
|
|
pyframe = cframe2pyframe(frame_type, frame)
|
|
if pyframe:
|
|
callback(pysession, pyframe)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_ctrl_recv_callback(cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
_call_frame_callback(pysession, frame_type, frame,
|
|
pysession.on_ctrl_recv_cb)
|
|
|
|
cdef void on_invalid_ctrl_recv_callback(cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
uint32_t status_code,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
|
|
if not pysession.on_invalid_ctrl_recv_cb:
|
|
return
|
|
try:
|
|
pyframe = cframe2pyframe(frame_type, frame)
|
|
if pyframe:
|
|
pysession.on_invalid_ctrl_recv_cb(pysession, pyframe, status_code)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void before_ctrl_send_callback(cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
_call_frame_callback(pysession, frame_type, frame,
|
|
pysession.before_ctrl_send_cb)
|
|
|
|
cdef void on_ctrl_send_callback(cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
_call_frame_callback(pysession, frame_type, frame,
|
|
pysession.on_ctrl_send_cb)
|
|
|
|
cdef void on_ctrl_not_send_callback(cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
cspdylay.spdylay_frame *frame,
|
|
int error_code,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
|
|
if not pysession.on_ctrl_not_send_cb:
|
|
return
|
|
try:
|
|
pyframe = cframe2pyframe(frame_type, frame)
|
|
if pyframe:
|
|
pysession.on_ctrl_not_send_cb(pysession, pyframe, error_code)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_ctrl_recv_parse_error_callback(\
|
|
cspdylay.spdylay_session *session,
|
|
cspdylay.spdylay_frame_type frame_type,
|
|
uint8_t *head, size_t headlen,
|
|
uint8_t *payload, size_t payloadlen,
|
|
int error_code, void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
|
|
if not pysession.on_ctrl_recv_parse_error_cb:
|
|
return
|
|
try:
|
|
pysession.on_ctrl_recv_parse_error_cb(pysession, frame_type,
|
|
(<char*>head)[:headlen],
|
|
(<char*>payload)[:payloadlen],
|
|
error_code)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_unknown_ctrl_recv_callback(cspdylay.spdylay_session *session,
|
|
uint8_t *head, size_t headlen,
|
|
uint8_t *payload, size_t payloadlen,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
|
|
if not pysession.on_unknown_ctrl_recv_cb:
|
|
return
|
|
try:
|
|
pysession.on_unknown_ctrl_recv_cb(pysession,
|
|
(<char*>head)[:headlen],
|
|
(<char*>payload)[:payloadlen])
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef ssize_t recv_callback(cspdylay.spdylay_session *session,
|
|
uint8_t *buf, size_t length,
|
|
int flags, void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.recv_callback:
|
|
try:
|
|
data = pysession.recv_callback(pysession, length)
|
|
except EOFError as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_EOF
|
|
except CallbackFailureError as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except Exception as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
if data:
|
|
if len(data) > length:
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
memcpy(buf, <char*>data, len(data))
|
|
return len(data)
|
|
else:
|
|
return cspdylay.SPDYLAY_ERR_WOULDBLOCK
|
|
else:
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
|
|
cdef ssize_t send_callback(cspdylay.spdylay_session *session,
|
|
uint8_t *data, size_t length, int flags,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.send_callback:
|
|
try:
|
|
rv = pysession.send_callback(pysession, (<char*>data)[:length])
|
|
except CallbackFailureError as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except Exception as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
|
|
if rv:
|
|
return rv
|
|
else:
|
|
return cspdylay.SPDYLAY_ERR_WOULDBLOCK
|
|
else:
|
|
# If no send_callback is given, pretend all data were sent and
|
|
# just return length
|
|
return length
|
|
|
|
cdef void on_data_chunk_recv_callback(cspdylay.spdylay_session *session,
|
|
uint8_t flags, int32_t stream_id,
|
|
uint8_t *data, size_t length,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.on_data_chunk_recv_cb:
|
|
try:
|
|
pysession.on_data_chunk_recv_cb(pysession, flags, stream_id,
|
|
(<char*>data)[:length])
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_data_recv_callback(cspdylay.spdylay_session *session,
|
|
uint8_t flags, int32_t stream_id,
|
|
int32_t length, void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.on_data_recv_cb:
|
|
try:
|
|
pysession.on_data_recv_cb(pysession, flags, stream_id, length)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_data_send_callback(cspdylay.spdylay_session *session,
|
|
uint8_t flags, int32_t stream_id,
|
|
int32_t length, void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.on_data_send_cb:
|
|
try:
|
|
pysession.on_data_send_cb(pysession, flags, stream_id, length)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_stream_close_callback(cspdylay.spdylay_session *session,
|
|
int32_t stream_id,
|
|
cspdylay.spdylay_status_code status_code,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.on_stream_close_cb:
|
|
try:
|
|
pysession.on_stream_close_cb(pysession, stream_id, status_code)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef void on_request_recv_callback(cspdylay.spdylay_session *session,
|
|
int32_t stream_id,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
if pysession.on_request_recv_cb:
|
|
try:
|
|
pysession.on_request_recv_cb(pysession, stream_id)
|
|
except Exception as e:
|
|
pysession.error = e
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
|
|
cdef class ReadCtrl:
|
|
cdef int flags
|
|
|
|
def __cinit__(self):
|
|
self.flags = 0
|
|
|
|
property flags:
|
|
def __set__(self, value):
|
|
self.flags = value
|
|
|
|
cdef ssize_t read_callback(cspdylay.spdylay_session *session,
|
|
int32_t stream_id, uint8_t *buf, size_t length,
|
|
int *eof, cspdylay.spdylay_data_source *source,
|
|
void *user_data):
|
|
cdef Session pysession = <Session>user_data
|
|
cdef ReadCtrl read_ctrl = ReadCtrl()
|
|
data_prd = <object>source.ptr
|
|
|
|
try:
|
|
res = data_prd.read_cb(pysession, stream_id, length, read_ctrl,
|
|
data_prd.source)
|
|
except TemporalCallbackFailureError as e:
|
|
return cspdylay.SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE
|
|
except CallbackFailureError as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except Exception as e:
|
|
pysession.error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
except BaseException as e:
|
|
pysession.base_error = e
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
|
|
if read_ctrl.flags & READ_EOF:
|
|
eof[0] = 1
|
|
if res == cspdylay.SPDYLAY_ERR_DEFERRED:
|
|
return res
|
|
elif res:
|
|
if len(res) > length:
|
|
return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
memcpy(buf, <char*>res, len(res))
|
|
return len(res)
|
|
else:
|
|
return 0
|
|
|
|
cdef class Session:
|
|
cdef cspdylay.spdylay_session *_c_session
|
|
cdef object recv_callback
|
|
cdef object send_callback
|
|
cdef object on_ctrl_recv_cb
|
|
cdef object on_invalid_ctrl_recv_cb
|
|
cdef object on_data_chunk_recv_cb
|
|
cdef object on_data_recv_cb
|
|
cdef object before_ctrl_send_cb
|
|
cdef object on_ctrl_send_cb
|
|
cdef object on_ctrl_not_send_cb
|
|
cdef object on_data_send_cb
|
|
cdef object on_stream_close_cb
|
|
cdef object on_request_recv_cb
|
|
cdef object on_ctrl_recv_parse_error_cb
|
|
cdef object on_unknown_ctrl_recv_cb
|
|
cdef object user_data
|
|
|
|
cdef object error
|
|
cdef object base_error
|
|
|
|
property user_data:
|
|
def __get__(self):
|
|
return self.user_data
|
|
|
|
def __cinit__(self, side, version, config=None,
|
|
send_cb=None, recv_cb=None,
|
|
on_ctrl_recv_cb=None,
|
|
on_invalid_ctrl_recv_cb=None,
|
|
on_data_chunk_recv_cb=None,
|
|
on_data_recv_cb=None,
|
|
before_ctrl_send_cb=None,
|
|
on_ctrl_send_cb=None,
|
|
on_ctrl_not_send_cb=None,
|
|
on_data_send_cb=None,
|
|
on_stream_close_cb=None,
|
|
on_request_recv_cb=None,
|
|
on_ctrl_recv_parse_error_cb=None,
|
|
on_unknown_ctrl_recv_cb=None,
|
|
user_data=None):
|
|
cdef cspdylay.spdylay_session_callbacks c_session_callbacks
|
|
cdef int rv
|
|
self._c_session = NULL
|
|
memset(&c_session_callbacks, 0, sizeof(c_session_callbacks))
|
|
c_session_callbacks.recv_callback = \
|
|
<cspdylay.spdylay_recv_callback>recv_callback
|
|
c_session_callbacks.send_callback = \
|
|
<cspdylay.spdylay_send_callback>send_callback
|
|
c_session_callbacks.on_ctrl_recv_callback = \
|
|
<cspdylay.spdylay_on_ctrl_recv_callback>on_ctrl_recv_callback
|
|
c_session_callbacks.on_invalid_ctrl_recv_callback = \
|
|
<cspdylay.spdylay_on_invalid_ctrl_recv_callback>\
|
|
on_invalid_ctrl_recv_callback
|
|
c_session_callbacks.on_data_chunk_recv_callback = \
|
|
<cspdylay.spdylay_on_data_chunk_recv_callback>\
|
|
on_data_chunk_recv_callback
|
|
c_session_callbacks.on_data_recv_callback = \
|
|
<cspdylay.spdylay_on_data_recv_callback>on_data_recv_callback
|
|
c_session_callbacks.before_ctrl_send_callback = \
|
|
<cspdylay.spdylay_before_ctrl_send_callback>\
|
|
before_ctrl_send_callback
|
|
c_session_callbacks.on_ctrl_send_callback = \
|
|
<cspdylay.spdylay_on_ctrl_send_callback>on_ctrl_send_callback
|
|
c_session_callbacks.on_ctrl_not_send_callback = \
|
|
<cspdylay.spdylay_on_ctrl_not_send_callback>\
|
|
on_ctrl_not_send_callback
|
|
c_session_callbacks.on_data_send_callback = \
|
|
<cspdylay.spdylay_on_data_send_callback>on_data_send_callback
|
|
c_session_callbacks.on_stream_close_callback = \
|
|
<cspdylay.spdylay_on_stream_close_callback>on_stream_close_callback
|
|
c_session_callbacks.on_request_recv_callback = \
|
|
<cspdylay.spdylay_on_request_recv_callback>on_request_recv_callback
|
|
# c_session_callbacks.get_credential_proof = NULL
|
|
# c_session_callbacks.get_credential_ncerts = NULL
|
|
# c_session_callbacks.get_credential_cert = NULL
|
|
c_session_callbacks.on_ctrl_recv_parse_error_callback = \
|
|
<cspdylay.spdylay_on_ctrl_recv_parse_error_callback>\
|
|
on_ctrl_recv_parse_error_callback
|
|
c_session_callbacks.on_unknown_ctrl_recv_callback = \
|
|
<cspdylay.spdylay_on_unknown_ctrl_recv_callback>\
|
|
on_unknown_ctrl_recv_callback
|
|
|
|
self.recv_callback = recv_cb
|
|
self.send_callback = send_cb
|
|
self.on_ctrl_recv_cb = on_ctrl_recv_cb
|
|
self.on_invalid_ctrl_recv_cb = on_invalid_ctrl_recv_cb
|
|
self.on_data_chunk_recv_cb = on_data_chunk_recv_cb
|
|
self.on_data_recv_cb = on_data_recv_cb
|
|
self.before_ctrl_send_cb = before_ctrl_send_cb
|
|
self.on_ctrl_send_cb = on_ctrl_send_cb
|
|
self.on_ctrl_not_send_cb = on_ctrl_not_send_cb
|
|
self.on_data_send_cb = on_data_send_cb
|
|
self.on_stream_close_cb = on_stream_close_cb
|
|
self.on_request_recv_cb = on_request_recv_cb
|
|
self.on_ctrl_recv_parse_error_cb = on_ctrl_recv_parse_error_cb
|
|
self.on_unknown_ctrl_recv_cb = on_unknown_ctrl_recv_cb
|
|
|
|
self.user_data = user_data
|
|
|
|
if side == CLIENT:
|
|
rv = cspdylay.spdylay_session_client_new(&self._c_session,
|
|
version,
|
|
&c_session_callbacks,
|
|
<void*>self)
|
|
elif side == SERVER:
|
|
rv = cspdylay.spdylay_session_server_new(&self._c_session,
|
|
version,
|
|
&c_session_callbacks,
|
|
<void*>self)
|
|
else:
|
|
raise InvalidArgumentError('side must be either CLIENT or SERVER')
|
|
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_ZLIB:
|
|
raise ZlibError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_UNSUPPORTED_VERSION:
|
|
raise UnsupportedVersionError(_strerror(rv))
|
|
|
|
def __init__(self, side, version, config=None,
|
|
send_cb=None, recv_cb=None,
|
|
on_ctrl_recv_cb=None,
|
|
on_invalid_ctrl_recv_cb=None,
|
|
on_data_chunk_recv_cb=None,
|
|
on_data_recv_cb=None,
|
|
before_ctrl_send_cb=None,
|
|
on_ctrl_send_cb=None,
|
|
on_ctrl_not_send_cb=None,
|
|
on_data_send_cb=None,
|
|
on_stream_close_cb=None,
|
|
on_request_recv_cb=None,
|
|
on_ctrl_recv_parse_error_cb=None,
|
|
user_data=None):
|
|
pass
|
|
|
|
def __dealloc__(self):
|
|
cspdylay.spdylay_session_del(self._c_session)
|
|
|
|
cpdef recv(self, data=None):
|
|
cdef int rv
|
|
cdef char *c_data
|
|
self.error = self.base_error = None
|
|
if data is None:
|
|
rv = cspdylay.spdylay_session_recv(self._c_session)
|
|
else:
|
|
c_data = data
|
|
rv = cspdylay.spdylay_session_mem_recv(self._c_session,
|
|
<uint8_t*>c_data, len(data))
|
|
if self.base_error:
|
|
raise self.base_error
|
|
if self.error:
|
|
raise self.error
|
|
|
|
if rv >= 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_EOF:
|
|
raise EOFError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE:
|
|
raise CallbackFailureError()
|
|
|
|
cpdef send(self):
|
|
cdef int rv
|
|
self.error = self.base_error = None
|
|
rv = cspdylay.spdylay_session_send(self._c_session)
|
|
if self.base_error:
|
|
raise self.base_error
|
|
elif self.error:
|
|
raise self.error
|
|
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE:
|
|
raise CallbackFailureError()
|
|
|
|
cpdef resume_data(self, stream_id):
|
|
cpdef int rv
|
|
rv = cspdylay.spdylay_session_resume_data(self._c_session, stream_id)
|
|
if rv == 0:
|
|
return True
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
return False
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef want_read(self):
|
|
return cspdylay.spdylay_session_want_read(self._c_session)
|
|
|
|
cpdef want_write(self):
|
|
return cspdylay.spdylay_session_want_write(self._c_session)
|
|
|
|
cpdef get_stream_user_data(self, stream_id):
|
|
return <object>cspdylay.spdylay_session_get_stream_user_data(\
|
|
self._c_session, stream_id)
|
|
|
|
cpdef get_outbound_queue_size(self):
|
|
return cspdylay.spdylay_session_get_outbound_queue_size(\
|
|
self._c_session)
|
|
|
|
cpdef get_pri_lowest(self):
|
|
return cspdylay.spdylay_session_get_pri_lowest(self._c_session)
|
|
|
|
|
|
cpdef fail_session(self, status_code):
|
|
cdef int rv
|
|
rv = cspdylay.spdylay_session_fail_session(self._c_session,
|
|
status_code)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_request(self, pri, nv, data_prd=None, stream_user_data=None):
|
|
cdef cspdylay.spdylay_data_provider c_data_prd
|
|
cdef cspdylay.spdylay_data_provider *c_data_prd_ptr
|
|
cpdef int rv
|
|
cdef char **cnv
|
|
nv = pynv_encode(nv)
|
|
cnv = pynv2cnv(nv)
|
|
if data_prd:
|
|
create_c_data_prd(&c_data_prd, data_prd)
|
|
c_data_prd_ptr = &c_data_prd
|
|
else:
|
|
c_data_prd_ptr = NULL
|
|
|
|
rv = cspdylay.spdylay_submit_request(self._c_session, pri, cnv,
|
|
c_data_prd_ptr,
|
|
<void*>stream_user_data)
|
|
free(cnv)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_response(self, stream_id, nv, data_prd=None):
|
|
cdef cspdylay.spdylay_data_provider c_data_prd
|
|
cdef cspdylay.spdylay_data_provider *c_data_prd_ptr
|
|
cpdef int rv
|
|
cdef char **cnv
|
|
nv = pynv_encode(nv)
|
|
cnv = pynv2cnv(nv)
|
|
if data_prd:
|
|
create_c_data_prd(&c_data_prd, data_prd)
|
|
c_data_prd_ptr = &c_data_prd
|
|
else:
|
|
c_data_prd_ptr = NULL
|
|
|
|
rv = cspdylay.spdylay_submit_response(self._c_session, stream_id,
|
|
cnv, c_data_prd_ptr)
|
|
free(cnv)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_syn_stream(self, flags, pri, nv, assoc_stream_id=0,
|
|
stream_user_data=None):
|
|
cdef int rv
|
|
cdef char **cnv
|
|
nv = pynv_encode(nv)
|
|
cnv = pynv2cnv(nv)
|
|
rv = cspdylay.spdylay_submit_syn_stream(self._c_session,
|
|
flags,
|
|
assoc_stream_id,
|
|
pri,
|
|
cnv,
|
|
<void*>stream_user_data)
|
|
free(cnv)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_syn_reply(self, flags, stream_id, nv):
|
|
cdef int rv
|
|
cdef char **cnv
|
|
nv = pynv_encode(nv)
|
|
cnv = pynv2cnv(nv)
|
|
rv = cspdylay.spdylay_submit_syn_reply(self._c_session,
|
|
flags, stream_id, cnv)
|
|
free(cnv)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_headers(self, flags, stream_id, nv):
|
|
cdef int rv
|
|
cdef char **cnv
|
|
nv = pynv_encode(nv)
|
|
cnv = pynv2cnv(nv)
|
|
rv = cspdylay.spdylay_submit_headers(self._c_session,
|
|
flags, stream_id, cnv)
|
|
free(cnv)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_data(self, stream_id, flags, data_prd):
|
|
cdef cspdylay.spdylay_data_provider c_data_prd
|
|
cdef cspdylay.spdylay_data_provider *c_data_prd_ptr
|
|
cpdef int rv
|
|
if data_prd:
|
|
create_c_data_prd(&c_data_prd, data_prd)
|
|
c_data_prd_ptr = &c_data_prd
|
|
else:
|
|
c_data_prd_ptr = NULL
|
|
|
|
rv = cspdylay.spdylay_submit_data(self._c_session, stream_id,
|
|
flags, c_data_prd_ptr)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_rst_stream(self, stream_id, status_code):
|
|
cdef int rv
|
|
rv = cspdylay.spdylay_submit_rst_stream(self._c_session, stream_id,
|
|
status_code)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_ping(self):
|
|
cdef int rv
|
|
rv = cspdylay.spdylay_submit_ping(self._c_session)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_goaway(self, status_code):
|
|
cdef int rv
|
|
rv = cspdylay.spdylay_submit_goaway(self._c_session, status_code)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_window_update(self, stream_id, delta_window_size):
|
|
cdef int rv
|
|
rv = cspdylay.spdylay_submit_window_update(self._c_session, stream_id,
|
|
delta_window_size)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_STREAM_CLOSED:
|
|
raise StreamClosedError()
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cpdef submit_settings(self, flags, iv):
|
|
''' Submit SETTINGS frame. iv is list of tuple (settings_id,
|
|
flag, value)
|
|
'''
|
|
cdef int rv
|
|
cdef cspdylay.spdylay_settings_entry *civ = pysettings2csettings(iv)
|
|
rv = cspdylay.spdylay_submit_settings(self._c_session, flags,
|
|
civ, len(iv))
|
|
free(civ)
|
|
if rv == 0:
|
|
return
|
|
elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT:
|
|
raise InvalidArgumentError(_strerror(rv))
|
|
elif rv == cspdylay.SPDYLAY_ERR_NOMEM:
|
|
raise MemoryError()
|
|
|
|
cdef _strerror(int error_code):
|
|
return cspdylay.spdylay_strerror(error_code).decode('UTF-8')
|
|
|
|
cpdef get_npn_protocols():
|
|
cdef size_t proto_list_len
|
|
cdef cspdylay.spdylay_npn_proto *proto_list
|
|
proto_list = cspdylay.spdylay_npn_get_proto_list(&proto_list_len)
|
|
res = []
|
|
for i in range(proto_list_len):
|
|
res.append((<char*>proto_list[i].proto)[:proto_list[i].len]\
|
|
.decode('UTF-8'))
|
|
return res
|
|
|
|
cpdef int npn_get_version(proto):
|
|
cdef char *cproto
|
|
if proto == None:
|
|
return 0
|
|
proto = proto.encode('UTF-8')
|
|
cproto = proto
|
|
return cspdylay.spdylay_npn_get_version(<unsigned char*>cproto, len(proto))
|
|
|
|
# Side
|
|
CLIENT = 1
|
|
SERVER = 2
|
|
|
|
# SPDY protocol version
|
|
PROTO_SPDY2 = cspdylay.SPDYLAY_PROTO_SPDY2
|
|
PROTO_SPDY3 = cspdylay.SPDYLAY_PROTO_SPDY3
|
|
|
|
# Control frame flags
|
|
CTRL_FLAG_NONE = cspdylay.SPDYLAY_CTRL_FLAG_NONE
|
|
CTRL_FLAG_FIN = cspdylay.SPDYLAY_CTRL_FLAG_FIN
|
|
CTRL_FLAG_UNIDIRECTIONAL = cspdylay.SPDYLAY_CTRL_FLAG_UNIDIRECTIONAL
|
|
|
|
# Data frame flags
|
|
DATA_FLAG_NONE = cspdylay.SPDYLAY_DATA_FLAG_NONE
|
|
DATA_FLAG_FIN = cspdylay.SPDYLAY_DATA_FLAG_FIN
|
|
|
|
# Error codes
|
|
ERR_INVALID_ARGUMENT = cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT
|
|
ERR_ZLIB = cspdylay.SPDYLAY_ERR_ZLIB
|
|
ERR_UNSUPPORTED_VERSION = cspdylay.SPDYLAY_ERR_UNSUPPORTED_VERSION
|
|
ERR_WOULDBLOCK = cspdylay.SPDYLAY_ERR_WOULDBLOCK
|
|
ERR_PROTO = cspdylay.SPDYLAY_ERR_PROTO
|
|
ERR_INVALID_FRAME = cspdylay.SPDYLAY_ERR_INVALID_FRAME
|
|
ERR_EOF = cspdylay.SPDYLAY_ERR_EOF
|
|
ERR_DEFERRED = cspdylay.SPDYLAY_ERR_DEFERRED
|
|
ERR_STREAM_ID_NOT_AVAILABLE = cspdylay.SPDYLAY_ERR_STREAM_ID_NOT_AVAILABLE
|
|
ERR_STREAM_CLOSED = cspdylay.SPDYLAY_ERR_STREAM_CLOSED
|
|
ERR_STREAM_CLOSING = cspdylay.SPDYLAY_ERR_STREAM_CLOSING
|
|
ERR_STREAM_SHUT_WR = cspdylay.SPDYLAY_ERR_STREAM_SHUT_WR
|
|
ERR_INVALID_STREAM_ID = cspdylay.SPDYLAY_ERR_INVALID_STREAM_ID
|
|
ERR_INVALID_STREAM_STATE = cspdylay.SPDYLAY_ERR_INVALID_STREAM_STATE
|
|
ERR_DEFERRED_DATA_EXIST = cspdylay.SPDYLAY_ERR_DEFERRED_DATA_EXIST
|
|
ERR_SYN_STREAM_NOT_ALLOWED = cspdylay.SPDYLAY_ERR_SYN_STREAM_NOT_ALLOWED
|
|
ERR_GOAWAY_ALREADY_SENT = cspdylay.SPDYLAY_ERR_GOAWAY_ALREADY_SENT
|
|
ERR_INVALID_HEADER_BLOCK = cspdylay.SPDYLAY_ERR_INVALID_HEADER_BLOCK
|
|
ERR_INVALID_STATE = cspdylay.SPDYLAY_ERR_INVALID_STATE
|
|
ERR_GZIP = cspdylay.SPDYLAY_ERR_GZIP
|
|
ERR_TEMPORAL_CALLBACK_FAILURE = cspdylay.SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE
|
|
ERR_FATAL = cspdylay.SPDYLAY_ERR_FATAL
|
|
ERR_NOMEM = cspdylay.SPDYLAY_ERR_NOMEM
|
|
ERR_CALLBACK_FAILURE = cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE
|
|
|
|
# Read Callback Flags
|
|
READ_EOF = 1
|
|
|
|
# The status code for RST_STREAM
|
|
OK = cspdylay.SPDYLAY_OK
|
|
PROTOCOL_ERROR = cspdylay.SPDYLAY_PROTOCOL_ERROR
|
|
INVALID_STREAM = cspdylay.SPDYLAY_INVALID_STREAM
|
|
REFUSED_STREAM = cspdylay.SPDYLAY_REFUSED_STREAM
|
|
UNSUPPORTED_VERSION = cspdylay.SPDYLAY_UNSUPPORTED_VERSION
|
|
CANCEL = cspdylay.SPDYLAY_CANCEL
|
|
INTERNAL_ERROR = cspdylay.SPDYLAY_INTERNAL_ERROR
|
|
FLOW_CONTROL_ERROR = cspdylay.SPDYLAY_FLOW_CONTROL_ERROR
|
|
# Following status codes were introduced in SPDY/3
|
|
STREAM_IN_USE = cspdylay.SPDYLAY_STREAM_IN_USE
|
|
STREAM_ALREADY_CLOSED = cspdylay.SPDYLAY_STREAM_ALREADY_CLOSED
|
|
INVALID_CREDENTIALS = cspdylay.SPDYLAY_INVALID_CREDENTIALS
|
|
FRAME_TOO_LARGE = cspdylay.SPDYLAY_FRAME_TOO_LARGE
|
|
|
|
# The status codes for GOAWAY, introduced in SPDY/3.
|
|
GOAWAY_OK = cspdylay.SPDYLAY_GOAWAY_OK
|
|
GOAWAY_PROTOCOL_ERROR = cspdylay.SPDYLAY_GOAWAY_PROTOCOL_ERROR
|
|
GOAWAY_INTERNAL_ERROR = cspdylay.SPDYLAY_GOAWAY_INTERNAL_ERROR
|
|
|
|
# Frame types
|
|
SYN_STREAM = cspdylay.SPDYLAY_SYN_STREAM
|
|
SYN_REPLY = cspdylay.SPDYLAY_SYN_REPLY
|
|
RST_STREAM = cspdylay.SPDYLAY_RST_STREAM
|
|
SETTINGS = cspdylay.SPDYLAY_SETTINGS
|
|
NOOP = cspdylay.SPDYLAY_NOOP
|
|
PING = cspdylay.SPDYLAY_PING
|
|
GOAWAY = cspdylay.SPDYLAY_GOAWAY
|
|
HEADERS = cspdylay.SPDYLAY_HEADERS
|
|
WINDOW_UPDATE = cspdylay.SPDYLAY_WINDOW_UPDATE
|
|
CREDENTIAL = cspdylay.SPDYLAY_CREDENTIAL
|
|
|
|
# The flags for the SETTINGS control frame.
|
|
FLAG_SETTINGS_NONE = cspdylay.SPDYLAY_FLAG_SETTINGS_NONE
|
|
FLAG_SETTINGS_CLEAR_SETTINGS = cspdylay.SPDYLAY_FLAG_SETTINGS_CLEAR_SETTINGS
|
|
|
|
# The flags for SETTINGS ID/value pair.
|
|
ID_FLAG_SETTINGS_NONE = cspdylay.SPDYLAY_ID_FLAG_SETTINGS_NONE
|
|
ID_FLAG_SETTINGS_PERSIST_VALUE = cspdylay.SPDYLAY_ID_FLAG_SETTINGS_PERSIST_VALUE
|
|
ID_FLAG_SETTINGS_PERSISTED = cspdylay.SPDYLAY_ID_FLAG_SETTINGS_PERSISTED
|
|
|
|
# The SETTINGS ID.
|
|
SETTINGS_UPLOAD_BANDWIDTH = cspdylay.SPDYLAY_SETTINGS_UPLOAD_BANDWIDTH
|
|
SETTINGS_DOWNLOAD_BANDWIDTH = cspdylay.SPDYLAY_SETTINGS_DOWNLOAD_BANDWIDTH
|
|
SETTINGS_ROUND_TRIP_TIME = cspdylay.SPDYLAY_SETTINGS_ROUND_TRIP_TIME
|
|
SETTINGS_MAX_CONCURRENT_STREAMS = \
|
|
cspdylay.SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS
|
|
SETTINGS_CURRENT_CWND = cspdylay.SPDYLAY_SETTINGS_CURRENT_CWND
|
|
SETTINGS_DOWNLOAD_RETRANS_RATE = \
|
|
cspdylay.SPDYLAY_SETTINGS_DOWNLOAD_RETRANS_RATE
|
|
SETTINGS_INITIAL_WINDOW_SIZE = cspdylay.SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE
|
|
SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = \
|
|
cspdylay.SPDYLAY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE
|
|
SETTINGS_MAX = cspdylay.SPDYLAY_SETTINGS_MAX
|
|
|
|
try:
|
|
# Simple SPDY Server implementation. We mimics the methods and
|
|
# attributes of http.server.BaseHTTPRequestHandler. Since this
|
|
# implementation uses TLS NPN, Python 3.3.0 or later is required.
|
|
|
|
import socket
|
|
import threading
|
|
import socketserver
|
|
import ssl
|
|
import io
|
|
import select
|
|
import sys
|
|
import time
|
|
from xml.sax.saxutils import escape
|
|
|
|
class Stream:
|
|
def __init__(self, stream_id):
|
|
self.stream_id = stream_id
|
|
self.data_prd = None
|
|
|
|
self.method = None
|
|
self.path = None
|
|
self.version = None
|
|
self.scheme = None
|
|
self.host = None
|
|
self.headers = []
|
|
|
|
self.rfile = None
|
|
self.wfile = None
|
|
|
|
def process_headers(self, headers):
|
|
for k, v in headers:
|
|
if k == ':method':
|
|
self.method = v
|
|
elif k == ':scheme':
|
|
self.scheme = v
|
|
elif k == ':path':
|
|
self.path = v
|
|
elif k == ':version':
|
|
self.version = v
|
|
elif k == ':host':
|
|
self.host = v
|
|
else:
|
|
self.headers.append((k, v))
|
|
|
|
class SessionCtrl:
|
|
def __init__(self):
|
|
self.streams = {}
|
|
|
|
class BaseSPDYRequestHandler(socketserver.BaseRequestHandler):
|
|
|
|
server_version = 'Python-spdylay'
|
|
|
|
error_content_type = 'text/html; charset=UTF-8'
|
|
|
|
# Same HTML from Apache error page
|
|
error_message_format = '''\
|
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
|
<html><head>
|
|
<title>{code} {reason}</title>
|
|
</head><body>
|
|
<h1>{reason}</h1>
|
|
<p>{explain}</p>
|
|
<hr>
|
|
<address>{server} at {hostname} Port {port}</address>
|
|
</body></html>
|
|
'''
|
|
|
|
def send_error(self, code, message=None):
|
|
# Make sure that code is really int
|
|
code = int(code)
|
|
try:
|
|
shortmsg, longmsg = self.responses[code]
|
|
except KeyError:
|
|
shortmsg, longmsg = '???', '???'
|
|
if message is None:
|
|
message = shortmsg
|
|
explain = longmsg
|
|
|
|
content = self.error_message_format.format(\
|
|
code=code,
|
|
reason = escape(message),
|
|
explain=escape(explain),
|
|
server=escape(self.server_version),
|
|
hostname=escape(socket.getfqdn()),
|
|
port=self.server.server_address[1]).encode('UTF-8')
|
|
|
|
self.send_response(code, message)
|
|
self.send_header('content-type', self.error_content_type)
|
|
self.send_header('content-length', str(len(content)))
|
|
|
|
self.wfile.write(content)
|
|
|
|
def send_response(self, code, message=None):
|
|
if message is None:
|
|
try:
|
|
shortmsg, _ = self.responses[code]
|
|
except KeyError:
|
|
shortmsg = '???'
|
|
message = shortmsg
|
|
|
|
self._response_headers.append((':status',
|
|
'{} {}'.format(code, message)))
|
|
|
|
def send_header(self, keyword, value):
|
|
self._response_headers.append((keyword, value))
|
|
|
|
def version_string(self):
|
|
return self.server_version + ' ' + self.sys_version
|
|
|
|
def handle_one_request(self, stream):
|
|
self.stream = stream
|
|
|
|
stream.wfile = io.BytesIO()
|
|
|
|
self.command = stream.method
|
|
self.path = stream.path
|
|
self.request_version = stream.version
|
|
self.headers = stream.headers
|
|
self.rfile = stream.rfile
|
|
self.wfile = stream.wfile
|
|
self._response_headers = []
|
|
|
|
if stream.method is None:
|
|
self.send_error(400)
|
|
else:
|
|
mname = 'do_' + stream.method
|
|
if hasattr(self, mname):
|
|
method = getattr(self, mname)
|
|
|
|
if self.rfile is not None:
|
|
self.rfile.seek(0)
|
|
|
|
method()
|
|
else:
|
|
self.send_error(501, 'Unsupported method ({})'\
|
|
.format(stream.method))
|
|
|
|
self.wfile.seek(0)
|
|
data_prd = DataProvider(self.wfile, self.read_cb)
|
|
stream.data_prd = data_prd
|
|
|
|
self.send_header(':version', 'HTTP/1.1')
|
|
self.send_header('server', self.version_string())
|
|
self.send_header('date', self.date_time_string())
|
|
|
|
self.session.submit_response(stream.stream_id,
|
|
self._response_headers, data_prd)
|
|
|
|
|
|
def send_cb(self, session, data):
|
|
return self.request.send(data)
|
|
|
|
def read_cb(self, session, stream_id, length, read_ctrl, source):
|
|
data = source.read(length)
|
|
if not data:
|
|
read_ctrl.flags = READ_EOF
|
|
return data
|
|
|
|
def on_ctrl_recv_cb(self, session, frame):
|
|
if frame.frame_type == SYN_STREAM:
|
|
stream = Stream(frame.stream_id)
|
|
self.ssctrl.streams[frame.stream_id] = stream
|
|
|
|
stream.process_headers(frame.nv)
|
|
elif frame.frame_type == HEADERS:
|
|
if frame.stream_id in self.ssctrl.streams:
|
|
stream = self.ssctrl.streams[frame.stream_id]
|
|
stream.process_headers(frame.nv)
|
|
|
|
def on_data_chunk_recv_cb(self, session, flags, stream_id, data):
|
|
if stream_id in self.ssctrl.streams:
|
|
stream = self.ssctrl.streams[stream_id]
|
|
if stream.method == 'POST':
|
|
if not stream.rfile:
|
|
stream.rfile = io.BytesIO()
|
|
stream.rfile.write(data)
|
|
else:
|
|
# We don't allow request body if method is not POST
|
|
session.submit_rst_stream(stream_id, PROTOCOL_ERROR)
|
|
|
|
def on_stream_close_cb(self, session, stream_id, status_code):
|
|
if stream_id in self.ssctrl.streams:
|
|
del self.ssctrl.streams[stream_id]
|
|
|
|
def on_request_recv_cb(self, session, stream_id):
|
|
if stream_id in self.ssctrl.streams:
|
|
stream = self.ssctrl.streams[stream_id]
|
|
self.handle_one_request(stream)
|
|
|
|
def handle(self):
|
|
self.request.setsockopt(socket.IPPROTO_TCP,
|
|
socket.TCP_NODELAY, True)
|
|
try:
|
|
self.request.do_handshake()
|
|
self.request.setblocking(False)
|
|
|
|
version = npn_get_version(self.request.selected_npn_protocol())
|
|
if version == 0:
|
|
return
|
|
|
|
self.ssctrl = SessionCtrl()
|
|
self.session = Session(\
|
|
SERVER, version,
|
|
send_cb=self.send_cb,
|
|
on_ctrl_recv_cb=self.on_ctrl_recv_cb,
|
|
on_data_chunk_recv_cb=self.on_data_chunk_recv_cb,
|
|
on_stream_close_cb=self.on_stream_close_cb,
|
|
on_request_recv_cb=self.on_request_recv_cb)
|
|
|
|
self.session.submit_settings(\
|
|
FLAG_SETTINGS_NONE,
|
|
[(SETTINGS_MAX_CONCURRENT_STREAMS, ID_FLAG_SETTINGS_NONE,
|
|
100)]
|
|
)
|
|
|
|
while self.session.want_read() or self.session.want_write():
|
|
want_read = want_write = False
|
|
try:
|
|
data = self.request.recv(4096)
|
|
if data:
|
|
self.session.recv(data)
|
|
else:
|
|
break
|
|
except ssl.SSLWantReadError:
|
|
want_read = True
|
|
except ssl.SSLWantWriteError:
|
|
want_write = True
|
|
try:
|
|
self.session.send()
|
|
except ssl.SSLWantReadError:
|
|
want_read = True
|
|
except ssl.SSLWantWriteError:
|
|
want_write = True
|
|
|
|
if want_read or want_write:
|
|
select.select([self.request] if want_read else [],
|
|
[self.request] if want_write else [],
|
|
[])
|
|
finally:
|
|
self.request.setblocking(True)
|
|
|
|
# The following methods and attributes are copied from
|
|
# Lib/http/server.py of cpython source code
|
|
|
|
def date_time_string(self, timestamp=None):
|
|
"""Return the current date and time formatted for a
|
|
message header."""
|
|
if timestamp is None:
|
|
timestamp = time.time()
|
|
year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
|
|
s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
|
|
self.weekdayname[wd],
|
|
day, self.monthname[month], year,
|
|
hh, mm, ss)
|
|
return s
|
|
|
|
weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
|
|
|
monthname = [None,
|
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
|
|
# The Python system version, truncated to its first component.
|
|
sys_version = "Python/" + sys.version.split()[0]
|
|
|
|
# Table mapping response codes to messages; entries have the
|
|
# form {code: (shortmessage, longmessage)}.
|
|
# See RFC 2616 and 6585.
|
|
responses = {
|
|
100: ('Continue', 'Request received, please continue'),
|
|
101: ('Switching Protocols',
|
|
'Switching to new protocol; obey Upgrade header'),
|
|
|
|
200: ('OK', 'Request fulfilled, document follows'),
|
|
201: ('Created', 'Document created, URL follows'),
|
|
202: ('Accepted',
|
|
'Request accepted, processing continues off-line'),
|
|
203: ('Non-Authoritative Information',
|
|
'Request fulfilled from cache'),
|
|
204: ('No Content', 'Request fulfilled, nothing follows'),
|
|
205: ('Reset Content', 'Clear input form for further input.'),
|
|
206: ('Partial Content', 'Partial content follows.'),
|
|
|
|
300: ('Multiple Choices',
|
|
'Object has several resources -- see URI list'),
|
|
301: ('Moved Permanently',
|
|
'Object moved permanently -- see URI list'),
|
|
302: ('Found', 'Object moved temporarily -- see URI list'),
|
|
303: ('See Other', 'Object moved -- see Method and URL list'),
|
|
304: ('Not Modified',
|
|
'Document has not changed since given time'),
|
|
305: ('Use Proxy',
|
|
'You must use proxy specified in Location to access this '
|
|
'resource.'),
|
|
307: ('Temporary Redirect',
|
|
'Object moved temporarily -- see URI list'),
|
|
|
|
400: ('Bad Request',
|
|
'Bad request syntax or unsupported method'),
|
|
401: ('Unauthorized',
|
|
'No permission -- see authorization schemes'),
|
|
402: ('Payment Required',
|
|
'No payment -- see charging schemes'),
|
|
403: ('Forbidden',
|
|
'Request forbidden -- authorization will not help'),
|
|
404: ('Not Found', 'Nothing matches the given URI'),
|
|
405: ('Method Not Allowed',
|
|
'Specified method is invalid for this resource.'),
|
|
406: ('Not Acceptable', 'URI not available in preferred format.'),
|
|
407: ('Proxy Authentication Required', 'You must authenticate with '
|
|
'this proxy before proceeding.'),
|
|
408: ('Request Timeout', 'Request timed out; try again later.'),
|
|
409: ('Conflict', 'Request conflict.'),
|
|
410: ('Gone',
|
|
'URI no longer exists and has been permanently removed.'),
|
|
411: ('Length Required', 'Client must specify Content-Length.'),
|
|
412: ('Precondition Failed', 'Precondition in headers is false.'),
|
|
413: ('Request Entity Too Large', 'Entity is too large.'),
|
|
414: ('Request-URI Too Long', 'URI is too long.'),
|
|
415: ('Unsupported Media Type',
|
|
'Entity body in unsupported format.'),
|
|
416: ('Requested Range Not Satisfiable',
|
|
'Cannot satisfy request range.'),
|
|
417: ('Expectation Failed',
|
|
'Expect condition could not be satisfied.'),
|
|
428: ('Precondition Required',
|
|
'The origin server requires the request to be conditional.'),
|
|
429: ('Too Many Requests', 'The user has sent too many requests '
|
|
'in a given amount of time ("rate limiting").'),
|
|
431: ('Request Header Fields Too Large',
|
|
'The server is unwilling to process '
|
|
'the request because its header fields are too large.'),
|
|
|
|
500: ('Internal Server Error', 'Server got itself in trouble'),
|
|
501: ('Not Implemented',
|
|
'Server does not support this operation'),
|
|
502: ('Bad Gateway',
|
|
'Invalid responses from another server/proxy.'),
|
|
503: ('Service Unavailable',
|
|
'The server cannot process the request due to a high load'),
|
|
504: ('Gateway Timeout',
|
|
'The gateway server did not receive a timely response'),
|
|
505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
|
|
511: ('Network Authentication Required',
|
|
'The client needs to authenticate to gain network access.'),
|
|
}
|
|
|
|
class ThreadedSPDYServer(socketserver.ThreadingMixIn,
|
|
socketserver.TCPServer):
|
|
def __init__(self, server_address, RequestHandlerCalss,
|
|
cert_file, key_file):
|
|
self.allow_reuse_address = True
|
|
|
|
self.ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
self.ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | \
|
|
ssl.OP_NO_COMPRESSION
|
|
self.ctx.load_cert_chain(cert_file, key_file)
|
|
self.ctx.set_npn_protocols(get_npn_protocols())
|
|
|
|
socketserver.TCPServer.__init__(self, server_address,
|
|
RequestHandlerCalss)
|
|
|
|
def start(self, daemon=False):
|
|
server_thread = threading.Thread(target=self.serve_forever)
|
|
server_thread.daemon = daemon
|
|
server_thread.start()
|
|
|
|
def process_request(self, request, client_address):
|
|
# ThreadingMixIn.process_request() dispatches request and
|
|
# client_address to separate thread. To cleanly shutdown
|
|
# SSL/TLS wrapped socket, we wrap socket here.
|
|
|
|
# SSL/TLS handshake is postponed to each thread.
|
|
request = self.ctx.wrap_socket(\
|
|
request, server_side=True, do_handshake_on_connect=False)
|
|
|
|
socketserver.ThreadingMixIn.process_request(self,
|
|
request, client_address)
|
|
|
|
|
|
# Simple SPDY client implementation. Since this implementation
|
|
# uses TLS NPN, Python 3.3.0 or later is required.
|
|
|
|
from urllib.parse import urlsplit
|
|
|
|
class BaseSPDYStreamHandler:
|
|
def __init__(self, url, fetcher):
|
|
self.url = url
|
|
self.fetcher = fetcher
|
|
self.stream_id = None
|
|
|
|
def on_header(self, nv):
|
|
pass
|
|
|
|
def on_data(self, data):
|
|
pass
|
|
|
|
def on_close(self, status_code):
|
|
pass
|
|
|
|
class UrlFetchError(Exception):
|
|
pass
|
|
|
|
class UrlFetcher:
|
|
def __init__(self, server_address, urls, StreamHandlerClass):
|
|
self.server_address = server_address
|
|
self.handlers = [StreamHandlerClass(url, self) for url in urls]
|
|
self.streams = {}
|
|
self.finished = []
|
|
|
|
self.ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
self.ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | \
|
|
ssl.OP_NO_COMPRESSION
|
|
self.ctx.set_npn_protocols(get_npn_protocols())
|
|
|
|
def send_cb(self, session, data):
|
|
return self.sock.send(data)
|
|
|
|
def before_ctrl_send_cb(self, session, frame):
|
|
if frame.frame_type == SYN_STREAM:
|
|
handler = session.get_stream_user_data(frame.stream_id)
|
|
if handler:
|
|
handler.stream_id = frame.stream_id
|
|
self.streams[handler.stream_id] = handler
|
|
|
|
def on_ctrl_recv_cb(self, session, frame):
|
|
if frame.frame_type == SYN_REPLY or frame.frame_type == HEADERS:
|
|
if frame.stream_id in self.streams:
|
|
handler = self.streams[frame.stream_id]
|
|
handler.on_header(frame.nv)
|
|
|
|
def on_data_chunk_recv_cb(self, session, flags, stream_id, data):
|
|
if stream_id in self.streams:
|
|
handler = self.streams[stream_id]
|
|
handler.on_data(data)
|
|
|
|
def on_stream_close_cb(self, session, stream_id, status_code):
|
|
if stream_id in self.streams:
|
|
handler = self.streams[stream_id]
|
|
handler.on_close(status_code)
|
|
del self.streams[stream_id]
|
|
self.finished.append(handler)
|
|
|
|
def connect(self, server_address):
|
|
self.sock = None
|
|
for res in socket.getaddrinfo(server_address[0], server_address[1],
|
|
socket.AF_UNSPEC,
|
|
socket.SOCK_STREAM):
|
|
af, socktype, proto, canonname, sa = res
|
|
try:
|
|
self.sock = socket.socket(af, socktype, proto)
|
|
except OSError as msg:
|
|
self.sock = None
|
|
continue
|
|
try:
|
|
self.sock.connect(sa)
|
|
except OSError as msg:
|
|
self.sock.close()
|
|
self.sock = None
|
|
continue
|
|
break
|
|
else:
|
|
raise UrlFetchError('Could not connect to {}'\
|
|
.format(server_address))
|
|
|
|
def tls_handshake(self):
|
|
self.sock = self.ctx.wrap_socket(self.sock, server_side=False,
|
|
do_handshake_on_connect=False)
|
|
self.sock.do_handshake()
|
|
|
|
self.version = npn_get_version(self.sock.selected_npn_protocol())
|
|
if self.version == 0:
|
|
raise UrlFetchError('NPN failed')
|
|
|
|
def loop(self):
|
|
self.connect(self.server_address)
|
|
try:
|
|
self._loop()
|
|
finally:
|
|
self.sock.shutdown(socket.SHUT_RDWR)
|
|
self.sock.close()
|
|
|
|
def _loop(self):
|
|
self.tls_handshake()
|
|
self.sock.setblocking(False)
|
|
|
|
session = Session(CLIENT,
|
|
self.version,
|
|
send_cb=self.send_cb,
|
|
on_ctrl_recv_cb=self.on_ctrl_recv_cb,
|
|
on_data_chunk_recv_cb=self.on_data_chunk_recv_cb,
|
|
before_ctrl_send_cb=self.before_ctrl_send_cb,
|
|
on_stream_close_cb=self.on_stream_close_cb)
|
|
|
|
session.submit_settings(\
|
|
FLAG_SETTINGS_NONE,
|
|
[(SETTINGS_MAX_CONCURRENT_STREAMS, ID_FLAG_SETTINGS_NONE, 100)]
|
|
)
|
|
|
|
if self.server_address[1] == 443:
|
|
hostport = self.server_address[0]
|
|
else:
|
|
hostport = '{}:{}'.format(self.server_address[0],
|
|
self.server_address[1])
|
|
|
|
for handler in self.handlers:
|
|
res = urlsplit(handler.url)
|
|
if res.path:
|
|
path = res.path
|
|
else:
|
|
path = '/'
|
|
if res.query:
|
|
path = '?'.join([path, res.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=handler)
|
|
|
|
while (session.want_read() or session.want_write()) \
|
|
and not len(self.finished) == len(self.handlers):
|
|
want_read = want_write = False
|
|
try:
|
|
data = self.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([self.sock] if want_read else [],
|
|
[self.sock] if want_write else [],
|
|
[])
|
|
|
|
def _urlfetch_session_one(urls, StreamHandlerClass):
|
|
res = urlsplit(urls[0])
|
|
if res.scheme != 'https':
|
|
raise UrlFetchError('Unsupported scheme {}'.format(res.scheme))
|
|
hostname = res.hostname
|
|
port = res.port if res.port else 443
|
|
|
|
f = UrlFetcher((hostname, port), urls, StreamHandlerClass)
|
|
f.loop()
|
|
|
|
def urlfetch(url_or_urls, StreamHandlerClass):
|
|
if isinstance(url_or_urls, str):
|
|
_urlfetch_session_one([url_or_urls], StreamHandlerClass)
|
|
else:
|
|
urls = []
|
|
prev_addr = (None, None)
|
|
for url in url_or_urls:
|
|
res = urlsplit(url)
|
|
port = res.port if res.port else 443
|
|
if prev_addr != (res.hostname, port):
|
|
if urls:
|
|
_urlfetch_session_one(urls, StreamHandlerClass)
|
|
urls = []
|
|
prev_addr = (res.hostname, port)
|
|
urls.append(url)
|
|
if urls:
|
|
_urlfetch_session_one(urls, StreamHandlerClass)
|
|
|
|
except ImportError:
|
|
# No server for 2.x because they lack TLS NPN.
|
|
pass
|