37

I have been combing through the many many posts about uploading images via POST in iOS. Despite the wealth of information on this topic, I cannot manage to correctly upload JPEG data taken from my iPhone Simulator Photo Library. The data, once on the server, is just a huge string of hexidecimal. Shouldn't NSData just be a byte stream? I don't get whats going on with all the hex, or why this code seems to work for everyone else.

Here is the code in question:

-(void)uploadWithUserLocationString:(NSString*)userLocation{
NSString *urlString = @"http://some.url.com/post";

// set up the form keys and values (revise using 1 NSDictionary at some point - neater than 2 arrays)
NSArray *keys = [[NSArray alloc] initWithObjects:@"auth",@"text",@"location",nil];
NSArray *vals = [[NSArray alloc] initWithObjects:self.authToken,self.textBox.text,userLocation,nil];

// set up the request object
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:@"POST"];

//Add content-type to Header. Need to use a string boundary for data uploading.
NSString *boundary = [NSString stringWithString:@"0xKhTmLbOuNdArY"];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField: @"Content-Type"];

//create the post body
NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n",boundary] dataUsingEncoding:NSASCIIStringEncoding]];

//add (key,value) pairs (no idea why all the \r's and \n's are necessary ... but everyone seems to have them)
for (int i=0; i<[keys count]; i++) {
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[keys objectAtIndex:i]] dataUsingEncoding:NSASCIIStringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@",[vals objectAtIndex:i]] dataUsingEncoding:NSASCIIStringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSASCIIStringEncoding]];
}
[body appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"image\"\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
[body appendData:[[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
[body appendData:[NSData dataWithData:self.imageData]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSASCIIStringEncoding]];

// set the body of the post to the reqeust
[request setHTTPBody:body];

// make the connection to the web
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

NSLog(returnString);
[keys release];
[vals release];
}

Thanks for your time.

corescan
  • 481
  • 1
  • 5
  • 10

5 Answers5

39

This code works in my app. If you're not using ARC you'll need to modify the code to release anything alloc'ed.

// create request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];                                    
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[request setHTTPShouldHandleCookies:NO];
[request setTimeoutInterval:30];
[request setHTTPMethod:@"POST"];

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

// post body
NSMutableData *body = [NSMutableData data];

// add params (all params are strings)
for (NSString *param in _params) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", [_params objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}

// add image data
NSData *imageData = UIImageJPEGRepresentation(imageToPost, 1.0);
if (imageData) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", FileParamConstant] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:@"Content-Type: image/jpeg\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:imageData];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
}

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

// setting the body of the post to the reqeust
[request setHTTPBody:body];

// set URL
[request setURL:requestURL];
XJones
  • 21,828
  • 10
  • 63
  • 81
  • Did you ever have issues making this work? It's almost exactly my code, and I even changed my content-type to 'image/jpeg' like yours. Perhaps my problem is server-side, although I'm pretty positive it's not. – corescan Nov 08 '11 at 17:10
  • No issues, this code works in my app to upload JPG files captured from the iPhone camera to our web server. – XJones Nov 08 '11 at 19:53
  • Could you explain the purpose for the filename=\"image.jpg\" ? I don't have this in my code, I had assumed it was extra information, but maybe its necessary? – corescan Nov 08 '11 at 21:31
  • I did this so long ago I don't recall why. I don't think it's required. – XJones Nov 08 '11 at 22:35
  • Hey XJones, what is all that SSAPI stuff in your code? constants defined elsewhere in your code? – corescan Dec 14 '11 at 17:56
  • SSAPI... are constants defined in my app, I've removed reference to them in the answer. Sorry if it was confusing. – XJones Dec 14 '11 at 19:13
  • 3
    I wanted to add a comment here about this code. The carriage returns and line feeds and the extra dashes are all CRITICAL to how this process works. You MUST have the double CR/LF in the right place and the extra dashes in the right place.I had thought it was just so much extra stuff but it is critical. XJone's code helped me figure out my issue where I was missing a lousy CR/LF and my upload was failing. +1 for you sir and for this question. – TJ Asher Jun 28 '12 at 03:52
  • Hello XJones, can you please tell me what _params dictionary has? – Rajan Maharjan Sep 25 '12 at 10:34
  • _params is a dictionary of key/values you want to include with the post as parameters (e.g. "username=mysticboy59" or whatever). – XJones Sep 25 '12 at 15:59
  • What is the difference between `boundary` and `BoundaryConstant`? Should they be different? – fredley Feb 06 '13 at 16:48
  • How did u name your files on the PHP side? – DesperateLearner Feb 20 '13 at 07:33
  • 1
    I'm not sure. I didn't write the server side code, I'm posting to a proprietary API. The naming on the server doesn't make a difference to the client code however. – XJones Feb 20 '13 at 09:29
  • @Jones, Along with text I want to upload multiple images..in my case it is 3 images..Can u give me some idea how to do this. – Sandeep Jul 01 '13 at 15:32
  • it should be as simple as taking the portion of my code with the comment "add image data" and repeating that in a loop for each image you want to upload. change the filename within the loop (e.g. "image1.jpg", "image2.jpg", etc. I haven't done this personally and don't have time to debug it right now but let me know how it goes. I'll update the answer if it works for you. – XJones Jul 01 '13 at 17:58
  • @Jones,Can u check this code once..It is saving all text details and one image...The remaining two images are not saving..I am not sure that problem is with code or in web service part.Is there any problem with this code. http://pastebin.com/M5KAN6pq – Sandeep Jul 02 '13 at 12:39
  • I'm doing something similar--appending the `UIImage` data into an `NSString`, but this is giving me memory issues. If I have a lot of images, or even just a few hi-res images, appending the image data itself into an ever-growing `NSString` throws a memory warning and eventually crashes the app. – MLQ Sep 04 '13 at 04:22
  • I was Stuck in this Problem for 1 day. THANKS a bunch. I was using MKNetworkKit but had to switch to NSURLRequest :) – Zeeshan Jan 26 '14 at 20:00
  • I have a problem with the image append, if i comment it, it works well and returns correct Data, but if i uncomment it i get a null object. Have you ever had this problem? – Alexandru Dranca Jun 04 '14 at 13:22
  • Using above code in my application, after executing "[body appendData:imageData];", while debugging I noticed that "body" is nil. till the above line of appendData:imageData I'm able to see some value for "body". I'm not able to identify what's going wrong? – Satyam Jul 01 '14 at 17:35
