Search code examples
pythongzipbottle

Bottle/Python - "IOError: [Errno 9] read() on write-only GzipFile object" when trying to decompress request body


I use Bottle to receive gzipped request body. When the size of the request is small, everything is fine. However, if the size of request body is a bit larger (say, >= 20kb), an IOError is thrown.

The following is the code used to read and decompress the request body:

@post("/receive")
def receive():    
    try:
        f = request.body
        g_f = gzip.GzipFile(fileobj=f)
        content = g_f.read()
        # other stuff on the content...
    except IOError, e:  
        # handle the error

The error message is:

[2015-09-07 16:27:27,967][ERROR   ] Failed to decompress gzipped data: [Errno 9] read() on write-only GzipFile object
127.0.0.1 - - [07/Sep/2015 16:27:27] "POST /receive HTTP/1.1" 400 756

Solution

  • This problem is caused by the inheritance of the read/write mode of the fileobj used to create the GzipFile object.

    If the size of the request.body is less than 20k, Bottle loads the whole binary data as a StringIO object. GzipFile deals with StringIO very well, everything works fine.

    On the other hand, if the size of the request.body is larger than 20k, Bottle will use tempfile module to create a temporary file for this request body, for platform-consistency, the default mode of files created by tempfile is 'w+b'.

    However, GzipFile determines if a file readable or not only by the mode string obtained by hasattr(fileobj, 'mode'), if this string is something like 'rxx', GzipFile considers it as readable, and vice versa. If the read() function of a non-readable GzipFile is invoked, an IOError will be thrown.

    Since the mode of the file which will be used by the GzipFile is 'w+b', the GzipFile is still going to consider it as 'non-readable', so, boom! Error thrown. To fix this, just add the mode argument when creating the GzipFile object:

    try:
        f = request.body
        g_f = gzip.GzipFile(fileobj=f, mode='rb')
        content = g_f.read()
        # other stuff on the content...
    except IOError, e:  
        # handle the error