Thursday, January 22, 2009

Uploading a file.... from browser to a webapp, to another webapp ???

It's been six and a half months since I last do anything interesting, but here it is.

Problem: We have a RESTful web service, and a web application that talks to each other. We want to allow the user to upload a file, particularly binary, to the web app, and put that in the database in the REST server.
- We can't make a multipart request from the app to the REST server, it will come out as a string literal "".
- We can't call read on the UploadedStringIO and put that into the request from the app to the REST server. It will come out as gibberish string.
- We can't put the specific header that we want to do a multi-part request. I didn't even dare track that down.
- I was going to try make a raw HTTP request, saving the file in a temporary file on the app somewhere, but before I did that, I realized something...

IT'S ALL XML!

When we make requests to REST server using ActiveResource, the request body is marshaled into xml format. That means all your binary is going to mess up the xml document, and/or recognized as the string literal above.

What do I do?

A multi-part stream has a few important attributes: original_filename, content_type, and the file data we can get by calling read on the IO object. So I created a hash with these attribute names as keys (call the content whatever you like, make sure your REST server knows what it is and fetch it accordingly), and the value from the user's request as the value. I encode the binary content with the Base64.encode64. Now this hash can be marshaled easily. It's all ASCII text. So, go ahead and put that in your request parameters and fire away.

On the server side, handle the request as you would normally, except when you write the file to your storage, remind yourself that it is encoded by Base64.

Now comes the other way around, showing what we just uploaded. Again, it's all xml up until it reaches the app. So, fetch what you stored, make sure it goes out the REST server with the binary content encoded with Base64, and decode it at the app when you're ready to present it on screen.

This works pretty well with ActiveResource. A nice feature request to ActiveResource people would be to automate all this. :)