9

i hope this code will help some body else ...

//create request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];

//Set Params
[request setHTTPShouldHandleCookies:NO];
[request setTimeoutInterval:60];
[request setHTTPMethod:@"POST"];

//Create boundary, it can be anything
NSString *boundary = @"------VohpleBoundary4QuqLuM1cE5lMwCy";

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

// post body
NSMutableData *body = [NSMutableData data];

//Populate a dictionary with all the regular values you would like to send.
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];

[parameters setValue:param1 forKey:@"param1-name"];

[parameters setValue:param2 forKey:@"param2-name"];

[parameters setValue:param3 forKey:@"param3-name"];


// add params (all params are strings)
for (NSString *param in parameters) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", [parameters objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}

NSString *FileParamConstant = @"imageParamName";

NSData *imageData = UIImageJPEGRepresentation(imageObject, 1);

//Assuming data is not nil we add this to the multipart form
if (imageData)
{
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", FileParamConstant] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type:image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:imageData];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
}

//Close off the request with the boundary
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

// setting the body of the post to the request
[request setHTTPBody:body];

// set URL
[request setURL:[NSURL URLWithString:baseUrl]];

[NSURLConnection sendAsynchronousRequest:request
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {

                           NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;

                           if ([httpResponse statusCode] == 200) {

                               NSLog(@"success");
                           }

                       }];
Shaik Riyaz
  • 10,085
  • 7
  • 48
  • 67
  • if i am having the image path http://dev-demo.info.bh-in-15.webhostbox.net/dv/xyz/ulpoad/post/imageName then where should i put this in the above code – Abhi Jul 01 '16 at 07:25
  • @Abhi NSData *imageData = UIImageJPEGRepresentation(imageObject, 1) replace this line .. load image from path and convert to nsdata – Shaik Riyaz Jul 01 '16 at 07:44
  • its confusing can you please update it in your answer or in comment – Abhi Jul 01 '16 at 07:50
  • @Abhi NSData *imageData = [NSData dataWithContentOfFile:filePath]; provide ur file path – Shaik Riyaz Jul 01 '16 at 07:55
6

Take a look at Apple's SimpleURLConnections example project. You can use the PostController from there with a few modifications.

However - it's not exactly simple. The difference to the solution above is that Apple's is using a stream to stream the file to the server. That's much more memory-friendly than keeping the encoded image data around for the duration of the upload. It's also way more complicated.

n13
  • 6,568
  • 48
  • 40
1

I see that you send multiple files in one request, correct?

From HTTP specification, you should use multipart/mixed Content-type inside embedding multipart/form-data

Example from link above:

Content-type: multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="field1"

 Joe Blow
 --AaB03x
 content-disposition: form-data; name="pics"
 Content-type: multipart/mixed, boundary=BbC04y

 --BbC04y
  Content-disposition: attachment; filename="file1.txt"
Shaik Riyaz
  • 10,085
  • 7
  • 48
  • 67
vale4674
  • 3,931
  • 13
  • 41
  • 72
1
NSData *imgData = UIImagePNGRepresentation(imgUser.image);

NSString *str=[NSString stringWithFormat:@"%@upload_image",appDelegate.strRoot];
NSString *urlString = [NSString stringWithFormat:@"%@",str];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:@"POST"];
NSMutableData *body = [NSMutableData data];
NSString *boundary = @"---------------------------14737809831466499882746641449";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField: @"Content-Type"];

[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"a.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:imgData]];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

//  parameter UserId

[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userid\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];

[body appendData:[appDelegate.strUserID dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];


// close form
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];


// setting the body of the post to the request 
[request setHTTPBody:body];


NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
// NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSDictionary *dict=[NSJSONSerialization JSONObjectWithData:returnData options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@",dict);
Code cracker
  • 2,782
  • 6
  • 31
  • 57
BKjadav
  • 493
  • 2
  • 15
  • thank you very much for your code! other snippets didn't work and the difference with yours was that you close the form with the boundary. – Alberto M Feb 07 '17 at 17:41