Skip to content
5 changes: 3 additions & 2 deletions MicroWebSrv2/httpRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

# ------------------------------------------------------------------------
Expand Down Expand Up @@ -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 )
Expand Down
2 changes: 1 addition & 1 deletion MicroWebSrv2/httpResponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down
73 changes: 59 additions & 14 deletions MicroWebSrv2/libs/XAsyncSockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 = [ ]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.')
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions MicroWebSrv2/microWebSrv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

# ============================================================================
Expand Down
8 changes: 4 additions & 4 deletions MicroWebSrv2/mods/WebSockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

# ============================================================================
Expand Down Expand Up @@ -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

# ============================================================================
Expand Down
5 changes: 3 additions & 2 deletions MicroWebSrv2/webRoute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down
12 changes: 10 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down