Search code examples
nsurlsessionnsurlsessionuploadtask

NSURLSessionUploadTask not uploading image using POST


I have the following android code which works perfectly:

DefaultHttpClient client = new DefaultHttpClient(UploadPostActivity.this);
String s = "http://dacaea28.ngrok.io/my-site/multipart.php";
URL url = new URL(s);

String filepath = "/sdcard/Download/images.jpg";

File file = new File(filepath);
FileBody cbFile = new FileBody(file);


MultipartEntity mpEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
org.apache.http.client.methods.HttpPost post = new org.apache.http.client.methods.HttpPost(s);

mpEntity.addPart("image",cbFile);

mpEntity.addPart("name", new StringBody("Test", Charset.forName("UTF-8")));
mpEntity.addPart("data", new StringBody("This is test report", Charset.forName("UTF-8")));
org.apache.http.HttpResponse response = null;


post.setEntity(mpEntity);
org.apache.http.HttpResponse response1 = client.execute(post);
String t = EntityUtils.toString(response1.getEntity());

Log.d("Response:", t);

The PHP code I am using:

<?php
//Receive the data from android
$name = $_POST['name'];
$data = $_POST['data'];

if(empty($_FILES))
{
echo json_encode(
            array(

                'msg1'=>'file array is empty'
                )
            );

}
else if(!isset($_FILES['image']))
{
echo json_encode(
            array(

                'msg2'=>'image is not being set'
                )
            );
}
else
{
$file = $_FILES['image'];
$file2 = $_FILES['doc'];
//echo json_encode(
//            array(
//                'result'=>'success adding $name , $data and  $file',
//                'msg'=>'Report added successfully.'
//                )
//            );
$size = $file['size'];
echo "Name is $name and file size is $size";

$info = pathinfo($file['name']);
$ext = $info['extension']; // get the extension of the file
$newname = $file['name']; 
$target = '/Applications/MAMP/htdocs/my-site/images/'.$newname;
move_uploaded_file( $file['tmp_name'], $target);
echo "Name is $name, file size is $size, extension is $ext, new file name is $newname and finally target is $target";

$size2 = $file2['size'];
echo "Name is $name and file size is $size";

$info2 = pathinfo($file2['name']);
$ext2 = $info2['extension']; // get the extension of the file
$newname2 = $file2['name']; 
$target2 = '/Applications/MAMP/htdocs/my-site/images/'.$newname2;
move_uploaded_file( $file2['tmp_name'], $target2);
echo "Name is $name, file size is $size, extension is $ext, new file name is $newname and finally target is $target";


}



?>

Now the corresponding iOS code DOES NOT Work for me and returns 'msg1'=>'file array is empty'

I am using selecting an image from gallery and trying to upload it.

#import "UploadViewController.h"

@interface UploadViewController ()<NSURLSessionDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation UploadViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}


- (IBAction)uploadData:(id)sender {
       const NSString *boundaryConstant = @"----------V2ymHFg03ehbqgZCaKO6jy";
const NSString *fileParamConstant = @"image";

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];

NSURL* requestURL = [NSURL URLWithString:@"http://dacaea28.ngrok.io/my-site/multipart.php"];


NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:requestURL];
[request setHTTPMethod:@"POST"];

NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundaryConstant];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];

NSMutableData *body = [NSMutableData data];


NSData *imageData = UIImageJPEGRepresentation(self.imageView.image,0.9);

//    NSLog(@"image data added %lu",self.imageView.image.size);

if (imageData) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
                [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fileParamConstant, @"filename"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:imageData];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
}

        [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];

        NSString *postLength = [NSString stringWithFormat:@"%zu", [body length]];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

        [request setHTTPBody:body];
    [request addValue:@"Test" forHTTPHeaderField:@"name"];

    [request addValue:@"Test2" forHTTPHeaderField:@"data"];

        NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:imageData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            NSLog(@"STRING %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
            NSLog(@"%@", response);
            NSLog(@"%@", error);
        }];
        [uploadTask resume];
}



- (IBAction)pickImage:(id)sender {

    UIImagePickerController *pickerController = [[UIImagePickerController alloc]
                                                 init];
    pickerController.delegate = self;
    [self presentViewController:pickerController animated:YES completion:nil];
}


- (void) imagePickerController:(UIImagePickerController *)picker
         didFinishPickingImage:(UIImage *)image
                   editingInfo:(NSDictionary *)editingInfo
{
    self.imageView.image = image;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

Please let me know which part I am missing in my objc code


Solution

  • Here's a short list:

    • You need to include both Content-Type and Content-Disposition headers in each chunk. (You originally had both, but commented out.)
    • Each set of headers must end with two \r\n pairs, not one. (The original code was right in this regard, but commented out.)
    • You were originally attaching it with the form field name "photo" instead of "image" as the Android and PHP code are using. This is now correct.
    • There's no need to set Content-Length on the request in the first place; the task does that for you.
    • Your script expects two files to be uploaded, and you're only attaching one.

    This is probably an incomplete list, so even after you fix them, this may not work. There's a reason I usually recommend that developers skip the form-data encoding and use URL encoding for uploads instead. It's a bit less efficient, but it is a lot simpler to write. :-)

    Anyway, I would strongly urge you to run Charles Proxy and point a web browser or your Android app at it and see what is sent, then do the same with iOS, and compare the two. That should make most or all the bugs much easier to spot.