Add Python wrapper for spdylay
Still incomplete. See SPDY server sample in spdyserv.py.
This commit is contained in:
parent
8561ef72ed
commit
8ce1925609
|
@ -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)
|
|
@ -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'])]
|
||||
)
|
|
@ -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 = <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 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 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 = <Session>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 = <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 == 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 = <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 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
|
||||
data_prd = <object>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, <char*>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 = \
|
||||
<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 = NULL
|
||||
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 = 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,
|
||||
<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 == 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,
|
||||
<uint8_t*>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 <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 == 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,
|
||||
<void*>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,
|
||||
<void*>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 <spdylay/spdylay.h>
|
||||
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
|
|
@ -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 = '''\
|
||||
<html>
|
||||
<head><title>SPDY FTW</title></head>
|
||||
<body>
|
||||
<h1>SPDY FTW</h1>
|
||||
<p>The age of HTTP/1.1 is over. The time of SPDY has come.</p>
|
||||
<p>Your browser {} supports SPDY.</p>
|
||||
</body>
|
||||
</html>
|
||||
'''.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()
|
Loading…
Reference in New Issue