Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt to ports/unix MicroPython using select.poll #49

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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