Python WSGI Middleware for automatic Gzipping

I've just started learning Python WSGI (PEP-333) and thought the best way to learn would be to write some WSGI tools myself. Most recently, I chose to write a middleware application that converts all output into valid gzipped data. In this article, I will be demonstrating how my middleware gzipper works and how to implement it.

Gzipping arbitrary data

The first obstacle was that Python has no simple way of converting a normal string into a gzipped string. To do this, let's build off of the gzip.GzipFile object, which isn't really designed for this sort of task.

#!/usr/bin/python2.5
from gzip import GzipFile
import StringIO
 
def gzip_string(string, compression_level):
    """ The `gzip` module didn't provide a way to gzip just a string.
        Had to hack together this. I know, it isn't pretty.
    """
    fake_file = StringIO.StringIO()
    gz_file = GzipFile(None, 'wb', compression_level, fileobj=fake_file)
    gz_file.write(string)
    gz_file.close()
    return fake_file.getvalue()

The above function accomplishes this nicely. By using a `StringIO` instance for the `fileobj` in the `GzipFile`, it allows us to make the function think that it is writing to a normal file. Instead, we're grabbing the content through our fake file.

Does the client want gzipped output?

This is a pretty important task. What if the client didn't even ask for gzipped output? Or worse, what if they can't even support it? Cases like these must be taken into consideration. We'll use the following code to check.

def parse_encoding_header(header):
    """ Break up the `HTTP_ACCEPT_ENCODING` header into a dict of
        the form, {'encoding-name':qvalue}.
    """
    encodings = {'identity':1.0}
 
    for encoding in header.split(","):
        if(encoding.find(";") > -1):
            encoding, qvalue = encoding.split(";")
            encoding = encoding.strip()
            qvalue = qvalue.split('=', 1)[1]
            if(qvalue != ""):
                encodings[encoding] = float(qvalue)
            else:
                encodings[encoding] = 1
        else:
            encodings[encoding] = 1
    return encodings
 
def client_wants_gzip(accept_encoding_header):
    """ Check to see if the client can accept gzipped output, and whether
        or not it is even the preferred method. If `identity` is higher, then
        no gzipping should occur.
    """
    encodings = parse_encoding_header(accept_encoding_header)
 
    # Do the actual comparisons
    if('gzip' in encodings):
        return encodings['gzip'] >= encodings['identity']
 
    elif('*' in encodings):
        return encodings['*'] >= encodings['identity']
 
    else:
        return False

What this does is parse the `HTTP_ACCEPT_ENCODING` response header to make sure that it says that gzipping is alright and preferred over no compression. It does this by looking at the qvalue weights for each type of encoding. For more information on this, I suggest you read RFC 2616, section 14 which covers the "Accept-Encoding" header.

Creating the WSGI middleware

Alright, now it's time to use that function an create a middleware application.

class Gzipper(object):
    """ WSGI middleware to wrap around and gzip all output.
        This automatically adds the content-encoding header.
    """
    def __init__(self, app, compresslevel=6):
        self.app = app
        self.compresslevel = compresslevel
 
    def __call__(self, environ, start_response):
        """ Do the actual work. If the host doesn't support gzip as a proper encoding,
            then simply pass over to the next app on the wsgi stack.
        """
        accept_encoding_header = environ.get("HTTP_ACCEPT_ENCODING", "")
        if(not client_wants_gzip(accept_encoding_header)):
                return self.app(environ, start_response)
 
        def _start_response(status, headers, *args, **kwargs):
            """ Wrapper around the original `start_response` function.
                The sole purpose being to add the proper headers automatically.
            """
            headers.append(("Content-Encoding", "gzip"))
            headers.append(("Vary", "Accept-Encoding"))
            return start_response(status, headers, *args, **kwargs)
 
        # Unfortunately, we can't gzip data chunk-by-chunk, so we need to join up whatever
        # is being sent out first. (Remember, WSGI apps return items which can be iterated.)
        data = "".join(self.app(environ, _start_response))
        return [gzip_string(data, self.compresslevel)]

Before doing any encoding, this checks to make sure the client can even support gzipped output by looking at the "HTTP_ACCEPT_ENCODING" header value. What's really cool about how this works is that it adds a content-encoding header for you. This, however, shows a weakness in WSGI as the middleware is required to write a new start_response function that wraps around the old one. WSGI 2.0 clears this up, but this version isn't in use yet.

Using the middleware

Now, let's see how we would implement this with a simple "hello world" WSGI application.

