2

I'm having some trouble getting some code I have acquired working, my experience with php/html is 'limited' at best so I hope I'm not looking like too much of a noob by asking this.

The problem I am having is not being able to upload a small image to a web-server. I have played around with the following for the past 4-5 hours, it's nearly 2am here in the UK and the only progress I have made has been changing

[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];

to

[NSString stringWithFormat:@"enctype=\"multipart/form-data\"; boundary=%@",boundary];

This gets my Server to stop giving an internal error every time i send something.

the code is below, it is on a number of pages on this site and I cannot find the original author to give credit. I am currently getting an empty array when I have print_r($_FILES); in the receiving php file.

Any help what so ever would be massively appreciated, thanks in advance.

NSString *urlString = @"http://mysite/upload_image.php";

UIImage * img = [UIImage imageNamed:@"mypic.png"];
NSData *imageData = UIImageJPEGRepresentation(img, 90);

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init] ;
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:@"POST"];

NSString *boundary = @"---------------------------14737809831466499882746641449";
NSString *contentType = [NSString stringWithFormat:@"enctype=\"multipart/form-data\"; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField: @"Content-Type"];


NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Disposition: attachment; name=\"image\"; filename=\"davesfile.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:imageData]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:body];

NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

NSLog(@"%@",returnString);

1 Answer 1

7

A couple of reactions:

  1. If you use AFNetworking, the process of creating these sorts of requests is simpler, getting you out of the weeds of constructing the request yourself:

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    [manager POST:[url absoluteString] parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileData:imageData name:fieldName fileName:[imagePath lastPathComponent] mimeType:[self mimeTypeForPath:imagePath]];
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
        NSLog(@"Success: %@", string);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];
    
  2. By the way, I usually use MobileCoreServices.framework to get the mime type on the basis of the extension, rather than hard coding it:

    - (NSString *)mimeTypeForPath:(NSString *)path
    {
        // get a mime type for an extension using MobileCoreServices.framework
    
        CFStringRef extension = (__bridge CFStringRef)[path pathExtension];
        CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
        assert(UTI != NULL);
    
        NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType));
        assert(mimetype != NULL);
    
        CFRelease(UTI);
    
        return mimetype;
    }
    
  3. I notice that you're retrieving the image into a UIImage, and then using UIImageJPEGRepresentation to get the NSData. Do that if you want or must, but I generally prefer to send the raw data, directly bypassing the roundtrip through a UIImage, which can introduce image degradation and loss of meta data. Instead, you might want to grab the NSData directly:

    NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"mypic" ofType:@"png"]; // note, I'm going back to bundle, not grabbing `UIImage` from imageview
    NSData   *imageData = [NSData dataWithContentsOfFile:imagePath];
    

    If you really must do the roundtrip through UIImage, then you might want to use imageWithContentsOfFile rather than imageNamed, because the latter will cache the image unnecessarily.

  4. If you're determined to create the request yourself, this is the routine that I've used in the past:

    - (void)uploadFileAtPath:(NSString *)imagePath
                    forField:(NSString *)fieldName
                         URL:(NSURL*)url
                  parameters:(NSDictionary *)parameters
    {
        NSString *filename = [imagePath lastPathComponent];
        NSData *imageData = [NSData dataWithContentsOfFile:imagePath];
    
        NSMutableData *httpBody = [NSMutableData data];
        NSString *boundary = [self generateBoundaryString];
        NSString *mimetype = [self mimeTypeForPath:imagePath];
    
        // configure the request
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
        [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
        [request setHTTPShouldHandleCookies:NO];
        [request setTimeoutInterval:30];
        [request setHTTPMethod:@"POST"];
    
        // set content type
    
        NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
        [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    
        // add params (all params are strings)
    
        [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
        }];
    
        // add image data
    
        if (imageData) {
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:imageData];
            [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    
        [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
        // setting the body of the post to the reqeust
    
        [request setHTTPBody:httpBody];
    
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            if (connectionError)
            {
                NSLog(@"sendAsynchronousRequest error=%@", connectionError);
                return;
            }
    
            NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"Success: %@", string);
        }];
    }
    

    Clearly, if you're not using any parameters (which you don't appear to be doing), you can just pass nil for that parameter. But the key is that it supports additional parameters if you need it. I also employ asynchronous network requests, because one should never issue synchronous network requests on the main queue.

    This uses the generateBoundaryString method from Apple's SimpleURLConnections example:

    - (NSString *)generateBoundaryString
    {
        // generate boundary string
        //
        // adapted from http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections
        //
        // Note in iOS 6 and later, you can just:
        //
        //    return [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    
        CFUUIDRef  uuid;
        NSString  *uuidStr;
    
        uuid = CFUUIDCreate(NULL);
        assert(uuid != NULL);
    
        uuidStr = CFBridgingRelease(CFUUIDCreateString(NULL, uuid));
        assert(uuidStr != NULL);
    
        CFRelease(uuid);
    
        return [NSString stringWithFormat:@"Boundary-%@", uuidStr];
    }
    
  5. If you prefer, I retrofitted your code, modeled on some of the above code:

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"POST"];
    
    NSString *boundary = @"---------------------------14737809831466499882746641449";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    NSMutableData *body = [NSMutableData data];
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, [imagePath lastPathComponent]] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [self mimeTypeForPath:imagePath]] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:imageData]];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [request setHTTPBody:body];
    
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",returnString);
    
  6. Personally, I'd suggest changing your PHP code to generate a JSON response (as that will be easier for your app to consume).

By the way, I've tested the above with my PHP code. If you're still having problems, I'd suggest sharing your PHP code with us as well, as the construction of a well-formed request is intimately tied to what the PHP code requires.

Sign up to request clarification or add additional context in comments.

3 Comments

You have no idea how much of a help that is. thank you so much for the swift and comprehensive reply. A few points of my own regarding your comments; 1. I have never used AFNetworking, the app i'm developing basically fires json data to and from a server and so far that has been pretty easy, its only now that a picture is involved that I have got stuck, I will certainly look this up when the project is over. 2. Thanks for the hint on the MIME type, i'll be using this in the future
3. Dont worry about the Asynchronous side of things, one of my biggest hates is unresponsive apps so I always use them, I will put that in once the thing is working, WHICH IT IS NOW!!! Thanks Again, if I were anywhere near New York I'd buy you a drink :)
@Rob, awesome answer. Just what I was looking for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.