belyalov / tinyweb

Simple and lightweight HTTP async server for micropython
MIT License
247 stars 40 forks source link

Force push response content out? #28

Closed UnexpectedMaker closed 4 years ago

UnexpectedMaker commented 4 years ago

Is there a way I can force tinyweb to push out the response content before the end of the function is reached?

If I have this code. html is declared above this snippet.

await response.start_html()
st_str = '<h2>Credentials saved!</h2><br>You can now close this page...'
html = html.format(self.web_title, st_str)
await response.send(html)
await asyncio.sleep(5)
self.stop( True )

The html is never served to the user because stop() stops the web server, and in all of my tests, the page is never sent, until the end of the function is reached, even if I give it a 5 second sleep.

even if I just do this...

await response.start_html()
st_str = '<h2>Credentials saved!</h2><br>You can now close this page...'
html = html.format(self.web_title, st_str)
await response.send(html)
await asyncio.sleep(30)

The page doesn't get sent for 30 seconds.

So I'm looking for something like a "response.flush()" or similar that will just push the data immediately.

BTW, I can't see anything in the code that looks like it's waiting for any specific thing before writing the data, but I can repro this behaviour quite easily. That said, it might be my own implementation causing this, but I have nothing blocking the event_loop that I can find.

jimmo commented 4 years ago

I looked at this from the MicroPython uasyncio side. Using the tinyweb's examples/hello_world.py example, but with some sleeps shows the behavior @UnexpectedMaker describes, however if you connect manually (e.g. via telnet) you can see that the data is flushed immediately. This is just how browsers work. It seems that you need to get past some minimum number of bytes in the response before the browser will start rendering the page, e.g. this works fine:

@app.route('/')
async def index(request, response):
    # Start HTTP response with content-type text/html
    await response.start_html()
    # Send actual HTML page
    await response.send('<html><body><h1>Hello</h1>\n')
    await response.send(' ' * 1024)   # <-- Make the browser start rendering.
    for _ in range(5):
      await uasyncio.sleep(5)
      await response.send('<h2>hello</h2>\n')
    await response.send('</html>\n')

FYI @belyalov , MicroPython is going to release a new version of uasyncio soon. See https://github.com/micropython/micropython/pull/5332 Unfortunately this will break TinyWeb due to some internal dependencies it has on (for example uasyncio.IORead ) For the most part, moving to uasyncio.start_server will solve this.

belyalov commented 4 years ago

@UnexpectedMaker: @jimmo is right - this is how browser works.

Usually, to force browser to show/process received data immediately either chunks or close connection could be used.

So you can try to switch your handler to be generator (to force tinyweb to use Transfer-Encoding: chunked), like this:

async def something():
    yield '<h2>Credentials saved!</h2><br>You can now close this page...'
    yield html.format(self.web_title, st_str)
    # ...

test: https://github.com/belyalov/tinyweb/blob/master/test/test_server.py#L283 implementation: https://github.com/belyalov/tinyweb/blob/master/tinyweb/server.py#L327

belyalov commented 4 years ago

FYI @belyalov , MicroPython is going to release a new version of uasyncio soon. See micropython/micropython#5332 Unfortunately this will break TinyWeb due to some internal dependencies it has on (for example uasyncio.IORead ) For the most part, moving to uasyncio.start_server will solve this.

This is actually great - I'll adapt tinyweb once PR merged. Thanks

UnexpectedMaker commented 4 years ago

Yeah, new asyncio looks promising and much easier for noobs like me to grok! Thanks for your help on this issue @belyalov and @jimmo - working great now, so closing it!