1

I'm using AFNetworking to upload an image along with a few parameters to a PHP script (built with CodeIgniter) which will receive the image, place the file name and parameters in a database and move the image to a permanent location.

Here's the Obj-C:

NSURL *url = [NSURL URLWithString:@"http://my_api_endpoint"];
NSData *imageToUpload = UIImageJPEGRepresentation(_mainMedia, .25f);
NSDictionary *params = [[NSDictionary alloc]initWithObjectsAndKeys:
                        _topicText.text,@"Topic",
                        @"1",@"Category",
                        @"1",@"Creator",
                        nil];
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:url];

NSString *timeStamp = [NSString stringWithFormat:@"%0.0f.jpg", [[NSDate date] timeIntervalSince1970]];

NSMutableURLRequest *request = [client multipartFormRequestWithMethod:@"POST" path:@"apidebate/debates" parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
    [formData appendPartWithFileData: imageToUpload name:@"MainMedia" fileName:timeStamp mimeType:@"image/jpeg"];
}];

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSString *response = [operation responseString];
    NSLog(@"response: [%@]",response);

    [self dismissViewControllerAnimated:YES completion:nil];
    [self.delegate createTopicViewControllerDidCreate:self];

    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    if([operation.response statusCode] == 403){
        NSLog(@"Upload Failed");
        return;
    }
    NSLog(@"error: %@", [operation error]);

    [self dismissViewControllerAnimated:YES completion:nil];
    [self.delegate createTopicViewControllerDidCreate:self];
}];

[operation setUploadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
    NSLog(@"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
    float width = totalBytesWritten / totalBytesExpectedToWrite;

}];

[operation start];

Here's the PHP:

//CONTROLLER FROM API
function debates_post()
{
mail('myemailaddress@gmail.com', 'Test', 'Posted');
$tmp_dir = "images/posted/";

if(isset($_FILES['MainMedia'])){
    $SaniFileName = preg_replace('/[^a-zA-Z0-9-_\.]/','', basename($_FILES['MainMedia']['name']));

    $file = $tmp_dir . $SaniFileName;
    move_uploaded_file($_FILES['MainMedia']['tmp_name'], $file);
}
else
    $SaniFileName = NULL;

$data = array('Topic'=>$this->post('Topic'), 'MainMedia'=>$this->post('MainMedia'), 'Category'=>$this->post('Category'), 'Creator'=>$this->post('Creator'));
$insert = $this->debate->post_debate($this->post('Topic'), $SaniFileName, $this->post('Category'), $this->post('Creator'));
if($insert){
    $message = $this->db->insert_id();
}
else{
    $message = 'Insert failed';
}

$this->response($message, 200);
}

//MODEL
function post_debate($Topic=NULL, $MainMedia='', $Category=NULL, $Creator=NULL){
$MainMedia = ($MainMedia)?$MainMedia:'';
$data = array(
                'Topic' => $Topic,
                'MainMedia' => $MainMedia,
                'Category' => $Category,
                'Creator' => $Creator,
                'Created' => date('Y-m-d h:i:s')
            );
return $this->db->insert('debate_table', $data);
}

My current problem is that the upload from iOS very rarely completes and there is no pattern to when it does not. I can add large or small photos or no photo at all with just parameters and it works 20% of the time. When it fails, this is the error message I get in X-Code:

2012-08-26 01:52:10.698 DebateIt[24215:907] error: Error Domain=NSURLErrorDomain 
Code=-1021 "request body stream exhausted" UserInfo=0x1dd7a0d0 
{NSErrorFailingURLStringKey=http://my_api_url, 
NSErrorFailingURLKey=http://my_api_url,
NSLocalizedDescription=request body stream exhausted, 
NSUnderlyingError=0x1e8535a0 "request body stream exhausted"}

What in the world is this?

I have a basic HTML form that I post the same image with to the same endpoint and it works every time with any size image. iOS seems to allow images of varying size just fine...but large or small, it will probably only work on the second try.

Thoughts?

