ws: tiny asynchronous webserver framework without uasyncio

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
bitninja
Posts: 165
Joined: Thu Sep 15, 2016 4:09 pm
Location: Spring, Texas

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by bitninja » Thu Nov 16, 2017 6:04 am

Wow, you really spent some effort to prove your point. :)

I guess it depends on your use-case for your server. You are dealing with tiny (scripted) requests and (simple) responses... is that what your end goal will look like?

If you are aiming for a more general purpose framework, then I would do more realistic testing... starting with just serving static content. I know you have your webrepl.html example, but I am thinking of a more typical multi-threaded browser request loading a page with style sheets, script files and images, etc...

Using PicoWeb, doing just do this, it does struggle with some pages (on an ESP8266). That said, it actually handles the multiple incoming request pretty well.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by pythoncoder » Thu Nov 16, 2017 12:11 pm

jacob019 wrote:
Sun Nov 05, 2017 9:46 pm
... I am going to prove that it is possible to use MicroPython without uasyncio for non-trivial asynchronous applications.
It's always possible to rewrite code based on cooperative multi-tasking in a way which doesn't use it. A library like uasyncio facilitates an event-driven programming paradigm but fundamentally the application is a state machine(SM) which interacts with hardware. Cooperative multi tasking provides a way of writing that single SM as a number of separate coroutines, each being its own simple SM. The same program logic may be achieved by explicitly coding a single SM; but for non trivial applications it can rapidly become revoltingly complicated and hard to maintain.

The uasyncio library, with all due respect to the programming skills of @pfalcon, is Python not thaumaturgy ;)

The question is whether there is any gain in code readability or performance in avoiding uasyncio. I'd be astonished if a web application yielded performance gains but it would be interesting to see practical evidence based on a true like-for-like comparison.
Peter Hinch
Index to my micropython libraries.

jacob019
Posts: 13
Joined: Tue Oct 03, 2017 6:14 pm

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by jacob019 » Thu Nov 16, 2017 1:12 pm

bitninja wrote:
Thu Nov 16, 2017 6:04 am
I know you have your webrepl.html example, but I am thinking of a more typical multi-threaded browser request loading a page with style sheets, script files and images, etc...
I have done this and I am seeing similar results, 5-7X performance difference. I'll post some test results at a later date, but even in production static content can be compiled to a single compressed document it that's what it takes to offer a great user experience.
pythoncoder wrote:
Thu Nov 16, 2017 12:11 pm
I'd be astonished if a web application yielded performance gains but it would be interesting to see practical evidence based on a true like-for-like comparison.
I agree that it would be nice to stick with uasyncio for maintainability reasons. I have used twisted, so I know how important that is. I have provided two examples showing a large performance difference and I'm confident that we'll see the same difference as the project scales. The problem here is the implementation of select, and as much as I would like to dig in and find out why we are getting such poor performance from select, it is not something I'm prepared for. select.poll needs to be in a tight loop of native code, otherwise the performance tanks and it waits around all day on the select statement even when the TCP buffer is full. Also, it is MUCH faster to buffer 536 bytes at a time before writing to the socket, otherwise subsequent writes will result in short writes until the buffer drains. These are platform specific performance quirks and might only apply to the ESP8266. But the performance difference is real.

I do prefer to use the standard tools, but the users of my product won't care how standards compliant or maintainable the code is. Faster is better. My application requires both an HTTP server and an MQTT client. I'm going to try using the same select loop for both. I wish we could have frozen native code.

bitninja
Posts: 165
Joined: Thu Sep 15, 2016 4:09 pm
Location: Spring, Texas

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by bitninja » Fri Nov 17, 2017 4:24 am

I'm going to admit that I am lost by the last two posts... :oops: I'm new to Python in general and not very strong in the http protocol itself. Jacob, can you generate a simple script using ws that would serve the contents of a path that I pass in? That way I could test it for my needs. Here is the PicoWeb app I currently use...

Code: Select all

#
# Basic PicoWeb application for serving up content in a sub-folder
#
import ure as re
import picoweb

# sub-folder we will serve conent from
rootdir = "wwwroot/"

# handle the root URL
def index(req, resp):
    yield from app.sendfile(resp, rootdir + "index.html")

# handle all other resource (file) requests
def file(req, resp):
    fname = req.url_match.group(1)
    yield from app.sendfile(resp, rootdir + fname)

# define the picoweb routing table
ROUTES = [
    ("/", index),
    (re.compile("^/(.+)"), file),
]

# setup the logging that is used by picoweb
import logging
logging.basicConfig(level=logging.DEBUG)

# run the server
app = picoweb.WebApp(None, ROUTES)
app.run(host="0.0.0.0", port=80, debug=True)
Hopefully it should be trivial (for you).

Thanks.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Slow performance. But is uasyncio the culprit?

Post by pythoncoder » Fri Nov 17, 2017 10:35 am

