iOS: Uploading an Image from Your iPhone to a Server

Uploading an image to a server from your iOs device works by simulating an upload via a plain and simple web form – you know, those where you have a button that says “Browse” and another one that says “Upload” once you’ve selected an appropriate image file.

Today we’ll take a look at how the implementation on the iPhone/iPad can be done.

First of all, we need to set up the basics to get an asynchroneous connection to the server. We’ll set the HTTP method to ‘POST’ since we want to create/add something.

<code> 
#define DataDownloaderRunMode @"myapp.run_mode" 

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url 
                               cachePolicy:NSURLRequestUseProtocolCachePolicy 
                               timeoutInterval:60]; 

[request setHTTPMethod:@"POST"]; 

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request 
                                                       delegate:self 
                                                       startImmediately:NO]; 

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:DataDownloaderRunMode]; 

[connection start]; </code>

 

The next thing we must do, is to add certain fields to the HTTP header on the one hand, the actual simulated web form with the image data to the body of the HTTP request on the other.

So, what are the header and the body supposed to look like?
The best way to find out is to go download a packet sniffer application, I highly recommend Wireshark. There probably also are all sorts of plugins for your browsers available that can do this.

Now start Wireshark, start a new capturing session and go to the web page that contains the form you use for the image submission. Go ahead and pick an image and hit the ‘upload’ button. You will see a list of packets in Wireshark. Now go to ‘File > Export > PLain Text File’. This will give you a .txt file with the traced packets. You’ll have to browse through it for a while, but at some point you will see many HEX numbers on the left (like a table) and the corresponding human readable content on the right. Just edit the text file so that at the end you only have the human readable content. The actual headers upload packets could be more than one, you might want to remove any information that’s in between. At the end you should have something like this (I’ve highlighted the important fields, you can ignore pretty much everything else like cookies or other fields):

So, what is this mysterious ‘boundary’? It’s a separator, that helps the receiver know when certain segments in the body of the HTTP request start and end, so it helps delimiting these segments of content. The RFC 2388 Specification explains it as follows:

4.1 Boundary

   As with other multipart types, a boundary is selected that does not
   occur in any of the data. Each field of the form is sent, in the
   order defined by the sending appliction and form, as a part of the
   multipart stream.  Each part identifies the INPUT name within the
   original form. Each part should be labelled with an appropriate
   content-type if the media type is known (e.g., inferred from the file
   extension or operating system typing information) or as
   "application/octet-stream".

 
 
What we need to do is to modify our HTTP request sent from the iPhone to look like what we’ve just traced. Note also that you have to set the appropriate url (as seen in the trace image) and make it a POST request.

#define DataDownloaderRunMode @"myapp.run_mode" 

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url 
                               cachePolicy:NSURLRequestUseProtocolCachePolicy 
                               timeoutInterval:60]; 

[request setHTTPMethod:@"POST"]; 

// We need to add a header field named Content-Type with a value that tells that it's a form and also add a boundary.
// I just picked a boundary by using one from a previous trace, you can just copy/paste from the traces.
NSString *boundary = @"----WebKitFormBoundarycC4YiaUFwM44F6rT";
    
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];

[request addValue:contentType forHTTPHeaderField: @"Content-Type"];
// end of what we've added to the header

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

// Now we need to append the different data 'segments'. We first start by adding the boundary.
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
	
// Now append the image
// Note that the name of the form field is exactly the same as in the trace ('attachment[file]' in my case)!
// You can choose whatever filename you want.
[body appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"attachment[file]\";
filename=\"picture.png\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];

// We now need to tell the receiver what content type we have
// In my case it's a png image. If you have a jpg, set it to 'image/jpg'
[body appendData:[[NSString stringWithString:@"Content-Type: image/png\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];

// Now we append the actual image data
[body appendData:[NSData dataWithData:imageData]];

// and again the delimiting boundary    
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
	
// adding the body we've created to the request
[request setHTTPBody:body];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request 
                                                       delegate:self 
                                                       startImmediately:NO]; 

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:DataDownloaderRunMode]; 

[connection start];

 
 
This worked perfectly for me. It is possible that you’re going to face some trouble, since just one missing line or whitespace too much in the header or body might cause you problems. In this case, go back to Wireshark, start a new tracing session and start an image upload by running your app on the simulator from xCode. Again, export the trace to a text file, modify it to make it more readable and compare it to the trace from the web form. This will help you see if your HTTP headers and bodies have the same content – if not, modify the code accordingly.

Good luck! :-)