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.0' 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 = 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 =\ 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 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 = 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 = 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 = 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 = 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 = 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 = user_data if not pysession.on_ctrl_recv_parse_error_cb: return try: pysession.on_ctrl_recv_parse_error_cb(pysession, frame_type, (head)[:headlen], (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 = user_data if not pysession.on_unknown_ctrl_recv_cb: return try: pysession.on_unknown_ctrl_recv_cb(pysession, (head)[:headlen], (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 = 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: 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 = 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 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 = 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 = 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 = 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 = 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 = user_data cdef ReadCtrl read_ctrl = ReadCtrl() data_prd = 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, 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 = \ 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 = \ \ on_invalid_ctrl_recv_callback c_session_callbacks.on_data_chunk_recv_callback = \ \ on_data_chunk_recv_callback c_session_callbacks.on_data_recv_callback = \ on_data_recv_callback c_session_callbacks.before_ctrl_send_callback = \ \ before_ctrl_send_callback c_session_callbacks.on_ctrl_send_callback = \ on_ctrl_send_callback c_session_callbacks.on_ctrl_not_send_callback = \ \ on_ctrl_not_send_callback c_session_callbacks.on_data_send_callback = \ on_data_send_callback c_session_callbacks.on_stream_close_callback = \ on_stream_close_callback c_session_callbacks.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 = \ \ on_ctrl_recv_parse_error_callback c_session_callbacks.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, 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 == 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, 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 elif rv == cspdylay.SPDYLAY_ERR_INVALID_ARGUMENT: raise InvalidArgumentError(_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 == 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, 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, 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 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(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 = '''\ {code} {reason}

{reason}

{explain}


{server} at {hostname} Port {port}
''' 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(['spdy/3', 'spdy/2']) 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) except ImportError: # No server for 2.x because they lack TLS NPN. pass