@jacob19 My interest in uasyncio is primarily in hardware interfacing rather than web applications; I have benchmarked and tested it fairly extensively and it is highly efficient. On the Pyboard it can task switch in 288μs. On an ESP8266 it takes 2.2ms. I'm sure you'll agree that in the context of web applications that's fast. It's possible that the uasyncio IORead/IOWrite mechanism may be the culprit but from your description it sounds as if poll/select is the actual cause. So discarding uasyncio as a workround for a performance issue with poll/select sounds rather like throwing away the baby with the bathwater ;)

If you could devise a minimal test case demonstrating the problem with select and raise an issue I'm sure the maintainers would address it, which would benefit us all. It might even be a quicker solution to completing your application than rewriting the webserver.
Peter Hinch
Index to my micropython libraries.

jacob019
Posts: 13
Joined: Tue Oct 03, 2017 6:14 pm

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by jacob019 » Fri Nov 17, 2017 3:54 pm

bitninja wrote:
Fri Nov 17, 2017 4:24 am
can you generate a simple script using ws that would serve the contents of a path that I pass in?

Code: Select all

import ws
import os
rootdir = '/wwwroot'
ws.routefile('/',rootdir+'/index.html')
for file in os.listdir(rootdir):
    ws.routefile('/'+file,rootdir+'/'+file)
ws.serve()
pythoncoder wrote:
Fri Nov 17, 2017 10:35 am
It's possible that the uasyncio IORead/IOWrite mechanism may be the culprit but from your description it sounds as if poll/select is the actual cause.
I'm certain that the culprit is select. If select.poll is called from within a tight loop of native code then it runs fast, and prebuffering writes into chunks before passing them to the socket reduces iteration to make it even faster. uasyncio cannot not take advantage of these optimizations. I can think of several workarounds, but writing a simple webserver is easier.
pythoncoder wrote:
Fri Nov 17, 2017 10:35 am
If you could devise a minimal test case demonstrating the problem with select and raise an issue I'm sure the maintainers would address it, which would benefit us all. It might even be a quicker solution to completing your application than rewriting the webserver.
I agree 100% that the best solution is to improve the performance of select. It looks like uselect support was only enabled on the ESP8266 in December 2016. https://github.com/micropython/micropython/pull/2411. Someone made a platform specific implementation, but it was abandoned when moduselect.c was generalized for use across all ports. I will do as you suggest.

bitninja
Posts: 165
Joined: Thu Sep 15, 2016 4:09 pm
Location: Spring, Texas

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by bitninja » Fri Nov 17, 2017 5:26 pm

Thanks for the test code. Unfortunately I could only get it to serve the root level files and none of the ones in sub-directories like the style sheet and the image. Even when I build the routes for those files...

Code: Select all

import ws
import os
rootdir = '/wwwroot'
ws.routefile('/',rootdir+'/index.html')

def addfiles(path):
    for file in os.ilistdir(path):
        if file[1] == 16384:
            addfiles(path + '/' + file[0])
        else:
            rpath = path + '/' + file[0]
            rootlen = len(rootdir) + 1
            vpath = rpath[rootlen:]
            ws.routefile(vpath, rpath)

addfiles(rootdir)
ws.serve()
Anyway, it looks like you are narrowing down the suspects on where the bottleneck is. So that's good.

jacob019
Posts: 13
Joined: Tue Oct 03, 2017 6:14 pm

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by jacob019 » Fri Nov 17, 2017 5:35 pm

@bitninja, The paths require '/' to be prepended. So adjust your code like this:
ws.routefile('/'+vpath, rpath)

bitninja
Posts: 165
Joined: Thu Sep 15, 2016 4:09 pm
Location: Spring, Texas

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by bitninja » Fri Nov 17, 2017 6:12 pm

OK, that fixed it.

Well after my testing with Chrome and Edge (I'm on Windows) I still don't see the performance gains between the two, so I'm not sure what to think. I'm using the profiling tool in Chrome and the times are pretty consistent...

Here is the capture of ws loading the webrepl.html...
ws.png
ws.png (25.98 KiB) Viewed 7381 times

jacob019
Posts: 13
Joined: Tue Oct 03, 2017 6:14 pm

Re: ws: tiny asynchronous webserver framework without uasyncio

Post by jacob019 » Fri Nov 17, 2017 6:21 pm

I appreciate the testing.
15 seconds is what I'm seeing with picoweb, or without native code.
I'm getting a consistent 2 second load time for the single file webrepl with ws. In both Chrome and Firefox on Linux.
Here's my screen capture of it loading in chrome in <2s:
http://jacobstoner.com/screenshot.jpg

I am using MicroPython v1.9.3-7 on the ESP8266 in STA mode. This is after a fresh boot and running these commands:

Code: Select all

import ws
ws.routefile('/webrepl.html','/wwwroot/webrepl.html')
ws.serve()
I tested loading a page with several resources totaling ~200kb, it only took a few seconds.

I would really like to figure out why you are not getting the same results:
1. Are you on the ESP8266?
2. Is the device connected to the network in STA mode?

Post Reply