diff options
Diffstat (limited to 'wsgitools/scgi/asynchronous.py')
-rw-r--r-- | wsgitools/scgi/asynchronous.py | 98 |
1 files changed, 70 insertions, 28 deletions
diff --git a/wsgitools/scgi/asynchronous.py b/wsgitools/scgi/asynchronous.py index 61bbc6b..264e43e 100644 --- a/wsgitools/scgi/asynchronous.py +++ b/wsgitools/scgi/asynchronous.py @@ -5,10 +5,24 @@ import io import socket import sys import errno +import typing -from wsgitools.internal import bytes2str, str2bytes +from wsgitools.internal import ( + bytes2str, + Environ, + HeaderList, + OptExcInfo, + str2bytes, + WriteCallback, + WsgiApp, +) from wsgitools.scgi import _convert_environ, FileWrapper +if sys.version_info >= (3, 8): + LiteralTrue = typing.Literal[True] +else: + LiteralTrue = bool + class SCGIConnection(asyncore.dispatcher): """SCGI connection class used by L{SCGIServer}.""" # connection states @@ -19,11 +33,27 @@ class SCGIConnection(asyncore.dispatcher): RESP = 3*4 | 2 # sending response, end state RESPH = 4*4 | 2 # buffered response headers, sending headers only, to TRANS TRANS = 5*4 | 2 # transferring using FileWrapper, end state - def __init__(self, server, connection, maxrequestsize=65536, - maxpostsize=8<<20, blocksize=4096, config={}): + + outheaders: typing.Union[ + typing.Tuple[()], # unset + typing.Tuple[str, HeaderList], # headers + LiteralTrue # sent + ] + wsgihandler: typing.Optional[typing.Iterable[bytes]] + wsgiiterator: typing.Optional[typing.Iterator[bytes]] + + def __init__( + self, + server: "SCGIServer", + connection: socket.socket, + maxrequestsize: int = 65536, + maxpostsize: int = 8<<20, + blocksize: int = 4096, + config: Environ = {}, + ): asyncore.dispatcher.__init__(self, connection) - self.server = server # WSGISCGIServer instance + self.server = server self.maxrequestsize = maxrequestsize self.maxpostsize = maxpostsize self.blocksize = blocksize @@ -35,10 +65,9 @@ class SCGIConnection(asyncore.dispatcher): self.wsgihandler = None # wsgi application self.wsgiiterator = None # wsgi application iterator self.outheaders = () # headers to be sent - # () -> unset, (..,..) -> set, True -> sent self.body = io.BytesIO() # request body - def _try_send_headers(self): + def _try_send_headers(self) -> None: if self.outheaders != True: assert not self.outbuff status, headers = self.outheaders @@ -47,7 +76,7 @@ class SCGIConnection(asyncore.dispatcher): self.outbuff = str2bytes(headdata) self.outheaders = True - def _wsgi_write(self, data): + def _wsgi_write(self, data: bytes) -> None: assert self.state >= SCGIConnection.RESP assert self.state < SCGIConnection.TRANS assert isinstance(data, bytes) @@ -55,15 +84,15 @@ class SCGIConnection(asyncore.dispatcher): self._try_send_headers() self.outbuff += data - def readable(self): + def readable(self) -> bool: """C{asyncore} interface""" return self.state & 1 == 1 - def writable(self): + def writable(self) -> bool: """C{asyncore} interface""" return self.state & 2 == 2 - def handle_read(self): + def handle_read(self) -> None: """C{asyncore} interface""" data = self.recv(self.blocksize) self.inbuff += data @@ -122,6 +151,7 @@ class SCGIConnection(asyncore.dispatcher): self.environ["wsgi.errors"] = self.server.error self.wsgihandler = self.server.wsgiapp(self.environ, self.start_response) + assert self.wsgihandler is not None if isinstance(self.wsgihandler, FileWrapper) and \ self.wsgihandler.can_transfer(): self._try_send_headers() @@ -134,7 +164,12 @@ class SCGIConnection(asyncore.dispatcher): self.reqlen -= len(self.inbuff) self.inbuff = b"" - def start_response(self, status, headers, exc_info=None): + def start_response( + self, + status: str, + headers: HeaderList, + exc_info: OptExcInfo = None, + ) -> WriteCallback: assert isinstance(status, str) assert isinstance(headers, list) if exc_info: @@ -147,7 +182,7 @@ class SCGIConnection(asyncore.dispatcher): self.outheaders = (status, headers) return self._wsgi_write - def send_buff(self): + def send_buff(self) -> None: try: sentbytes = self.send(self.outbuff[:self.blocksize]) except socket.error: @@ -155,11 +190,12 @@ class SCGIConnection(asyncore.dispatcher): else: self.outbuff = self.outbuff[sentbytes:] - def handle_write(self): + def handle_write(self) -> None: """C{asyncore} interface""" if self.state == SCGIConnection.RESP: if len(self.outbuff) < self.blocksize: self._try_send_headers() + assert self.wsgiiterator is not None for data in self.wsgiiterator: assert isinstance(data, bytes) if data: @@ -171,23 +207,26 @@ class SCGIConnection(asyncore.dispatcher): self.send_buff() elif self.state == SCGIConnection.RESPH: assert len(self.outbuff) > 0 + assert isinstance(self.wsgihandler, FileWrapper) self.send_buff() if not self.outbuff: self.state = SCGIConnection.TRANS else: assert self.state == SCGIConnection.TRANS + assert isinstance(self.wsgihandler, FileWrapper) assert self.wsgihandler.can_transfer() sent = self.wsgihandler.transfer(self.socket, self.blocksize) if sent <= 0: self.close() - def close(self): + def close(self) -> None: # None doesn't have a close attribute if hasattr(self.wsgihandler, "close"): + assert self.wsgihandler is not None self.wsgihandler.close() asyncore.dispatcher.close(self) - def handle_close(self): + def handle_close(self) -> None: """C{asyncore} interface""" self.close() @@ -195,33 +234,36 @@ __all__.append("SCGIServer") class SCGIServer(asyncore.dispatcher): """SCGI Server for WSGI applications. It does not use multiple processes or multiple threads.""" - def __init__(self, wsgiapp, port, interface="localhost", error=sys.stderr, - maxrequestsize=None, maxpostsize=None, blocksize=None, - config={}, reusesocket=None): + + def __init__( + self, + wsgiapp: WsgiApp, + port: int, + interface: str = "localhost", + error: typing.TextIO = sys.stderr, + maxrequestsize: typing.Optional[int] = None, + maxpostsize: typing.Optional[int] = None, + blocksize: typing.Optional[int] = None, + config: Environ = {}, + reusesocket: typing.Optional[socket.socket] = None + ): """ @param wsgiapp: is the wsgi application to be run. - @type port: int @param port: is an int representing the TCP port number to be used. - @type interface: str @param interface: is a string specifying the network interface to bind which defaults to C{"localhost"} making the server inaccessible over network. @param error: is a file-like object being passed as C{wsgi.error} in the environ parameter defaulting to stderr. - @type maxrequestsize: int @param maxrequestsize: limit the size of request blocks in scgi connections. Connections are dropped when this limit is hit. - @type maxpostsize: int @param maxpostsize: limit the size of post bodies that may be processed by this instance. Connections are dropped when this limit is hit. - @type blocksize: int @param blocksize: is amount of data to read or write from or to the network at once - @type config: {} @param config: the environ dictionary is updated using these values for each request. - @type reusesocket: None or socket.socket @param reusesocket: If a socket is passed, do not create a socket. Instead use given socket as listen socket. The passed socket must be set up for accepting tcp connections (i.e. C{AF_INET}, @@ -234,7 +276,7 @@ class SCGIServer(asyncore.dispatcher): self.wsgiapp = wsgiapp self.error = error - self.conf = {} + self.conf: Environ = {} if maxrequestsize is not None: self.conf["maxrequestsize"] = maxrequestsize if maxpostsize is not None: @@ -251,7 +293,7 @@ class SCGIServer(asyncore.dispatcher): else: self.accepting = True - def handle_accept(self): + def handle_accept(self) -> None: """asyncore interface""" try: ret = self.accept() @@ -264,7 +306,7 @@ class SCGIServer(asyncore.dispatcher): conn, _ = ret SCGIConnection(self, conn, **self.conf) - def run(self): + def run(self) -> None: """Runs the server. It will not return and you can invoke C{asyncore.loop()} instead achieving the same effect.""" asyncore.loop() |