CherryPy is a minimalist web application server written in Python. Hundreds of people have relied on it for more than fourteen years now. Recently, I’ve gained interest in the native asynchronous support Python has gained through the implementation of PEP-3156 and PEP-0492. Basically, Python now supports coroutines natively and in a friendly API interface. In addition, thanks to the built-in asyncio module, you can naturally develop concurrent applications.
CherryPy has always used a multi-threaded engine to support concurrent web applications. This may sound surprising, but this is working very well still in 2016. Yet, I love a puzzle and I was interested in making CherryPy run as a set of coroutines rather than a bunch of threads. So a couple of days ago, I set myself on the task to make it happen.
And so here it is, CherryPy on asyncio!
This a branch on my fork, not an official part of the CherryPy project yet.
Now, you can run code like this:
import cherrypy import cherrypy.async class Root: @cherrypy.expose async def index(self): return "hello" @cherrypy.expose async def echo(self, msg): return msg if __name__ == '__main__': cherrypy.quickstart(Root())
The only differences are:
- importing cherrypy.async
- turning page-handlers into coroutines
That is all.
The idea is that the cherrypy.async patches all the internals of CherryPy for you and turn it into an async-aware server.
Note that the code currently runs only on Python 3.5+ as we use the async/await keywords.
Has it been easy?
Turning a project that was not designed for coroutines is not that complicated thanks to the simple interface provided by async/await. However, anytime an I/O operation is performed, it is necessary to transform a call like:
data = fp.readline()
to
data= await fp.readline()
This mundane line is sometimes is part of a function call, in that case, you have to overwrite and copy/paste the whole function to change that one line. Mind you, this can’t be avoided because you must also re-declare the function as a coroutine anyway by prefixing it with async.
As usual, the difficulty lies in the entanglement of your code. The more you made simple to comprehend, the simpler and faster it will be to change with confidence.
What was changed?
Mostly the HTTP server, the machinery of CherryPy: the internal engine/bus, the dispatcher, the request handling.
If we can find a way to re-organise the existing code, actually few lines would eventually be changed.
Note, I made the decision not to make this server WSGI aware because I find that rather counter-intuitive with an async-based server somehow.
Is it production ready?
Not at all. It hasn’t been really tested (this will require to re-write many tests so that they play along with coroutines).
It is also, for some unknown reason yet, much slower than the multithreaded version. Profiling will need to be performed.
Still, if you are feeling like testing it:
$ hg clone https://Lawouach@bitbucket.org/Lawouach/cherrypy $ cd cherrypy $ hg update async
I don’t know if this code will go further than this but, maybe this will interest the community enough so that it moves forward. This would make CherryPy more suitable for HTTP2 and websockets.