2

I'm trying to POST some JSON and a binary file from an iPhone to a Django server running django-piston using ASIHTTPRequest

I know how to get it to work if I am ONLY sending JSON strings, and I know how to make it work if I am ONLY sending a file, but doing both is tricky.

So we'll start with ASIHTTPRequest code

ASIFormDataRequest *request = [[ASIFormDataRequest alloc] initWithURL:url];

[request setRequestMethod:@"POST"];

[request setPostFormat:ASIMultipartFormDataPostFormat];

[request appendPostData:[@"{\"save\":{\"name\":\"iostest\"}}" dataUsingEncoding:NSUTF8StringEncoding]];

[request addData:UIImageJPEGRepresentation([UIImage imageNamed:@"test.jpg"], 1.0f)
    withFileName:@"test.jpg"
  andContentType:@"image/jpeg"
          forKey:@"data"];

[request setDelegate:self];

[request startAsynchronous];

My best idea here is that adding raw string data directly to the POST body and then adding a file just doesn't work.

But if I instead try

[request setPostValue:@"{\"name\":\"iostest\"}" forKey:@"save"];

Then the piston data dictionary will store ['save'] as a string instead of a deserialized object, so it will literally deliver the string

"{\"name\":\"iostest\"}"

Here's my Piston handler code

def create(self, request):

     data = request.data

     print(data['save']) #{\"name\":\"iostest\"}"
     print("Files: " + request.FILES['data'].name) #test.jpg
     print("Data Save Name: " + data['save']['name']) #crash, interprets this as a string indeces lookup

Ideas are welcome.

M. Ryan
  • 6,973
  • 11
  • 52
  • 76

2 Answers2

1

My best idea here is that adding raw string data directly to the POST body and then adding a file just doesn't work.

That wouldn't work, no. If you're POSTing form data using 'application/x-www-form-urlencoded' format, or 'multipart/form-data' you're not going to be able to just tack some extra data on the end - it needs to go in as part of the form data. Something like this I guess...

[request setPostValue:@"{\"save\":{\"name\":\"iostest\"}}" forKey:@"data"];

But if I remove the string data and only post the file it still doesn't work.

Is more problematic...

or if it's Piston erroneously misreading the data.

I probably wouldn't look in that direction first - piston doesn't really mess with the request object, so it seems more likely that the ASI request isn't quite right.

I think the place to start would be to inspect the incoming request and check that it really is a valid formPOST request:

  1. Check that request["CONTENT_TYPE"] is set to 'multipart/form-data'
  2. Inspect the request.raw_post_data and make sure that it is valid form data as specified in http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - check that the key names are as you expected and that the file content is present. (Obviously you'll want to use a small text file when you're testing this!)
  3. Check which keys actually are present in request.FILES, if any, in case it's as simple as something like a misnamed field.

Failing all that I'd try to narrow down if it's a problem on the client or server side by trying to write a plain python client and seeing if you have the same issue then. Looking around, something like this: http://atlee.ca/software/poster/ might be useful.

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • I tried iterating over keys file FILES but it didn't seem to like .keys() on that object. What's a good way to iterate over the keys of FILES? -- I'll check on the other two as soon as I can get back on that project. – M. Ryan Feb 21 '11 at 17:18
  • Alright. So major problem #1 was that ASIHTTPRequest was silently failing with the file because the file path was sorta-kinda incorrect thanks to some part of Cocoa sucking. So, I am now at the point where Piston is reading and accepting the uploaded file, but now the big mystery is how to combine the file post AND the data post in a manner that Piston will be fine with. The setPostValue method for ASI just makes it end up as a string with Piston. so data['data'] in piston is just a string, not a JSON object. Will update main question now. – M. Ryan Feb 22 '11 at 03:26
1

I have basically hacked my way around this.

The basic problem is that the request format in which Django expects files to be submitted to the server is one which django-piston literally just drops the ball on.

When it encounters multipart requests, it simply doesn't try to parse the data.

The solution to this problem is to manually call the parsing engine, which, in the case of JSON, is straight out of django.utils (which is kind of disappointing).

You achieve this by using ASIHTTPRequest (or the request module of your choice) to set a standard post value by key, and then access it the old fashioned way.

from django.utils import simplejson
data = simplejson.loads(request.POST['save'])

Which basically just reduces this handler method at this point to nothing more than a regular old Django view in terms of the steps you have to take to get it going.

So clearly, django-piston is not built to deal with files apparently?

M. Ryan
  • 6,973
  • 11
  • 52
  • 76
  • I think you're being a little harsh on piston there ;-) What you're doing isn't a very common pattern. If you're uploading the file in a form POST request, it would probably be better to supply any additional data as form data too. I was being a little slow when I mentioned adding the JSON data into a form field in my reply - it'd make more sense to just do something like: [request setPostValue:@"iostest" forKey:@"name"] – Tom Christie Feb 22 '11 at 12:41
  • 1
    It would but when you normally spend time writing iOS code that serializes all your objects to JSON its disconcerting to have to break formation for one or two requests. – M. Ryan Feb 22 '11 at 17:21