From 8ce1925609e2a10b47b71ed4c9e2522173167cfe Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 17 Aug 2012 01:36:30 +0900 Subject: [PATCH] Add Python wrapper for spdylay Still incomplete. See SPDY server sample in spdyserv.py. --- python/cspdylay.pxd | 229 +++++++++++++++ python/setup.py | 12 + python/spdylay.pyx | 687 ++++++++++++++++++++++++++++++++++++++++++++ python/spdyserv.py | 123 ++++++++ 4 files changed, 1051 insertions(+) create mode 100644 python/cspdylay.pxd create mode 100644 python/setup.py create mode 100644 python/spdylay.pyx create mode 100644 python/spdyserv.py diff --git a/python/cspdylay.pxd b/python/cspdylay.pxd new file mode 100644 index 00000000..a033b1c5 --- /dev/null +++ b/python/cspdylay.pxd @@ -0,0 +1,229 @@ +from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t + +cdef extern from 'spdylay/spdylay.h': + + ctypedef enum spdylay_proto_version: + SPDYLAY_PROTO_SPDY2 + SPDYLAY_PROTO_SPDY3 + + ctypedef enum spdylay_error: + SPDYLAY_ERR_INVALID_ARGUMENT + SPDYLAY_ERR_ZLIB + SPDYLAY_ERR_UNSUPPORTED_VERSION + SPDYLAY_ERR_WOULDBLOCK + SPDYLAY_ERR_EOF + SPDYLAY_ERR_DEFERRED + SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE + # Fatal errors follow + SPDYLAY_ERR_NOMEM + SPDYLAY_ERR_CALLBACK_FAILURE + + ctypedef enum spdylay_ctrl_flag: + SPDYLAY_CTRL_FLAG_NONE + SPDYLAY_CTRL_FLAG_FIN + SPDYLAY_CTRL_FLAG_UNIDIRECTIONAL + + ctypedef enum spdylay_frame_type: + SPDYLAY_SYN_STREAM + SPDYLAY_SYN_REPLY + SPDYLAY_RST_STREAM + SPDYLAY_SETTINGS + SPDYLAY_NOOP + SPDYLAY_PING + SPDYLAY_GOAWAY + SPDYLAY_HEADERS + SPDYLAY_WINDOW_UPDATE + SPDYLAY_CREDENTIAL + + ctypedef enum spdylay_status_code: + SPDYLAY_OK + SPDYLAY_PROTOCOL_ERROR + SPDYLAY_INVALID_STREAM + SPDYLAY_REFUSED_STREAM + SPDYLAY_UNSUPPORTED_VERSION + SPDYLAY_CANCEL + SPDYLAY_INTERNAL_ERROR + SPDYLAY_FLOW_CONTROL_ERROR + # Following status codes were introduced in SPDY/3 + SPDYLAY_STREAM_IN_USE + SPDYLAY_STREAM_ALREADY_CLOSED + SPDYLAY_INVALID_CREDENTIALS + SPDYLAY_FRAME_TOO_LARGE + + # The status codes for GOAWAY, introduced in SPDY/3. + ctypedef enum spdylay_goaway_status_code: + SPDYLAY_GOAWAY_OK + SPDYLAY_GOAWAY_PROTOCOL_ERROR + SPDYLAY_GOAWAY_INTERNAL_ERROR + + ctypedef enum spdylay_settings_flag: + SPDYLAY_FLAG_SETTINGS_NONE + SPDYLAY_FLAG_SETTINGS_CLEAR_SETTINGS + + ctypedef enum spdylay_settings_id_flag: + SPDYLAY_ID_FLAG_SETTINGS_NONE + SPDYLAY_ID_FLAG_SETTINGS_PERSIST_VALUE + SPDYLAY_ID_FLAG_SETTINGS_PERSISTED + + ctypedef enum spdylay_settings_id: + SPDYLAY_SETTINGS_UPLOAD_BANDWIDTH + SPDYLAY_SETTINGS_DOWNLOAD_BANDWIDTH + SPDYLAY_SETTINGS_ROUND_TRIP_TIME + SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS + SPDYLAY_SETTINGS_CURRENT_CWND + SPDYLAY_SETTINGS_DOWNLOAD_RETRANS_RATE + SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE + SPDYLAY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE + SPDYLAY_SETTINGS_MAX + + ctypedef struct spdylay_ctrl_hd: + uint16_t version + uint16_t type + uint8_t flags + int32_t length + + ctypedef struct spdylay_syn_stream: + spdylay_ctrl_hd hd + int32_t stream_id + int32_t assoc_stream_id + uint8_t pri + uint8_t slot + char **nv + + ctypedef struct spdylay_syn_reply: + spdylay_ctrl_hd hd + int32_t stream_id + char **nv + + ctypedef struct spdylay_rst_stream: + spdylay_ctrl_hd hd + int32_t stream_id + uint32_t status_code + + ctypedef struct spdylay_settings_entry: + int32_t settings_id + uint8_t flags + uint32_t value + + ctypedef struct spdylay_settings: + spdylay_ctrl_hd hd + size_t niv + spdylay_settings_entry *iv + + ctypedef struct spdylay_goaway: + spdylay_ctrl_hd hd + int32_t last_good_stream_id + uint32_t status_code + + ctypedef union spdylay_frame: + spdylay_syn_stream syn_stream + spdylay_syn_reply syn_reply + spdylay_rst_stream rst_stream + spdylay_settings settings + #spdylay_ping ping + spdylay_goaway goaway + #spdylay_headers headers + #spdylay_window_update window_update + #spdylay_credential credential + + ctypedef union spdylay_data_source: + int fd + void *ptr + + ctypedef ssize_t (*spdylay_data_source_read_callback)\ + (spdylay_session *session, int32_t stream_id, + uint8_t *buf, size_t length, int *eof, + spdylay_data_source *source, void *user_data) + + ctypedef struct spdylay_data_provider: + spdylay_data_source source + spdylay_data_source_read_callback read_callback + + ctypedef struct spdylay_session: + pass + + + ctypedef ssize_t (*spdylay_send_callback)\ + (spdylay_session *session, + uint8_t *data, size_t length, int flags, void *user_data) + + ctypedef ssize_t (*spdylay_recv_callback)\ + (spdylay_session *session, + uint8_t *buf, size_t length, int flags, void *user_data) + + ctypedef void (*spdylay_on_ctrl_recv_callback)\ + (spdylay_session *session, spdylay_frame_type frame_type, + spdylay_frame *frame, void *user_data) + + ctypedef void (*spdylay_on_data_chunk_recv_callback)\ + (spdylay_session *session, uint8_t flags, int32_t stream_id, + uint8_t *data, size_t len, void *user_data) + + ctypedef struct spdylay_session_callbacks: + spdylay_send_callback send_callback + spdylay_recv_callback recv_callback + spdylay_on_ctrl_recv_callback on_ctrl_recv_callback + spdylay_on_data_chunk_recv_callback on_data_chunk_recv_callback + + int spdylay_session_client_new(spdylay_session **session_ptr, + int version, + spdylay_session_callbacks *callbacks, + void *user_data) + + int spdylay_session_server_new(spdylay_session **session_ptr, + int version, + spdylay_session_callbacks *callbacks, + void *user_data) + + void spdylay_session_del(spdylay_session *session) + + + int spdylay_session_recv(spdylay_session *session) + + ssize_t spdylay_session_mem_recv(spdylay_session *session, + uint8_t *data, size_t length) + + int spdylay_session_send(spdylay_session *session) + + int spdylay_session_resume_data(spdylay_session *session, + int32_t stream_id) + + bint spdylay_session_want_read(spdylay_session *session) + + bint spdylay_session_want_write(spdylay_session *session) + + void* spdylay_session_get_stream_user_data(spdylay_session *session, + int32_t stream_id) + + size_t spdylay_session_get_outbound_queue_size(spdylay_session *session) + + uint8_t spdylay_session_get_pri_lowest(spdylay_session *session) + + int spdylay_session_fail_session(spdylay_session *session, + uint32_t status_code) + + char* spdylay_strerror(int error_code) + + int spdylay_submit_request(spdylay_session *session, uint8_t pri, + char **nv, + spdylay_data_provider *data_prd, + void *stream_user_data) + + int spdylay_submit_response(spdylay_session *session, + int32_t stream_id, char **nv, + spdylay_data_provider *data_prd) + + int spdylay_submit_syn_stream(spdylay_session *session, uint8_t flags, + int32_t assoc_stream_id, uint8_t pri, + char **nv, void *stream_user_data) + + int spdylay_submit_syn_reply(spdylay_session *session, uint8_t flags, + int32_t stream_id, char **nv) + + int spdylay_submit_rst_stream(spdylay_session *session, + int32_t stream_id, uint32_t status_code) + + int spdylay_submit_goaway(spdylay_session *session, uint32_t status_code) + + int spdylay_submit_settings(spdylay_session *session, uint8_t flags, + spdylay_settings_entry *iv, size_t niv) diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 00000000..bc6b4765 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,12 @@ +from distutils.core import setup +from distutils.extension import Extension + +spdylay_dir = '../' + +setup( + ext_modules = [Extension("spdylay", + ["spdylay.c"], + include_dirs=[spdylay_dir + 'lib/includes'], + library_dirs=[spdylay_dir + 'lib/.libs'], + libraries=['spdylay'])] + ) diff --git a/python/spdylay.pyx b/python/spdylay.pyx new file mode 100644 index 00000000..ff00f287 --- /dev/null +++ b/python/spdylay.pyx @@ -0,0 +1,687 @@ +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 + +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 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 void 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 void 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 RstStreamFrame(CtrlFrame): + cdef int32_t stream_id + cdef uint32_t status_code + + cdef void 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 void 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 GoawayFrame(CtrlFrame): + cdef int32_t last_good_stream_id + cdef uint32_t status_code + + cdef void 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 object cnv2pynv(char **nv): + ''' Convert C-style name/value pairs ``nv`` to Python style + pairs. ''' + cdef size_t i + pynv = [] + i = 0 + while nv[i] != NULL: + pynv.append((nv[i], nv[i+1])) + i += 2 + return pynv + +cdef char** pynv2cnv(object nv) except *: + ''' Convert Python style name/value pairs ``nv`` to C-style + pairs. Python style name/value pairs are list of tuple (key, + value).''' + cdef char **cnv = 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 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 =\ + 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 = pydata_prd + cdata_prd.read_callback = read_callback + +cdef void on_ctrl_recv_callback(cspdylay.spdylay_session *session, + cspdylay.spdylay_frame_type frame_type, + cspdylay.spdylay_frame *frame, + void *user_data): + cdef SynStreamFrame syn_stream + cdef SynReplyFrame syn_reply + cdef RstStreamFrame rst_stream + cdef SettingsFrame settings + cdef GoawayFrame goaway + + cdef Session pysession = user_data + + if not pysession.on_ctrl_recv_cb: + return + + 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_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_GOAWAY: + goaway = GoawayFrame() + goaway.fill(&frame.goaway) + pyframe = goaway + + if pyframe: + try: + pysession.on_ctrl_recv_cb(pysession, pyframe) + 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 = 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, 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 = user_data + if pysession.send_callback: + try: + rv = pysession.send_callback(pysession, (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 == cspdylay.SPDYLAY_ERR_WOULDBLOCK: + return cspdylay.SPDYLAY_ERR_WOULDBLOCK + else: + return rv + 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 = user_data + if pysession.on_data_chunk_recv_cb: + try: + pysession.on_data_chunk_recv_cb(pysession, flags, stream_id, + (data)[:length]) + except Exception as e: + pysession.error = e + except BaseException as e: + pysession.base_error = e + +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 = user_data + data_prd = source.ptr + + try: + data, status = data_prd.read_cb(pysession, stream_id, length, + 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 status == cspdylay.SPDYLAY_ERR_EOF: + eof[0] = 1 + elif status == cspdylay.SPDYLAY_ERR_DEFERRED: + return status + if data: + if len(data) > length: + return cspdylay.SPDYLAY_ERR_CALLBACK_FAILURE + memcpy(buf, data, len(data)) + return len(data) + 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_data_chunk_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, + recv_cb=None, send_cb=None, + on_data_chunk_recv_cb=None, + on_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 = \ + recv_callback + c_session_callbacks.send_callback = \ + send_callback + c_session_callbacks.on_ctrl_recv_callback = \ + on_ctrl_recv_callback + # c_session_callbacks.on_invalid_ctrl_recv_callback = NULL + c_session_callbacks.on_data_chunk_recv_callback = \ + \ + on_data_chunk_recv_callback + # c_session_callbacks.on_data_recv_callback = NULL + # c_session_callbacks.before_ctrl_send_callback = NULL + # c_session_callbacks.on_ctrl_send_callback = NULL + # c_session_callbacks.on_ctrl_not_send_callback = NULL + # c_session_callbacks.on_data_send_callback = NULL + # c_session_callbacks.on_stream_close_callback = NULL + # c_session_callbacks.on_request_recv_callback = NULL + # 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 = NULL + # c_session_callbacks.on_unknown_ctrl_recv_callback = NULL + + self.recv_callback = recv_cb + self.send_callback = send_cb + self.on_data_chunk_recv_cb = on_data_chunk_recv_cb + self.on_ctrl_recv_cb = on_ctrl_recv_cb + self.user_data = user_data + + if side == CLIENT: + rv = cspdylay.spdylay_session_client_new(&self._c_session, + version, + &c_session_callbacks, + self) + elif side == SERVER: + rv = cspdylay.spdylay_session_server_new(&self._c_session, + version, + &c_session_callbacks, + self) + else: + raise InvalidArgumentError('side must be either CLIENT or SERVER') + + if rv == cspdylay.SPDYLAY_ERR_NOMEM: + raise MemoryError() + elif rv == cspdylay.SPDYLAY_ERR_ZLIB: + raise ZlibError(cspdylay.spdylay_strerror(rv)) + elif rv == cspdylay.SPDYLAY_ERR_UNSUPPORTED_VERSION: + raise UnsupportedVersionError(cspdylay.spdylay_strerror(rv)) + + def __init__(self, side, version, + recv_cb=None, send_cb=None, + on_data_chunk_recv_cb=None, + on_ctrl_recv_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, + c_data, len(data)) + if self.base_error: + raise self.base_error + if self.error: + raise self.error + if 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 == 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 == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT: + raise InvalidArgumentError(cspdylay.spdylay_strerror(rv)) + 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 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 == cspdylay.SPDYLAY_ERR_NOMEM: + raise MemoryError() + + cpdef submit_request(self, pri, nv, data_prd=None, stream_user_data=None): + ''' Submits frame and optionally one or more DATA frames. If + data_prd is not None, it provides data which will be sent in + subsequent DATA frames. It must have 2 attributes: source and + read_cb. source is an opaque object and passed to read_cb + callback. read_cb must be None or a callable object. The + library calls it when it needs data. 4 arguments are passed to + read_cb: session, stream_id, length and source. And it returns + at most length bytes of byte string. The session is self. The + stream_id is the stream ID of the stream. The length is the + maximum length the library expects. read_cb must not return + more that length bytes. The source is the object passed in + data_prd.source. + ''' + cdef cspdylay.spdylay_data_provider c_data_prd + cdef cspdylay.spdylay_data_provider *c_data_prd_ptr + cdef char **cnv = pynv2cnv(nv) + 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_request(self._c_session, pri, cnv, + c_data_prd_ptr, + stream_user_data) + free(cnv) + if rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT: + raise InvalidArgumentError(cspdylay.spdylay_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 + cdef char **cnv = pynv2cnv(nv) + 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_response(self._c_session, stream_id, + cnv, c_data_prd_ptr) + free(cnv) + if rv == cspdylay.SPDYLAY_ERR_NOMEM: + raise MemoryError() + + cpdef submit_syn_stream(self, flags, assoc_stream_id, pri, nv, + stream_user_data): + cdef char **cnv = pynv2cnv(nv) + cdef int rv + rv = cspdylay.spdylay_submit_syn_stream(self._c_session, + flags, + assoc_stream_id, + pri, + cnv, + stream_user_data) + free(cnv) + if rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT: + raise InvalidArgumentError(cspdylay.spdylay_strerror(rv)) + elif rv == cspdylay.SPDYLAY_ERR_NOMEM: + raise MemoryError() + + cpdef submit_syn_reply(self, flags, stream_id, nv): + cdef char **cnv = pynv2cnv(nv) + cdef int rv + rv = cspdylay.spdylay_submit_syn_reply(self._c_session, + flags, stream_id, cnv) + free(cnv) + if 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 == 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 == 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 == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT: + raise InvalidArgumentError(cspdylay.spdylay_strerror(rv)) + elif rv == cspdylay.SPDYLAY_ERR_NOMEM: + raise MemoryError() + +# 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 + +# Error codes used in callback +ERR_OK = 0 # Not defined in +ERR_EOF = cspdylay.SPDYLAY_ERR_EOF +ERR_DEFERRED = cspdylay.SPDYLAY_ERR_DEFERRED + +# 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 diff --git a/python/spdyserv.py b/python/spdyserv.py new file mode 100644 index 00000000..03fcf79e --- /dev/null +++ b/python/spdyserv.py @@ -0,0 +1,123 @@ +#!/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 + +import spdylay + +# private key file +KEY_FILE='server.key' +# certificate file +CERT_FILE='server.crt' + +def send_cb(session, data): + ssctrl = session.user_data + ssctrl.sock.sendall(data) + return len(data) + +def read_cb(session, stream_id, length, source): + data = source.read(length) + if data: + status = spdylay.ERR_OK + else: + status = spdylay.ERR_EOF + return data, status + +def on_ctrl_recv_cb(session, frame): + ssctrl = session.user_data + if frame.frame_type == spdylay.SYN_STREAM: + # This will crash cookies... + nv = dict(frame.nv) + if b'user-agent' in nv: + user_agent = nv[b'user-agent'].decode('utf-8') + else: + user_agent = '' + html = '''\ + +SPDY FTW + +

SPDY FTW

+

The age of HTTP/1.1 is over. The time of SPDY has come.

+

Your browser {} supports SPDY.

+ + +'''.format(user_agent) + data_prd = spdylay.DataProvider(io.BytesIO(bytes(html, 'utf-8')), + read_cb) + + stctrl = StreamCtrl(frame.stream_id, data_prd) + ssctrl.streams[frame.stream_id] = stctrl + nv = [(b':status', b'200 OK'), + (b':version', b'HTTP/1.1'), + (b'server', b'python+spdylay')] + session.submit_response(frame.stream_id, nv, data_prd) + +class StreamCtrl: + def __init__(self, stream_id, data_prd): + self.stream_id = stream_id + self.data_prd = data_prd + +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']) + sock = ctx.wrap_socket(self.request, server_side=True) + 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, + user_data=ssctrl) + + session.submit_settings(\ + spdylay.FLAG_SETTINGS_NONE, + [(spdylay.SETTINGS_MAX_CONCURRENT_STREAMS, + spdylay.ID_FLAG_SETTINGS_NONE, + 100)]) + while session.want_read() or session.want_write(): + data = sock.recv(4096) + if data: + session.recv(data) + session.send() + else: + break + +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()