# -*- encoding: utf-8 -*-
"""
hio.core.tcp.serving Module
Accepter listens and accepts incoming TCP socket connections
Server is subclass of Acceptor
Server creates Remoters
Remoter is accepted incoming socket connection
ServerTls is subclass of Server
RemoterTls is subclass of Remoter
"""
import sys
import os
import errno
import socket
import ssl
from collections import deque
from contextlib import contextmanager
from ... import help
from ...base import tyming, doing
from .. import coring
[docs]logger = help.ogler.getLogger()
@contextmanager
[docs]def openServer(cls=None, **kwa):
"""
Wrapper to create and open TCP Server instances
When used in with statement block, calls .close() on exit of with block
Parameters:
cls is Class instance of subclass instance
Usage:
with openServer() as server0:
server0.
with openServer(cls=ServerTls) as server0:
server0.
"""
server = None
cls = cls if cls is not None else Server
try:
server = cls(**kwa)
server.reopen() # opens accept socket
yield server
finally:
if server:
server.close()
[docs]class Acceptor(tyming.Tymee):
"""
Acceptor Base Class for Server.
Nonblocking TCP Socket Acceptor Class.
Listen socket for incoming TCP connections
See tyming.Tymee for inherited attributes, properties, and methods
Attributes:
.ha is (host,port) duple (two tuple)
host = "" or "0.0.0.0" means listen on all interfaces
.eha is normalized (host, port) duple for incoming TLS connections
as external facing address for TLS context
.bs is buffer size
.ss is server listen socket for incoming accept requests
.axes is deque of accepte connection duples (ca, cs)
.opened is boolean, True if listen socket .ss opened. False otherwise
"""
def __init__(self, ha=None, bs=8096, **kwa):
"""
Initialization method for instance.
ha is host address duple (host, port) listen interfaces
host = "" or "0.0.0.0" means listen on all interfaces
bs = buffer size
"""
super(Acceptor, self).__init__(**kwa)
self.ha = ha or (host, port) # ha = host address
eha = self.ha
host, port = eha # expand so can normalize host
host = coring.normalizeHost(host)
if host in ('0.0.0.0',):
host = '127.0.0.1' # need specific interface for tls
elif host in ("::", "0:0:0:0:0:0:0:0"):
host = "::1" # need specific interface for tls
self.eha = (host, port)
self.bs = bs
self.ss = None # listen socket for accepts
self.axes = deque() # deque of duple (ca, cs) accepted connections
self.opened = False
[docs] def actualBufSizes(self):
"""
Returns duple of the the actual socket send and receive buffer size
(send, receive)
"""
if not self.ss:
return (0, 0)
return (self.ss.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF),
self.ss.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
[docs] def open(self):
"""
Opens binds listen socket in non blocking mode.
if socket not closed properly, binding socket gets error
OSError: (48, 'Address already in use')
"""
#create server socket ss to listen on
self.ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# make socket address reusable.
# the SO_REUSEADDR flag tells the kernel to reuse a local socket in
# TIME_WAIT state, without waiting for its natural timeout to expire.
self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Linux TCP allocates twice the requested size
if sys.platform.startswith('linux'):
bs = 2 * self.bs # get size is twice the set size
else:
bs = self.bs
if self.ss.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) < bs:
self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.bs)
if self.ss.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) < bs:
self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, self.bs)
self.ss.setblocking(0) #non blocking socket
# bind to listen socket (host, port) to receive connections
self.ss.bind(self.ha)
self.ss.listen(5)
#try: # bind to listen socket (host, port) to receive connections
#self.ss.bind(self.ha)
#self.ss.listen(5)
#except OSError as ex:
#return False
self.ha = self.ss.getsockname() # get resolved ha after bind
self.opened = True
return True
[docs] def reopen(self):
"""
Idempotently opens listen socket
"""
self.close()
return self.open()
[docs] def close(self):
"""
Closes listen socket.
"""
if self.ss:
try:
self.ss.shutdown(socket.SHUT_RDWR) # shutdown socket
except OSError as ex:
pass
self.ss.close() #close socket
self.ss = None
self.opened = False
[docs] def accept(self):
"""
Accept new connection nonblocking
Returns duple (cs, ca) of connected socket and connected host address
Otherwise if no new connection returns (None, None)
"""
# accept new virtual connected socket created from server socket
try:
cs, ca = self.ss.accept() # virtual connection (socket, host address)
except OSError as ex:
if ex.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
return (None, None) # nothing yet
raise # re-raise
return (cs, ca)
[docs] def serviceAccepts(self):
"""
Service any accept requests
Adds to .cxes dict key by ca
"""
while True:
cs, ca = self.accept()
if not cs:
break
self.axes.append((cs, ca))
[docs]class Server(Acceptor):
"""
Nonblocking TCP Socket Server Class.
Listen socket for incoming TCP connections that generates Remoter sockets
for accepted connections
See tyming.Tymee for inherited attributes, properties, and methods
Inherited Attributes:
.ha is (host,port) duple (two tuple)
host = "" or "0.0.0.0" means listen on all interfaces
.eha is normalized (host, port) duple for incoming TLS connections
as external facing address for TLS context
.bs is buffer size
.ss is server listen socket for incoming accept requests
.axes is deque of accepte connection duples (ca, cs)
.opened is boolean, True if listen socket .ss opened. False otherwise
Attributes:
.tymeout is tymeout in seconds for connection refresh
.wl is WireLog instance if any
.ixes is dict of incoming connections indexed by remote (host, port) duple
"""
[docs] Tymeout = 1.0 # tymeout in seconds virtual tyme
def __init__(self,
ha=None,
host="",
port=56000,
tymeout=None,
wl=None,
**kwa):
"""
Initialization method for instance.
Parameters:
ha is TCP/IP (host, port) duple for listen socket
host is default TCP/IP host address for listen socket
"" or "0.0.0.0" is listen on all interfaces
port is default TCP/IP port
tymeout is default tymeout for to pass to remoters for incoming connections
wl is WireLog instance if any
"""
ha = ha or (host, port)
super(Server, self).__init__(ha=ha, **kwa)
self.tymeout = tymeout if tymeout is not None else self.Tymeout
self.wl = wl
self.ixes = dict() # ready to rx tx incoming connections, Remoter instances
[docs] def wind(self, tymth):
"""
Inject new tymist.tymth as new ._tymth. Changes tymist.tyme base.
Updates winds .tymer .tymth
"""
super(Server, self).wind(tymth)
for rm in self.ixes.values(): # remotoer
rm.wind(tymth)
[docs] def serviceAxes(self):
"""
Service axes
For each newly accepted connection in .axes create Remoter
and add to .ixes keyed by ca
"""
self.serviceAccepts() # populate .axes
while self.axes:
cs, ca = self.axes.popleft()
if ca != cs.getpeername(): #or self.eha != cs.getsockname():
raise ValueError("Accepted socket host addresses malformed for "
"peer. eha {0} != {1}, ca {2} != {3}\n".format(
self.eha, cs.getsockname(), ca, cs.getpeername()))
remoter = Remoter(tymth=self.tymth,
ha=cs.getsockname(),
ca=ca,
cs=cs,
bs=self.bs,
wl=self.wl,
timeout=self.tymeout)
if ca in self.ixes and self.ixes[ca] is not remoter:
self.shutdownIx[ca]
self.ixes[ca] = remoter
[docs] def serviceConnects(self):
"""
Service connects is method name to be used
"""
self.serviceAxes()
[docs] def shutdownIx(self, ca, how=socket.SHUT_RDWR):
"""
Shutdown remoter given by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].shutdown(how=how)
[docs] def shutdownSendIx(self, ca):
"""
Shutdown send on remoter given by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].shutdownSend()
[docs] def shutdownReceiveIx(self, ca):
"""
Shutdown send on remoter given by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].shutdownReceive()
[docs] def closeIx(self, ca):
"""
Shutdown and close remoter given by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].close()
[docs] def closeAllIx(self):
"""
Shutdown and close all remoter connections
"""
for rm in self.ixes.values(): # remoter
rm.close()
[docs] def close(self):
"""
Close all sockets
"""
super(Server, self).close() # call super close
self.closeAllIx()
[docs] def removeIx(self, ca, close=True):
"""
Remove remoter given by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
if close:
self.ixes[ca].close() # shutdown and close socket
del self.ixes[ca]
[docs] def serviceReceivesIx(self, ca):
"""
Service receives for remoter by connection address ca
"""
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].serviceReceives()
[docs] def serviceReceivesAllIx(self):
"""
Service receives for all remoters in .ixes
"""
for ix in self.ixes.values():
ix.serviceReceives()
[docs] def transmitIx(self, data, ca):
'''
Queue data onto .txbs for remoter given by connection address ca
'''
if ca not in self.ixes:
emsg = "Invalid connection address '{0}'".format(ca)
raise ValueError(emsg)
self.ixes[ca].tx(data)
[docs] def serviceSendsAllIx(self):
"""
Service transmits for all remoters in .ixes
"""
for rm in self.ixes.values(): # remoter
rm.serviceSends()
[docs] def service(self):
"""
Service connects and service receives and sends for all ix.
"""
self.serviceConnects()
self.serviceReceivesAllIx()
self.serviceSendsAllIx()
[docs]def initServerContext(context=None,
version=None,
certify=None,
keypath=None,
certpath=None,
cafilepath=None
):
"""
Initialize and return context for TLS Server
IF context is None THEN create a context
IF version is None THEN create context using ssl library default
ELSE create context with version
If certify is not None then use certify value provided Otherwise use default
context = context object for tls/ssl If None use default
version = ssl protocol version If None use default
certify = cert requirement If None use default
ssl.CERT_NONE = 0
ssl.CERT_OPTIONAL = 1
ssl.CERT_REQUIRED = 2
keypath = pathname of local server side PKI private key file path
If given apply to context
certpath = pathname of local server side PKI public cert file path
If given apply to context
cafilepath = Cert Authority file path to use to verify client cert
If given apply to context
"""
if context is None: # create context
if not version: # use default context with default protocol version
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.verify_mode = certify if certify is not None else ssl.CERT_REQUIRED
else: # create context with specified protocol version
context = ssl.SSLContext(protocol=version)
# disable bad protocols versions
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
# disable compression to prevent CRIME attacks (OpenSSL 1.0+)
context.options |= getattr(ssl._ssl, "OP_NO_COMPRESSION", 0)
# Prefer the server's ciphers by default fro stronger encryption
context.options |= getattr(ssl._ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
# Use single use keys in order to improve forward secrecy
context.options |= getattr(ssl._ssl, "OP_SINGLE_DH_USE", 0)
context.options |= getattr(ssl._ssl, "OP_SINGLE_ECDH_USE", 0)
# disallow ciphers with known vulnerabilities
context.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
context.verify_mode = certify if certify is not None else ssl.CERT_REQUIRED
if cafilepath:
context.load_verify_locations(cafile=cafilepath,
capath=None,
cadata=None)
elif context.verify_mode != ssl.CERT_NONE:
context.load_default_certs(purpose=ssl.Purpose.CLIENT_AUTH)
if keypath or certpath:
context.load_cert_chain(certfile=certpath, keyfile=keypath)
return context
[docs]class ServerTls(Server):
"""
Server with Nonblocking TLS/SSL support
Nonblocking TCP Socket Server Class.
Listen socket for incoming TCP connections
RemoterTLS sockets for accepted connections
See tyming.Tymee for inherited attributes, properties, and methods
Inherited Attributes:
.ha is (host,port) duple (two tuple)
host = "" or "0.0.0.0" means listen on all interfaces
.eha is normalized (host, port) duple for incoming TLS connections
as external facing address for TLS context
.bs is buffer size
.ss is server listen socket for incoming accept requests
.axes is deque of accepte connection duples (ca, cs)
.opened is boolean, True if listen socket .ss opened. False otherwise
.timeout is timeout in seconds for connection refresh
.wl is WireLog instance if any
.ixes is dict of incoming connections indexed by remote (host, port) duple
Attributes:
.context is TLS context instance
.version is TLS version
.certify is boolean, True to client certify, False otherwise
.keypath is path to key file
.certpath is path to cert file
.cafilepath is path to ca file
"""
def __init__(self,
context=None,
version=None,
certify=None,
keypath=None,
certpath=None,
cafilepath=None,
**kwa):
"""
Initialization method for instance.
"""
super(ServerTls, self).__init__(**kwa)
self.cxes = dict() # accepted incoming connections, RemoterTLS instances
self.context = context
self.version = version
self.certify = certify
self.keypath = keypath
self.certpath = certpath
self.cafilepath = cafilepath
self.context = initServerContext(context=context,
version=version,
certify=certify,
keypath=keypath,
certpath=certpath,
cafilepath=cafilepath
)
[docs] def serviceAxes(self):
"""
Service accepteds
For each new accepted connection create RemoterTLS and add to .cxes
Not Handshaked
"""
self.serviceAccepts() # populate .axes
while self.axes:
cs, ca = self.axes.popleft()
if ca != cs.getpeername() or self.eha != cs.getsockname():
raise ValueError("Accepted socket host addresses malformed for "
"peer ha {0} != {1}, ca {2} != {3}\n".format(
self.ha, cs.getsockname(), ca, cs.getpeername()))
remoter = RemoterTls(tymth=self.tymth,
ha=cs.getsockname(),
ca=ca,
bs=self.bs,
cs=cs,
wl=self.wl,
timeout=self.tymeout,
context=self.context,
version=self.version,
certify=self.certify,
keypath=self.keypath,
certpath=self.certpath,
cafilepath=self.cafilepath,
)
self.cxes[ca] = remoter
[docs] def serviceCxes(self):
"""
Service handshakes for every remoter in .cxes
If successful move to .ixes
"""
for ca, cx in list(self.cxes.items()):
if cx.serviceHandshake():
self.ixes[ca] = cx
del self.cxes[ca]
[docs] def serviceConnects(self):
"""
Service accept and handshake attempts
If not already accepted and handshaked Then
make nonblocking attempt
For each successful handshaked add to .ixes
Returns handshakeds
"""
self.serviceAxes()
self.serviceCxes()
[docs]class Remoter(tyming.Tymee):
"""
Class to service an incoming nonblocking TCP connection from a remote client.
Should only be used from Acceptor subclass
"""
[docs] Tymeout = 0.0 # virtual tymeout in seconds
def __init__(self,
ha,
ca,
cs,
tymeout=None,
refreshable=True,
bs=8096,
wl=None,
**kwa
):
"""
Initialization method for instance.
ha = host address duple (host, port) near side of connection. cs.getsockname()
useful for debugging after cs is closed
ca = connection address used as key in severs's ixes. Need this to
know how to delete from .ixes when connection closed as .cs loses
cs.getpeername() after its closed.
cs = connection socket object
tymeout = tymeout for .tymer
refreshable = True if tx/rx activity refreshes timer False otherwise
bs = buffer size
wl = WireLog object if any
"""
super(Remoter, self).__init__(**kwa)
self.ha = ha # connection address of server
self.ca = ca # connection address of peer used to index in server.ixes
self.cs = cs # connection socket
if self.cs:
self.cs.setblocking(0) # linux does not preserve blocking from accept
self.tymeout = tymeout if tymeout is not None else self.Tymeout
self.tymer = tyming.Tymer(tymth=self.tymth, duration=self.tymeout)
self.cutoff = False # True when detect connection closed on far side
self.refreshable = refreshable
self.bs = bs
self.txbs = bytearray() # bytearray of data to send
self.rxbs = bytearray() # bytearray of data received
self.wl = wl
[docs] def wind(self, tymth):
"""
Inject new tymist.tymth as new ._tymth. Changes tymist.tyme base.
Updates winds .tymer .tymth
"""
super(Remoter, self).wind(tymth)
self.tymer.wind(tymth)
[docs] def shutdown(self, how=socket.SHUT_RDWR):
"""
Shutdown connected socket .cs
"""
if self.cs:
try:
self.cs.shutdown(how) # shutdown socket
except OSError as ex:
pass
[docs] def shutdownSend(self):
"""
Shutdown send on connected socket .cs
"""
if self.cs:
try:
self.shutdown(how=socket.SHUT_WR) # shutdown socket
except OSError as ex:
pass
[docs] def shutdownReceive(self):
"""
Shutdown receive on connected socket .cs
"""
if self.cs:
try:
self.shutdown(how=socket.SHUT_RD) # shutdown socket
except OSError as ex:
pass
[docs] def close(self):
"""
Shutdown and close connected socket .cs
"""
if self.cs:
self.shutdown()
self.cs.close() #close socket
self.cs = None
[docs] def refresh(self):
"""
Restart tymer
"""
self.tymer.restart()
[docs] def receive(self):
"""
Perform non blocking receive on connected socket .cs
If no data then returns None
If connection closed then returns ''
Otherwise returns data
data is string in python2 and bytes in python3
"""
try:
data = self.cs.recv(self.bs)
except OSError as ex:
# ex.args[0] == ex.errno for better os compatibility.
# the value of a given errno.XXXXX may be different on each os
if ex.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
return None # keep trying
elif ex.args[0] in (errno.ECONNRESET,
errno.ENETRESET,
errno.ENETUNREACH,
errno.EHOSTUNREACH,
errno.ENETDOWN,
errno.EHOSTDOWN,
errno.ETIMEDOUT,
errno.ECONNREFUSED):
self.cutoff = True # this signals need to close/reopen connection
return bytes() # data empty
else:
raise # re-raise
if data: # connection open
if self.wl: # log over the wire rx
self.wl.writeRx(data, self.ca)
if self.refreshable:
self.refresh()
else: # data empty so connection closed on other end
self.cutoff = True
return data
[docs] def serviceReceives(self):
"""
Service receives until no more
"""
while not self.cutoff:
data = self.receive()
if not data:
break
self.rxbs.extend(data)
[docs] def serviceReceiveOnce(self):
'''
Retrieve from server only one reception
'''
if not self.cutoff:
data = self.receive()
if data:
self.rxbs.extend(data)
[docs] def clearRxbs(self):
"""
Clear .rxbs
"""
del self.rxbs[:]
[docs] def send(self, data):
"""
Perform non blocking send on connected socket .cs.
Return number of bytes sent
data is string in python2 and bytes in python3
"""
try:
count = self.cs.send(data) #result is number of bytes sent
except OSError as ex:
# ex.args[0] == ex.errno for better compat
# the value of a given errno.XXXXX may be different on each os
if ex.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
count = 0 # blocked try again
elif ex.args[0] in (errno.ECONNRESET,
errno.ENETRESET,
errno.ENETUNREACH,
errno.EHOSTUNREACH,
errno.ENETDOWN,
errno.EHOSTDOWN,
errno.ETIMEDOUT,
errno.ECONNREFUSED):
self.cutoff = True # this signals need to close/reopen connection
count = 0
else:
raise
if count:
if self.wl:
self.wl.writeTx(data[:count], self.ca)
if self.refreshable:
self.refresh()
return count
[docs] def tx(self, data):
'''
Queue data onto .txbs
'''
self.txbs.extend(data)
[docs] def serviceSends(self):
"""
Service transmits
For each tx if all bytes sent then keep sending until partial send
or no more to send
If partial send reattach and return
"""
while self.txbs and not self.cutoff:
count = self.send(self.txbs)
del self.txbs[:count]
break # try again later
[docs]class RemoterTls(Remoter):
"""
Class to service an incoming nonblocking TCP/TLS connection from a remote client.
Should only be used from Acceptor subclass
Provides nonblocking TLS/SSL support
"""
def __init__(self,
context=None,
version=None,
certify=None,
keypath=None,
certpath=None,
cafilepath=None,
**kwa):
"""
Initialization method for instance.
context = context object for tls/ssl If None use default
version = ssl version If None use default
certify = cert requirement If None use default
ssl.CERT_NONE = 0
ssl.CERT_OPTIONAL = 1
ssl.CERT_REQUIRED = 2
keypath = pathname of local server side PKI private key file path
If given apply to context
certpath = pathname of local server side PKI public cert file path
If given apply to context
cafilepath = Cert Authority file path to use to verify client cert
If given apply to context
"""
super(RemoterTls, self).__init__(**kwa)
self.connected = False # True once ssl handshake completed
self.context = initServerContext(context=context,
version=version,
certify=certify,
keypath=keypath,
certpath=certpath,
cafilepath=cafilepath
)
self.wrap()
[docs] def close(self):
"""
Shutdown and close connected socket .cs
"""
if self.cs:
self.shutdown()
self.cs.close() #close socket
self.cs = None
self.connected = False
[docs] def wrap(self):
"""
Wrap socket .cs in ssl context
"""
self.cs = self.context.wrap_socket(self.cs,
server_side=True,
do_handshake_on_connect=False)
[docs] def handshake(self):
"""
Attempt nonblocking ssl handshake to .ha
Returns True if successful
Returns False if not so try again later
"""
try:
self.cs.do_handshake()
except ssl.SSLError as ex:
if ex.errno in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE):
return False
elif ex.errno in (ssl.SSL_ERROR_EOF, ):
self.shutclose()
raise # should give up here nicely
else:
self.shutclose()
raise
except OSError as ex:
self.shutclose()
if ex.errno in (errno.ECONNABORTED, ):
raise # should give up here nicely
raise
except Exception as ex:
self.shutclose()
raise
self.connected = True
return True
[docs] def serviceHandshake(self):
"""
Service connection and handshake attempt
If not already accepted and handshaked Then
make nonblocking attempt
Returns .handshaked
"""
if not self.connected:
self.handshake()
return self.connected
[docs] def receive(self):
"""
Perform non blocking receive on connected socket .cs
If no data then returns None
If connection closed then returns ''
Otherwise returns data
data is string in python2 and bytes in python3
"""
try:
data = self.cs.recv(self.bs)
except OSError as ex: # ssl.SSLError is a subtype of OSError
# ex.args[0] == ex.errno for better compat
# the value of a given errno.XXXXX may be different on each os
if ex.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE):
return None # blocked waiting for data
elif ex.args[0] in (errno.ECONNRESET,
errno.ENETRESET,
errno.ENETUNREACH,
errno.EHOSTUNREACH,
errno.ENETDOWN,
errno.EHOSTDOWN,
errno.ETIMEDOUT,
errno.ECONNREFUSED,
ssl.SSLEOFError):
self.cutoff = True # this signals need to close/reopen connection
return bytes() # data empty
else:
raise # re-raise
if data: # connection open
if self.wl: # log over the wire rx
self.wl.writeRx(data, who=self.cs.getpeername())
else: # data empty so connection closed on other end
self.cutoff = True
return data
[docs] def send(self, data):
"""
Perform non blocking send on connected socket .cs.
Return number of bytes sent
data is string in python2 and bytes in python3
"""
try:
result = self.cs.send(data) #result is number of bytes sent
except OSError as ex: # ssl.SSLError is a subtype of OSError
# ex.args[0] == ex.errno for better compat
# the value of a given errno.XXXXX may be different on each os
if ex.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE):
result = 0 # blocked try again
elif ex.args[0] in (errno.ECONNRESET,
errno.ENETRESET,
errno.ENETUNREACH,
errno.EHOSTUNREACH,
errno.ENETDOWN,
errno.EHOSTDOWN,
errno.ETIMEDOUT,
errno.ECONNREFUSED,
ssl.SSLEOFError):
self.cutoff = True # this signals need to close/reopen connection
result = 0
else:
raise
if result:
if self.wl:
self.wl.writeTx(data[:result], who=self.cs.getpeername())
return result
[docs]class ServerDoer(doing.Doer):
"""
Basic TCP Server Doer
See Doer for inherited attributes, properties, and methods.
Attributes:
.server is TCP Server instance
Properties:
"""
def __init__(self, server, **kwa):
"""
Initialize
Parameters:
server is TCP Server instance
"""
super(ServerDoer, self).__init__(**kwa)
self.server = server
if self.tymth:
self.server.wind(self.tymth)
[docs] def wind(self, tymth):
"""
Inject new tymist.tymth as new ._tymth. Changes tymist.tyme base.
Updates winds .tymer .tymth
"""
super(ServerDoer, self).wind(tymth)
self.server.wind(tymth)
[docs] def enter(self):
""""""
self.server.reopen()
[docs] def recur(self, tyme):
""""""
self.server.service()
[docs] def exit(self):
""""""
self.server.close()
[docs]class EchoServerDoer(ServerDoer):
"""
Echo TCP Server
Just echoes back to client whatever it receives from client
See Doer for inherited attributes, properties, and methods.
Inherited Attributes:
.server is TCP Server instance
"""
[docs] def enter(self):
""""""
self.server.reopen()
[docs] def recur(self, tyme):
""""""
self.server.service()
for ca, ix in self.server.ixes.items():
if ix.rxbs:
ix.tx(bytes(ix.rxbs)) # echo back
ix.clearRxbs()
[docs] def exit(self):
""""""
self.server.close()