Handling file uploads in App Engine
Posted by Nick Johnson | Filed under coding, app-engine, cookbook, tech
This is the ninth in a series of 'Cookbook' posts describing useful strategies and functionality for writing better App Engine applications.One issue that comes up frequently on the App Engine groups is how to handle file uploads from users. Part of the confusion arises from the fact that App Engine apps cannot write to the local filesystem. Fortunately, the Datastore comes to the rescue: We can easily write an app that accepts file uploads and stores them in the datastore, then serves them back to users.
To start, we need an HTML form users can upload files through:
<html>
<head>
<title>File Upload</title>
</head>
<body>
<form method="post" action="/">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>
We'll also need a datastore model we can store the uploaded file in:
class DatastoreFile(db.Model):
data = db.BlobProperty(required=True)
mimetype = db.StringProperty(required=True)
And finally, a Request Handler that can handle the uploads:
class UploadHandler(webapp.RequestHandler):
def get(self):
self.response.out.write(template.render("upload.html", {}))
def post(self):
file = self.request.POST['file']
entity = DatastoreFile(data=file.value, mimetype=file.type)
entity.put()
file_url = "http://%s/%d/%s" % (self.request.host, entity.key().id(), file.name)
self.response.out.write("Your uploaded file is now available at %s" % (file_url,))
Note that we access the uploaded file using self.request.POST rather than self.request.get(); using self.request.get("file") would only give us the actual data of the uploaded file, whilst self.request.POST["file"] returns a cgi.FieldStorage object, which gives us access to the file data, filename, and mimetype. We take care to store the mimetype in addition to the original file data so we can make sure we serve it back up with the correct mimetype.
Serving the uploaded file from the datastore is, of course, also easy:
class DownloadHandler(webapp.RequestHandler):
def get(self, id, filename):
entity = DatastoreFile.get_by_id(int(id))
self.response.headers['Content-Type'] = entity.mimetype
self.response.out.write(entity.data)
Our handler here takes a filename in addition to the ID; the filename is purely for convenience, to make the URLs we provide more friendly. All we care about is the ID, which we use to find the file object in the datastore.
Finally, here's the webapp to glue all this together:
application = webapp.WSGIApplication([
('/', UploadHandler),
('/(\d+)/(.*)', DownloadHandler),
])
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
Next, we'll be taking a break from the cookbook series, and working through the steps of building a blogging system on App Engine.
Previous Post Next Post