Chapter 10 ■ http ServerS
175
In such cases, do consider the safety that a reverse proxy provides. To bring your web service to a halt, all
someone needs to do is to connect to your n-worker service with n sockets, offer a few initial desultory bytes of request
data, and then freeze. All of your workers will now be occupied waiting for a complete request that may never arrive.
With Apache or nginx in front of your service, by contrast, requests that take a long time to arrive—whether through
malice or because some of your clients run on mobile devices or are otherwise suffering low bandwidth—are slowly
collected by the buffers of the reverse proxy, which will typically not forward the request along to you until it has been
received in its entirety.
Of course, a proxy that collects full requests before forwarding them is no proof against a real denial-of-service
attack—nothing, alas, is—but it does prevent your dynamic language runtime from stalling when data from a client is
not yet forthcoming. It also insulates Python from many other kinds of pathological input, from megabyte-long header
names to entirely malformed requests, because Apache or nginx will reject these outright with 4xx errors without your
back-end application code even suspecting.
I currently gravitate toward three sweet spots on the spectrum of architectures in the previous list.
My default is Gunicorn behind nginx or, if a system administrator prefers, Apache.
If I am running a service that is really a pure API and does not involve any static components, then I will
sometimes attempt to run Gunicorn by itself or perhaps directly behind Varnish if I want even my dynamic resources
to benefit from its first-class caching logic.
It is only when architecting large web services that I go full-bore with three tiers: my Python running in Gunicorn,
behind nginx or Apache, behind either a local or geographically distributed Varnish cluster.
Many other configurations are, of course, possible, and I hope that the previous discussion included enough
caveats and trade-offs that you will be able to choose intelligently when the question comes up in your own projects
and organizations.
One important question that looms on the horizon is the emergence of Python runtimes like PyPy that can run
at machine speed. Once Python code can run as fast as Apache, why not have Python serve both static and dynamic
content? It will be interesting to see whether servers powered by fast Python runtimes create any competition for
old and reliable solutions like Apache and nginx. What incentives can Python servers offer for migration when the
industry favorites are so well documented, understood, and beloved by system administrators?
There are, of course, variations possible on any of the previous patterns. Gunicorn can run directly behind
Varnish, for example, if no static files need to be served or if you are happy to have Python pull them off of the disk
itself. Another option is to use nginx or Apache with their reverse-caching options turned on so that they provide
basic Varnish-like caching without the need for a third tier. Some sites experiment with alternative protocols for the
conversation between the front-end server and Python, like those supported by the Flup and uwsgi projects. The four
patterns featured in this section are merely among the most common. There are many other possible designs, most of
which are in use somewhere today.
Platforms as a Service
Many of the topics raised in the previous section—load balancing, multiple tiers of proxy server, and application
deployment—begin to veer in the direction of system administration and operations planning. Issues such as
the selection of a front-end load balancer or the choices involved in making an HTTP service physically and
geographically redundant are not unique to Python. If covered in this chapter, they would take you far afield from the
subject of Python network programming.
As you make Python part of your strategy for providing a network service, I encourage you also to read about
automated deployment, continuous integration, and high-performance scaling to learn about technologies that might
be applicable to your own service and organization. There is not enough space to cover them here.
But one topic does bear mentioning: the emergence of platform-as-a-service (PaaS) providers and the question
of how to package your applications for deployment on such services.