#!/usr/bin/python2.5
from gzipper import Gzipper
from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
 
#
# Let's create a simple WSGI application to test out the Gzipper.
#
def test_app(environ, start_response):
    status = "200 OK"
    headers = [("content-type", "text/html")]
    start_response(status, headers)
 
    return ["Hello world!"]
 
# Set up the WSGI server and add the middleware that wraps around
# the actual application.
httpd = WSGIServer(('', 8080), WSGIRequestHandler)
httpd.set_app(Gzipper(test_app, compresslevel=8))
httpd.serve_forever()

Final thoughts

Middleware can be incredibly useful. I, however, have been seeing numerous people approaching it incorrectly by modifying the environ and having the application relying on it. I believe that PJ Eby said it best: "If your application requires that API to be present, then it's not middleware any more!" It would be better to just add that code to a library utilized by the application.

To view all of the code in one block, go to the next page.

Pages: 1 2

 

 

5 Comments

  1. japherwocky wrote,

    I think that's exactly the proper usage of StringIO. Not a hack at all. :)

  2. Evan wrote,

    Heh, yeah I guess you're right. It just looks messy to me. :-P

  3. Jim wrote,

    Two bugs:

    > if(environ.get("HTTP_ACCEPT_ENCODING", "").find("gzip") < 0)

    This fails for cases like Accept-Encoding: identity, compress;q=0.5, gzip;q=0

    You need to actually split up the header value properly in order to handle it correctly, a simple search fails for corner cases.

    You also need to transmit a Vary header.

  4. Evan wrote,

    Jim, thank you for your input. I have updated the code accordingly.

  5. Sergey wrote,

    In the first for loop, you could do

    encoding, sep, qvalue = encoding.partition(';')
    qvalue = qvalue.partition('=')[2]
    qvalue = 1 if not qvalue else float(qvalue)
    encodings[encoding] = qvalue

    The advantage here is that the code is linear. The disadvantage is that a later Python version is required.

Leave a comment