d2burke
  • 4,081
  • 3
  • 39
  • 51
  • most likely you made a mistake — but how should anybody be able to help you without seeing the code? – vikingosegundo Aug 28 '12 at 14:07
  • related question: http://stackoverflow.com/questions/4359531/nsmutableurlrequest-and-request-body-stream-exhausted-error – Felix Aug 28 '12 at 14:08
  • does this happen in the simulator or only on device? Is there enough disk space on the device? Is the code in setUploadProgressBlock being executed? – Felix Aug 28 '12 at 14:23
  • 1
    Check your POST body size v/s "Content-Length" header. A mismatch will cause such error. – fsaint Aug 28 '12 at 14:24
  • @phix23 - It happens in the simulator and on several devices with 5.1 or 6.0 installed – d2burke Aug 28 '12 at 14:26
  • does it help to retain the AFHTTPRequestOperation or enqueue it with `[self.client enqueueHTTPRequestOperation:operation]`? – Felix Aug 28 '12 at 14:31
  • @phix23 - ok, just tried that, to no avail. What does `"request body stream exhausted` even mean? Some say the endpoint is unreachable or incorrect, but I know it's correct because I can hit it all day from a web form....this is SO frustrating – d2burke Aug 28 '12 at 14:37
  • @Felz - what's a quick way to check the content-length before I send it? I'm used to actually setting the content-length for API calls...but it was no where in the AFNetworking documentation (that I could find) – d2burke Aug 28 '12 at 14:38
  • @d2burke For this weird network errors I use a network sniffer and run the app in the simulator. I like HTTPScoop. – fsaint Aug 28 '12 at 14:50

3 Answers3

3

I'm using AFNetworking for upload images also, and i don't have problems with it, maybe because i'm coding my image data by base64?

NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                               token, @"token",
                               [UIImageJPEGRepresentation(image, 0.8) base64EncodedString],@"photo",
                               nil];

NSMutableURLRequest *request = [self.httpClient requestWithMethod:@"POST"
                                                             path:@"/user/upload/photo.json"
                                                       parameters:params];
Michal Zaborowski
  • 5,039
  • 36
  • 35
  • I was really hoping this would work. Is this exactly how you use it? My build fails for this error: "No visible @interface for 'NSData' declares the selector 'base64EncodeString'" – d2burke Sep 02 '12 at 22:08
  • 1
    https://github.com/m1entus/WCMientus/tree/master/WCMientus/External/Base64 - don't forget to add -fno-objc-arc – Michal Zaborowski Sep 03 '12 at 09:20
  • And you have to decode base64 in your server: http://php.net/manual/en/function.base64-decode.php – Michal Zaborowski Sep 03 '12 at 09:22
  • !!!!! That did it! The ONLY drawback seems to be that the image quality is a bit lower...is that the case with your uploads? Any way to fix it? Anyway, thanks for this. You have no idea how much time i've spent on this. – d2burke Sep 03 '12 at 15:18
  • 1
    You shouldn't send 'clear' bytes to server, so we used base64 encoding, about quality: UIImageJPEGRepresentation(_mainMedia, .25f); - you are using 0.25 compressionQuality, if you want to better quality change it to 1.0 - check UIImageJPEGRepresentation or use PNG - UIImagePNGRepresentation (almost no compression). – Michal Zaborowski Sep 03 '12 at 16:00
  • I tried this method on my codes and it worked before but now I tried building it on the device. I have this issue with the dictionary with the base64encoded string explained here: http://stackoverflow.com/questions/17985121/nsdictionary-base64-string-logs I'm really confused on why it happens 'cause on simulator it's fine but when built on device it's broken. – otakuProgrammer Aug 01 '13 at 08:22
0

I had a similar error some time ago. It was caused by a mismatch between the value of the "Content-Length" http header and the actual body size. The error was subtle. It occurred because I was using the string length as the content size and when the string had double byte characters in the UTF-8 encoding those contribute 1 the string length but 2 to the post body size. Check your POST body size v/s "Content-Length".

I debug this weird network errors with a network sniffed running the app in the simulator. I like HTTPScoop.

fsaint
  • 8,759
  • 3
  • 36
  • 48
  • while I'm on this...is there not a way to implicitly set the Content-Length? – d2burke Aug 28 '12 at 15:06
  • The content length is set by AFNetworking and it should be correct! I've read the AFNetworking code... – Felix Aug 28 '12 at 15:08
  • @phix23 I agree it should be correct. But the error is evidence it might be wrong. It is easy enough to check. – fsaint Aug 28 '12 at 15:15
  • @Felz - AFNetworking does, indeed, check and output the Content-Length, I have learned. Further, the matched just fine...so I still have no answer at this point. – d2burke Aug 29 '12 at 01:07
0

First, make sure you have the latest version of AFNetworking downloaded.

you can do [[AFHTTPRequestOperation alloc] initWithRequest:...] and then set the completionBlock with either the straight property accessor (operation.completionBlock = ^{...}), or with -setCompletionBlockWithSuccess:failure:. Keep in mind that completion blocks execute after the request has finished downloading.

As for the multipart form block, -appendWithFileData:mimeType:name was also removed a while back. The method you want is -appendPartWithFileData:name:fileName:mimeType:.

Make those two changes and everything should work.

prashant
  • 1,920
  • 14
  • 26
  • appendPartWithFileData:name:fileName:mimeType: this is the exact method listed above, no? I'm not sure what change you're proposing :( – d2burke Sep 03 '12 at 14:54