diff --git a/MicroWebSrv2/httpRequest.py b/MicroWebSrv2/httpRequest.py index 0f1df1e..6401dba 100644 --- a/MicroWebSrv2/httpRequest.py +++ b/MicroWebSrv2/httpRequest.py @@ -31,13 +31,14 @@ def _recvLine(self, onRecv) : # ------------------------------------------------------------------------ - def _waitForRecvRequest(self) : + def _waitForRecvRequest(self, isReuse=False) : self._httpVer = '' self._method = '' self._path = '' self._headers = { } self._content = None self._response = HttpResponse(self._mws2, self) + self._isReuse = isReuse self._recvLine(self._onFirstLineRecv) # ------------------------------------------------------------------------ @@ -164,7 +165,7 @@ def _routeRequest(self) : self._routeResult.Handler(self._mws2, self, self._routeResult.Args) else : self._routeResult.Handler(self._mws2, self) - if not self._response.HeadersSent : + if not self._response.HeadersSent and not self._isReuse: self._mws2.Log( 'No response was sent from route %s.' % self._routeResult, self._mws2.WARNING ) diff --git a/MicroWebSrv2/httpResponse.py b/MicroWebSrv2/httpResponse.py index 3d5fbe1..437e02d 100644 --- a/MicroWebSrv2/httpResponse.py +++ b/MicroWebSrv2/httpResponse.py @@ -174,7 +174,7 @@ def onLastChunkSent(xasCli, arg) : else : self._xasCli.OnClosed = None if self._keepAlive : - self._request._waitForRecvRequest() + self._request._waitForRecvRequest(isReuse=True) else : self._xasCli.Close() if self._onSent : diff --git a/MicroWebSrv2/libs/XAsyncSockets.py b/MicroWebSrv2/libs/XAsyncSockets.py index 836fed0..e847383 100644 --- a/MicroWebSrv2/libs/XAsyncSockets.py +++ b/MicroWebSrv2/libs/XAsyncSockets.py @@ -3,10 +3,18 @@ Copyright © 2019 Jean-Christophe Bos & HC² (www.hc2.fr) """ +import sys +_IS_MICROPYTHON = sys.implementation.name == 'micropython' +_IS_MICROPYTHON_LINUX = _IS_MICROPYTHON and (sys.platform == 'linux') from _thread import allocate_lock, start_new_thread from time import sleep -from select import select +if _IS_MICROPYTHON: + from select import poll + import select + import struct +else: + from select import select import socket import ssl @@ -30,6 +38,7 @@ def __init__(self) : self._processing = False self._threadsCount = 0 self._opLock = allocate_lock() + if _IS_MICROPYTHON: self._poll = poll() self._asyncSockets = { } self._readList = [ ] self._writeList = [ ] @@ -107,29 +116,56 @@ def _processWaitEvents(self) : self._incThreadsCount() timeSec = perf_counter() while self._processing : + if _IS_MICROPYTHON: + for socket in self._readList: + self._poll.register(socket, select.POLLIN) + for socket in self._writeList: + self._poll.register(socket, select.POLLOUT) try : try : - rd, wr, ex = select( self._readList, - self._writeList, - self._readList, - self._CHECK_SEC_INTERVAL ) + if _IS_MICROPYTHON: + ready = self._poll.poll(int(self._CHECK_SEC_INTERVAL * 1000)) + else: + rd, wr, ex = select( self._readList, + self._writeList, + self._readList, + self._CHECK_SEC_INTERVAL ) except KeyboardInterrupt as ex : raise ex - except : + except Exception as ex: continue if not self._processing : break - for socketsList in ex, wr, rd : - for socket in socketsList : + if _IS_MICROPYTHON: + for socket, mask in ready : + if (0x20) & mask: + self._poll.unregister(socket) + continue asyncSocket = self._asyncSockets.get(id(socket), None) if asyncSocket and self._socketListAdd(socket, self._handlingList) : - if socketsList is ex : - asyncSocket.OnExceptionalCondition() - elif socketsList is wr : - asyncSocket.OnReadyForWriting() - else : + # POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI + if ((select.POLLIN | 0x40 | 0x80 | 0x2) & mask): asyncSocket.OnReadyForReading() + # POLLOUT | POLLWRNORM | POLLWRBAND + elif ((select.POLLOUT | 0x100 | 0x200) & mask): + asyncSocket.OnReadyForWriting() + # POLLNVAL + else: + asyncSocket.OnExceptionalCondition() self._socketListRemove(socket, self._handlingList) + self._poll.unregister(socket) + else: + for socketsList in ex, wr, rd : + for socket in socketsList : + asyncSocket = self._asyncSockets.get(id(socket), None) + if asyncSocket and self._socketListAdd(socket, self._handlingList) : + if socketsList is ex : + asyncSocket.OnExceptionalCondition() + elif socketsList is wr : + asyncSocket.OnReadyForWriting() + else : + asyncSocket.OnReadyForReading() + self._socketListRemove(socket, self._handlingList) sec = perf_counter() if sec > timeSec + self._CHECK_SEC_INTERVAL : timeSec = sec @@ -372,7 +408,10 @@ def Create(asyncSocketsPool, srvAddr, srvBacklog=256, bufSlots=None) : raise XAsyncTCPServerException('Create : Cannot open socket (no enought memory).') try : srvSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - srvSocket.bind(srvAddr) + if _IS_MICROPYTHON: + srvSocket.bind(socket.getaddrinfo(srvAddr[0], srvAddr[1])[0][-1]) + else: + srvSocket.bind(srvAddr) srvSocket.listen(srvBacklog) except : raise XAsyncTCPServerException('Create : Error to binding the TCP server on this address.') @@ -401,6 +440,12 @@ def __init__(self, asyncSocketsPool, srvSocket, srvAddr, bufSlots) : def OnReadyForReading(self) : try : cliSocket, cliAddr = self._socket.accept() + if _IS_MICROPYTHON_LINUX: # TODO Resolve ports/unix dependency + # b'\x02\x00\x89L\x7f\x00\x00\x01' + address = ".".join([str(byte[0]) + for byte in struct.unpack('ssss', cliAddr[4:8])]) + port = struct.unpack('H', cliAddr[2:4])[0] + cliAddr = (address, port) except : return recvBufSlot = self._bufSlots.GetAvailableSlot() diff --git a/MicroWebSrv2/microWebSrv2.py b/MicroWebSrv2/microWebSrv2.py index 97fc6fc..b153d64 100644 --- a/MicroWebSrv2/microWebSrv2.py +++ b/MicroWebSrv2/microWebSrv2.py @@ -534,8 +534,8 @@ def OnLogging(self) : @OnLogging.setter def OnLogging(self, value) : - if type(value) is not type(lambda x:x) : - raise ValueError('"OnLogging" must be a function.') + #if type(value) is not type(lambda x:x) : + # raise ValueError('"OnLogging" must be a function.') self._onLogging = value # ============================================================================ diff --git a/MicroWebSrv2/mods/WebSockets.py b/MicroWebSrv2/mods/WebSockets.py index 4de0849..2bbb55e 100644 --- a/MicroWebSrv2/mods/WebSockets.py +++ b/MicroWebSrv2/mods/WebSockets.py @@ -75,8 +75,8 @@ def OnWebSocketAccepted(self) : @OnWebSocketAccepted.setter def OnWebSocketAccepted(self, value) : - if type(value) is not type(lambda x:x) : - raise ValueError('"OnWebSocketAccepted" must be a function.') + #if type(value) is not type(lambda x:x) : + # raise ValueError('"OnWebSocketAccepted" must be a function.') self._onWebSocketAccepted = value # ============================================================================ @@ -468,8 +468,8 @@ def OnClosed(self) : @OnClosed.setter def OnClosed(self, value) : - if type(value) is not type(lambda x:x) : - raise ValueError('"OnClosed" must be a function.') + #if type(value) is not type(lambda x:x) : + # raise ValueError('"OnClosed" must be a function.') self._onClosed = value # ============================================================================ diff --git a/MicroWebSrv2/webRoute.py b/MicroWebSrv2/webRoute.py index edbf894..8252ad0 100644 --- a/MicroWebSrv2/webRoute.py +++ b/MicroWebSrv2/webRoute.py @@ -28,8 +28,9 @@ def decorated(handler) : # ============================================================================ def RegisterRoute(handler, method, routePath, name=None) : - if type(handler) is not type(lambda x:x) : - raise ValueError('"handler" must be a function.') + # warning: Fails in MicroPython when using a closure. + #if type(handler) is not type(lambda x:x) : + # raise ValueError('"handler" must be a function.') if not isinstance(method, str) or len(method) == 0 : raise ValueError('"method" requires a not empty string.') if not isinstance(routePath, str) or len(routePath) == 0 : diff --git a/main.py b/main.py index bc792cc..e41ce09 100644 --- a/main.py +++ b/main.py @@ -163,8 +163,16 @@ def OnWSChatClosed(webSocket) : # For embedded MicroPython, use a very light configuration, mws2.SetEmbeddedConfig() -# All pages not found will be redirected to the home '/', -mws2.NotFoundURL = '/' +# mws2.RootPath = '/flash/www' # E.g., MicroPython +# Confirm that RootPath will resolve for home URL +HOME = '/' +if not mws2.ResolvePhysicalPath(HOME): + raise MicroWebSrv2Exception( + "RootPath '%s' does not resolve with URL '%s'" % (mws2.RootPath, HOME) + ) + +# All pages not found will be redirected to the home, +mws2.NotFoundURL = HOME # Starts the server as easily as possible in managed mode, mws2.StartManaged()