jczic / MicroWebSrv2

The last Micro Web Server for IoTs (MicroPython) or large servers (CPython), that supports WebSockets, routes, template engine and with really optimized architecture (mem allocations, async I/Os). Ready for ESP32, STM32 on Pyboard, Pycom's chipsets (WiPy, LoPy, ...). Robust, efficient and documented!
https://github.com/jczic/MicroWebSrv2
MIT License
662 stars 97 forks source link

"No response was sent from route" error always raised #58

Open cathaychris opened 3 years ago

cathaychris commented 3 years ago

The error

MWS2-DEBUG> From 192.168.86.48:61468 GET /test-post >> [200] OK
MWS2-WARNING> No response was sent from route TestPost1/2.
MWS2-DEBUG> From 192.168.86.48:61539   >> [501] Not Implemented

is raised for any route response, for example from the /test-post route in the example server, which calls request.Response.ReturnOk. But this also applies to anything that eventually calls request.Response.Return.

It can be reproduced by running the example server from main.py and navigating to http://localhost/test-post, and observing the server logs.

This supposedly arises from the absence of headers, but ReturnOk generates and transmits headers itself. My browser receives the headers and the content OK. Furthermore, if I add a response-finished callback set under request.Response.OnSent, I can confirm the response is completed successfully (including headers).

Indeed, querying request.Response._hdrSent returns False at any point, and request.Response._headers also returns {}, including after a successfully finished response (querying from within the @WebRoute callback function).

I can remove the error by duplicating the Return call:

request.Response.ReturnOk(content)
request.Response.ReturnOk(content)

What is especially odd is, if I query the HttpResponse object before and after sending a response

    @WebRoute(GET, '/test', name="test")
    def _handler(server, request):
        print('request:', request)
        print('response:', request.Response)
        request.Response.ReturnOkJSON('test')
        print('request:', request)
        print('response:', request.Response) # different memory address than before!

the response object has been recreated at a new spot in memory. This does not make sense to me, since the HttpResponse object is instantiated as part of the HttpRequest object, which in fact does not change throughout the handler.

My connection is currently quite slow, maybe this could be caused by a timeout issue? But based on the observations above, it seems more fundamental.

cathaychris commented 3 years ago

Update: this happens on CPython as well as micropython ports. I have tracked it down to the following and propose a fix, although I do not yet fully understand whether this is a bug or partially intentional.

The problem arises in _onDataSent in httpResponse, specifically:

if self._keepAlive :
    self._request._waitForRecvRequest()

where in _waitForRecvRequest() a new Response object is created (attached to the same Request object) and old headers are flushed. This could be OK, except that in the httpResponse function Return (for example, although this appears in many places) the last line checks self._hdrSent = True, where self is the original Response object.

However, when the _routeRequest checks if headers have been sent later,

            if not self._response.HeadersSent :
                self._mws2.Log( 'No response was sent from route %s.'
                                % self._routeResult,
                                self._mws2.WARNING )
                self._response.ReturnNotImplemented()

it pulls the new response object, on which the _hdrSet property has not been set. This is why different memory addresses were observed above.

This can be fixed by, for example, replacing

self._hdrSent = True

with

self.Request.Response._hdrSent = True

(in multiple locations) which enforces that the most recent Response object is used. A cleaner way would perhaps be to attach the _hdrSent property to the Request object, since this is less mutable, and the code is written such that the response object is highly subject to change.

brianwyld commented 3 years ago

I also have this issue. Althought the client end receives its data, it also seems to cause a 501 error for following request from certain browsers (actually probably if the browser is reusing the connection I think)

I went for the simple and stupid fix, and commented out the check in _routeRequest() completely, and it now no longer gives the error nor causes an issue with subsequent requests.

If there is a better (less hacky) fix then I'm in the market....

However, the project in general is fab and works well for my test setup! (PyCom FiPy test stub for as yet unavailable hw!)

thanks Brian

tf131 commented 3 years ago

I have the same issue on ESP32 with micropython. Besides this, very enjoyable project! Thank you

MWS2-DEBUG> From 192.168.4.2:50851 POST /processor >> [200] OK MWS2-WARNING> No response was sent from route processor. MWS2-DEBUG> From 192.168.4.2:50851 >> [501] Not Implemented

pluess commented 3 years ago

Same here with the latest code as of 2021-05-17.