One day of the week I hope to do something that’s not really business oriented, I call it my “Friday-Project”. This title comes from my time in IT where you would generally avoid do any major changes to the systems you are maintaining on Fridays because nobody likes to work on fixing these on the following weekend. So Friday is a great day to just experiment, prototype or dream. At Google they are supposedly giving their employees 20% of their working time for such projects, so I am dedicating as much time to playful exploration as well.
Today I want to explore and hopefully finish some code to upload an image to TwitPic. So the first step is to have a look at their API documentation which is not very pretty, but at least it’s complete. There we see that we want to implement the uploadAndPost function.
METHOD: http://twitpic.com/api/uploadAndPost
Use this method to upload an image to TwitPic and to send it as a status update to Twitter.
Fields to post in post data should be formatted as multipart/form-data:
– media (required) – Binary image data
– username (required) – Twitter username
– password (required) – Twitter password
– message (optional) – Message to post to twitter. The URL of the image is automatically added.
Sample response:
<?xml version=”1.0″ encoding=”UTF-8″?>
<rsp status=”ok”>
<statusid>1111</statusid>
<userid>11111</userid>
<mediaid>abc123</mediaid>
<mediaurl>http://twitpic.com/abc123</mediaurl>
</rsp>
For this we need to construct an HTTP POST request to the URL http://twitpic.com/api/uploadAndPost and construct a multipart body with binary image data, Twitter username, password and an optional message. The response will be XML giving us the URL we can use to link to the picture.
Having the parameters intermixed makes it a bit harder especially if you have never constructed a multipart body before. The easiest method of image uploading is if you can specify parameters in the URL, but the designer of this API thought it smarter to have it in the body. But don’t worry, we’ll get this figured out as well.
First we have to decide how to do the connection. There are convenience methods for simple HTTP GET to download binary data or even text. But for such a complex POST we need to go the fine-grained way via NSURLRequest. More precisely we need to construct our POST with NSMutableURLRequest because we are going to set headers and do some other modifications.
We get started by creating the API url, a connection object and changing the type to a POST. That’s the first reason (of many to follow) why we needed it mutable.
// create the URL NSURL *postURL = [NSURL URLWithString:@"http://twitpic.com/api/upload"]; // create the connection NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:apiURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; // change type to POST (default is GET) [postRequest setHTTPMethod:@"POST"]; |
That far was obvious. If this were an API with the parameters in the URL then we would have added them to the postURL, but here we need to go ahead and construct a multi-part body. To go with it we also need certain headers that tell the server what to expect. We want it to know that there are multiple parts in the request body and how to tell where the boundary is between them. For this purpose you specify a separator with hopefully characters that don’t occur inside your body.
// just some random text that will never occur in the body NSString *stringBoundary = @"0xKhTmLbOuNdArY---This_Is_ThE_BoUnDaRyy---pqo"; // header value NSString *headerBoundary = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", stringBoundary]; // set header [postRequest addValue:headerBoundary forHTTPHeaderField:@"Content-Type"]; |
The nitty gritty is now to create a mutable data object to which we can attach the individual parameters that the API mandates. Each part needs to have:
- A @”–” to begin
- The boundary string
- The CR and LF characters, @”\r\n”
- Content-Disposition: form-data; name=”fieldname”
- CRLF, twice
- —> THE RAW DATA
- CRLF
- And then it starts with “–” again …
So let’s code that for username, password and message. Note that we convert all strings into a UTF8 binary representation.
// create data NSMutableData *postBody = [NSMutableData data]; NSString *username = @"dr_touch"; NSString *password = @"That's My Secret"; NSString *message = @"Testing TwitPic Upload for Blog Post"; // username part [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"username\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[username dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; // password part [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"password\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[password dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; // message part [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"message\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[message dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; |
The final part we need is the data for the image, the attachment to the data object is identical to what we already did for the other parameters. Also note that we did not keep the same order as mentioned in the documentation. The order does generally not matter. One thing though adds complexity: we need to set the correct mime type so that the server knows what kind of binary image data we will be sending. image/jpeg, image/png or image/gif. But for sake of simplicity we’re only uploading a JPEG file I had on my desktop.
The content disposition for files also supports setting of a file name, but TwitPic ignores that, so we just have it as a dummy value here to show where it would be.
// media part [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"Content-Disposition: form-data; name=\"media\"; filename=\"dummy.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"Content-Type: image/jpeg\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"Content-Transfer-Encoding: binary\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; // get the image data from main bundle directly into NSData object NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"cats" ofType:@"jpg"]; NSData *imageData = [NSData dataWithContentsOfFile:imagePath]; // add it to body [postBody appendData:imageData]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; // final boundary [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; |
Now the only thing left is to open a connection with the request and if all went well we can get the URL for the uploaded image from the result. Now there is a hard way and an easy way to do that. The hard way would be to create a asynchronous connection and implement all the call-back methods necessary. But we are a sucker for simplicity, so we do it synchronous. The disadvantages is that this blocks the main thread, but we can always put this on a background thread later. It’s always preferred to code as if there was no blocking if you want to have your code clean and readable.
// add body to post [postRequest setHTTPBody:postBody]; // pointers to some necessary objects NSURLResponse* response; NSError* error; // synchronous filling of data from HTTP POST response NSData *responseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&response error:&error]; if (error) { NSLog(@"Error: %@", [error localizedDescription]); } // convert data into string NSString *responseString = [[[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding:NSUTF8StringEncoding] autorelease]; // see if we get a welcome result NSLog(@"%@", responseString); |
And Heureka! It takes very long because it’s a big photo, but eventually we get this answer:
<?xml version="1.0" encoding="UTF-8"?> <rsp stat="ok"> <mediaid>1457mp</mediaid> <mediaurl>http://twitpic.com/1457mp</mediaurl> </rsp> |
And checking out the mediaURL we got back we actually see that our picture ended up on TwitPic, sub-titled by the message we sent as well.
Now, one more thing …
As a free bonus we’ll also grab the mediaURL with a quick NSScanner. If this where production software then we would actually parse the whole XML and also check if there was an error.
// create a scanner NSString *mediaURL = nil; NSScanner *scanner = [NSScanner scannerWithString:responseString]; [scanner scanUpToString:@"" intoString:nil]; [scanner scanString:@"" intoString:nil]; [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"<"] intoString:&mediaURL]; NSLog(@"mediaURL is %@", mediaURL); |
This is fairly simple: we skip all until the first mediaurl tag, then we skip this and then we scan until the first < to get the URL.
There you have it. We actually implemented our first call to a web-based API. You see that it can be difficult to piece together those multiparts, but once you have the above code in front of you, it’s mostly copy&paste. To really use this code in your own software there are some more things you would be required to do.
First there is the problem with synchronous loading which blocks the UI as it happens on the main thread. To get around that you simply make this a method that you execute in the background. Second, you need some error handling and maybe you should resize the image before you send too much data over the cellular network. Finally, no background method is complete without either an NSNotification or delegate callback upon finishing or encountering an error.
Thanks to Ollie Levy for providing some source code for me to base this article on. It speed things up very much. 🙂
Categories: Recipes
Hi Oliver!
There is a much simpler way to post images on twitpic (or transmitting binary data via http in general).
Do you know asi-http-request? It’s a wrapper around CFNetwork:
http://github.com/pokeb/asi-http-request
With asi-http-request you can do the following:
NSData *imageData = UIImagePNGRepresentation(imageToPost);
NSURL *twitpicURL = [NSURL URLWithString:@”http://twitpic.com/api/uploadAndPost”];
ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:twitpicURL] autorelease];
[request setData:imageData forKey:@”media”];
[request setPostValue:@”myUsername” forKey:@”username”];
[request setPostValue:@”myPassword” forKey:@”password”];
[request setPostValue:@”myMessage” forKey:@”message”];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestDone:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request start];
That’s all!
I am aware of this technique. However some people might prefer to UNDERSTAND what happens behind the scenes to appreciate different way to performing it. That’s why I explored the barefoot approach.
Thanks for the article. I copy and paste your code and find error: [imageData release]; – must be delete
Hi Dr-Touch
thanks for this great tutorial
but when i implement the “create a scanner” part i get an error. the error is (amp undeclared) !
There was something wrong in the blog post due to a problem with angle brackets. Have another look and try again.
Very Nice code i have implemented them to my project and it is working very fine Thank u let me be always linked with u
Please use the Flattr button on articles you like and share them via the social sharing buttons.
Hi..,,
It’s is nice tutorial for twitter. But Dr. Drops can you please guide me how can I integrate this code with Oath.
I want to post image (Twitpic) but how can I do it.I am facing problem while passing the token.
Can you please help me with some code.
Sorry, I cannot help you there. OAuth is still a mystery to me.
Great post, you saved my day. Thank you so much.
Thanks for the Acknowledgement & your time.
I am stilling looking to integrate it with my TwitterEngine class.If some how you would get successful the pls let me the same.
Really nice link domzilla, thanks for sharing the link.