Nikola Otasevic Nikola Otasevic - 2 months ago 28
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/__init__.py", line 1794, in get_serving_url
return rpc.get_result()
File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", 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/__init__.py", line 1892, in get_serving_url_hook
raise _ToImagesError(e, readable_blob_key)
TransformationError


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

Answer

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')
    self.response.write(blobstore.BlobReader(blob_key).read())

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: http://stackoverflow.com/a/34023661/4495081.

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(blob_info.open().read()) 
response.headers['Content-Type'] = blob_info.content_type 
response.headers['Content-Disposition'] = 'filename="%s"' % blob_info.filename