Uploading Files from iPhone

by

We recently completed an iPhone application that allows user to, among other things, perform voice-recording and upload the recorded file to a server. For uploading, it uses HTTP multipart POST against an Apache webserver. This type of arrangement is a very common way of sending a file to a webserver. I am certain that similar implementations can be found in several different programming languages. Here is my Objective C implementation…I tried to focus on brevity and encapsulation.


The code:


/**
 * Upload a file to a webserver thru http post.
 * uid is the http request id
 * requestData is list of key/value pairs to be sent along with the file
 * url is the destination url
 * filePath is absolute file path of the file to be uploaded
 */
- (void)postUpload:(NSString *)url
                  :(NSString *)uid
                  :(NSDictionary *)requestData
                  :(NSString *)filePath
{
  NSURL *theURL = [NSURL URLWithString:url];
  NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL 
               cachePolicy:NSURLRequestReloadIgnoringCacheData 
           timeoutInterval:20.0f];
  [theRequest setHTTPMethod:@"POST"];
  [theRequest setValue:uid forHTTPHeaderField:@"uid"];
  
  // define post boundary...
  NSString *boundary = [[NSProcessInfo processInfo] globallyUniqueString];
  NSString *boundaryString = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  [theRequest addValue:boundaryString forHTTPHeaderField:@"Content-Type"];
  
  // define boundary separator...
  NSString *boundarySeparator = [NSString stringWithFormat:@"--%@\r\n", boundary];
  
  //adding the body...
  NSMutableData *postBody = [NSMutableData data];
  
  // adding params...
  for (id key in requestData) {
    NSString *formDataName = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key];
    NSString *formDataValue = [NSString stringWithFormat:@"%@\r\n", [requestData objectForKey:key]];
    [postBody appendData:[boundarySeparator dataUsingEncoding:NSUTF8StringEncoding]];
    [postBody appendData:[formDataName dataUsingEncoding:NSUTF8StringEncoding]];
    [postBody appendData:[formDataValue dataUsingEncoding:NSUTF8StringEncoding]];
  }
  // if file is defined, upload it...
  if (filePath) {
    NSArray *split = [filePath componentsSeparatedByString:@"/"];
    NSString *fileName = (NSString*)[split lastObject];
    NSData *fileContent = [NSData dataWithContentsOfFile:filePath options:0 error:nil];
    
    [postBody appendData:[boundarySeparator dataUsingEncoding:NSUTF8StringEncoding]];
    [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"audioBody\"; filename=\"%@\"\r\n", fileName] dataUsingEncoding:NSUTF8StringEncoding]];
    [postBody appendData:[@"Content-Type: application/octet-stream\r\n\r\n"
      dataUsingEncoding:NSUTF8StringEncoding]];
    [postBody appendData:fileContent];
  }
  [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary]
    dataUsingEncoding:NSUTF8StringEncoding]];
  [theRequest setHTTPBody:postBody];  
  
  NSURLResponse *theResponse = NULL;
  NSError *theError = NULL;
  NSData *reqResults = [NSURLConnection sendSynchronousRequest:theRequest
             returningResponse:&theResponse
               error:&theError];
  if (!reqResults) {
    NSLog(@"Connection error for URL: %@", [url description]);
    UIAlertView *alert = [UIAlertView alloc];
    [[alert initWithTitle:@"Error in connection"
            message:[theError localizedDescription]
           delegate:self
      cancelButtonTitle:nil
      otherButtonTitles:@"Quit", nil] autorelease];
    [alert show];
  }
}
Here’s an example of how you can use this method:

NSArray *keys =[NSArrayarrayWithObjects:@"recipients", @"title", @"textBody", nil];
NSArray *objects = [NSArray arrayWithObjects:uids, inputSubject.text, inputMessage.text, nil];
NSDictionary* postData = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

// post now…
[[WebServicealloc] postUpload:URL_ENDPOINT_SEND_MESSAGE
                :appDelegate.uid
                :postData
                :[recorder recordFilePath]];
URL_ENDPOINT_SEND_MESSAGE is set to the URL that can receive a multipart POST. To test this out, I have quickly written the following PHP code.

<?php
  define(‘FILE_SAVE_DIRECTORY’,‘/folder/to/save/uploaded/files/’);
  error_log(“Requesting upload file….”);
  error_log(print_r($_SERVER, true));
  error_log(“\n\n”);
  error_log(print_r($_FILES, true));
  if ($_POST && isset($_FILES['file'])) {
    error_log(“  the file:….” . $_FILES['file']['name']);
    $file = $_FILES['file'];
    $dst = FILE_SAVE_DIRECTORY . basename($file['name']);
    if (move_uploaded_file($file['tmp_name'], $dst)){
      echo ‘Upload Successfully: ‘.$dst;
    }
    else {
      echo ‘Error uploading the file.’;
    }
  }
?>
Put this code in your php-enabled webserver. Then, set URL_ENDPOINT_SEND_MESSAGE to point to the script.
It’s that simple. Now you’re ready to use HTTP upload in your next iPhone project.

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>