Take Albuterol Cheap
Inhaler Buying Albuterol
Drug Buying Albuterol
Order Cheapest Albuterol
Order Albuterol Free Delivery
Buying Without Prescription Aldactone
Buy Generic Aldactone
Order Aldactone Next Day Delivery
Purchase Aldactone Online no Prescription
Buy Tablets Aldactone
Take no Prescription Allopurinol
Purchase Allopurinol Drug
When to Take Allopurinol
Order Allopurinol Free Delivery
Pills Buying Allopurinol
Drug Buying Anafranil
Alternative Purchase Anafranil
Purchase Anafranil Drug
Buy Tablets Anafranil Online
Order no Prescription Anafranil
Pills Buying Atarax
Cheapest Order Atarax
Buying Without a Prescription Atarax Online
Buy Atarax Medication
Atarax 50
Avodart Finasteride
Take Avodart Legally
Purchase Avodart by Phone
Tablets Purchase Avodart
Why Take Avodart
Order Bactrim Generic
Buy Without a Prescription Bactrim
Take Bactrim Next Day Delivery
Buying Bactrim Overnight Delivery
Take Bactrim Medication
Why Take Celecoxib
Order Celecoxib Free Delivery
Order Celecoxib Next Day Delivery
Buy Cheap Celecoxib
Purchase no Prescription Celecoxib
Cheapest Buying Cipro
Order Cipro Free Delivery
Buy Without a Prescription Cipro
Buy Cipro Tablets
Purchase Cipro Free Delivery
Order Clomid Serophene Without a Prescription
Order Clomid Serophene Medication
Online Order Clomid Serophene Without Prescription
Buy Clomid Serophene Medication
Purchase Cheap Clomid Serophene
Buy Dapsone Without a Prescription
Alternative Order Dapsone
Buy Dapsone by Phone
Buy Dapsone Overnight Delivery
Buy Dapsone Tablets
Buy Desyrel Online no Prescription
Purchase Desyrel Next Day Delivery
Order Desyrel Free Delivery
Buy Desyrel Next Day Delivery
Purchase Cheapest Desyrel
Take Diflucan Medication
Purchase Diflucan Free Delivery
Tablets Buying Diflucan
Buy Diflucan Pills
Order Cheap Diflucan
Alternative Purchase Dilantin
Buying Without a Prescription Dilantin Online
Buy no Prescription Dilantin Online
Purchase no Prescription Dilantin
Cheapest Order Dilantin
Buy Tablets Diovan
Buy Diovan Free Delivery
Purchase Diovan Generic
Buying Pills Diovan
Buying Without a Prescription Diovan Online
Generic Order Doxycycline
Buy Tablets Doxycycline
Buy Doxycycline Overnight Delivery
Buy Tablets Doxycycline Online
Purchase Pills Doxycycline
Drug Order Elavil
Buy Elavil Overnight Delivery
Buy Elavil Without a Prescription
Take Elavil Legally
Buy Alternative Elavil
Purchase Online Without a Prescription Erythromycin
Purchase Erythromycin Without a Prescription
Generic Order Erythromycin
Pills Buying Erythromycin
Purchase Cheapest Erythromycin
Take Estrace Free Delivery
Pills Buying Estrace
Generic Buying Estrace
Purchase Online no Prescription Estrace
Order Estrace by Phone
Buy Furosemide Medication
Generic Buying Furosemide
Buying Furosemide On-Line
Buy Without a Prescription Furosemide
Take Furosemide Drug
Take Glucophage Next Day Delivery
Tablets Purchase Glucophage
Order Pills Glucophage
Order Glucophage Generic
Order Cheapest Glucophage
Purchase Imitrex Online no Prescription
Take Imitrex Without Prescription
Order Imitrex Medication
Buy Cheap Imitrex
Online Order Imitrex Without Prescription
Buy no Prescription Inderal
Pills Buying Inderal
Order Inderal Without a Prescription
Buying Inderal On-Line
Order Inderal Medication
Lasix 40
Buying Lasix On-Line
Buy Lasix no Prescription
Order Online Without a Prescription Lasix
Buy Lasix Generic
Take Levaquin Drug
Order Levaquin Pills
Buy Tablets Levaquin
Buying Without Prescription Levaquin
Purchase Cheapest Levaquin
Take Lexapro Daily Dose
Order Pills Lexapro
Take Lexapro Legally
Take Lexapro Drug
Purchase Generic Lexapro
Order Lipitor COD
Buy Pills Lipitor
Purchase Online Without a Prescription Lipitor
Lipitor Lipidor
Buy Cheapest Lipitor
Purchase Generic Lisinopril
Buy Lisinopril Tablets
Generic Buying Lisinopril
Purchase Lisinopril Online Without Prescription
Purchase Lisinopril Medication
Purchase Methotrexate Medication
Take Methotrexate Pills
Order Methotrexate Generic
Order Methotrexate Without a Prescription
Buy Tablets Methotrexate Online
Order Nizoral Pills
Order Nizoral Free Delivery
Order Nizoral no Prescription
Pills Buying Nizoral
Buy no Prescription Nizoral Online
Buy Nolvadex Cheapest
Alternative Order Nolvadex
Buying Nolvadex Free Delivery
Purchase Nolvadex COD
Alternative Purchase Nolvadex
Online Order Nortriptyline Without Prescription
Buying Without Prescription Nortriptyline
Purchase no Prescription Nortriptyline
Purchase Nortriptyline Online Without Prescription
Purchase Nortriptyline by Phone
Alternative Purchase Ortho Tri-Cyclen
Buy Ortho Tri-Cyclen Medication
Buying Ortho Tri-Cyclen Next Day Delivery
Online Buy Ortho Tri-Cyclen Without a Prescription
Purchase Ortho Tri-Cyclen Generic
Buy Tablets Premarin
Order Premarin Generic
Order Premarin Free Delivery
Purchase Pills Premarin
Purchase Premarin Next Day Delivery
When to Take Proscar
Order Proscar Free Delivery
Take Proscar Free Delivery
Cheapest Buy Proscar
Purchase Proscar Next Day Delivery
Online Order Risperdal Without Prescription
Buy Risperdal Without a Prescription
Generic Order Risperdal
Buying Risperdal Next Day Delivery
Buy Online Without a Prescription Risperdal
Buying Synthroid Without Prescription
Alternative Buying Synthroid
Order Synthroid by Phone
Buy Alternative Synthroid
Buy Online Without a Prescription Synthroid
Order Topamax Pills
Tablets Order Topamax
Order Cheap Topamax
Order Topamax Generic
Take Topamax Daily Dose
Purchase Valtrex COD
Buy no Prescription Valtrex
Buy Pills Valtrex Online
Tablets Buy Valtrex
Buy Valtrex COD
Legally Order Zithromax
Tablets Order Zithromax
Buy Without a Prescription Zithromax
Buying Without a Prescription Zithromax Online
Purchase Zithromax Without a Prescription