Nikola Otasevic Nikola Otasevic - 11 months ago 88
Python Question

How to serve pdf (or non-image) files through google app engine with python flask?

Here is the code that I currently use to upload the file that I get:

header = file.headers['Content-Type']
parsed_header = parse_options_header(header)
blob_key = parsed_header[1]['blob-key']
UserObject(file = blobstore.BlobKey(blob_key)).put()

On the serving side, I tried doing the same thing that I do for images. That is, I get the UserObject and then just do
photoUrl = images.get_serving_url(str(userObject.file))

To my surprise, this hack worked perfectly in local server. However, on production, it raises an exception:

File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/images/", line 1794, in get_serving_url
return rpc.get_result()
File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/", line 613, in get_result
return self.__get_result_hook(self)
File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/images/", line 1892, in get_serving_url_hook
raise _ToImagesError(e, readable_blob_key)

What is a proper way to store/serve non-image files such as pdf?

Answer Source

Don't use the images API, that won't work since (at least in production) it performs image content/format checks and you're not serving images.

You can have your own request path namespace with a handler, just like any other handler in your app, maybe something along these lines:

def pdfServingUrl(request, blob_key):
    from urlparse import urlsplit, urlunsplit
    split = list(urlsplit(request.url))
    split[2] = '/PDF?key=%s' % blob_key.urlsafe() # rewrite the url 'path'
    return urlunsplit(tuple(split))

And in the handler for /PDF*:

    blob_key = self.request.get('key')

If you use the Blobstore API on top of Google Cloud Storage you can also use direct GCS access paths, as mentioned in this answer:

Note: the above code is a barebone python suggestion just for accessing the blob data. Additional stuff like headers, etc. is not addressed.

Per @NicolaOtasevic's comment, for flask the more complete code might look like this:

blob_info = blobstore.get(request.args.get('key')) 
response = make_response( 
response.headers['Content-Type'] = blob_info.content_type 
response.headers['Content-Disposition'] = 'filename="%s"' % blob_info.filename