Sunday, November 26, 2006

Sending HTTP GET and POST from Cocoa

Ever wanted to send a form to a webpage from within Cocoa. Here's how.

GET forms are the easiest to do. You just tack on your variables to the end of the URL, like this:
http://www.nowhere.com/sendFormHere.php?key1=val1&key2=val2

Then you could use your standard NSURLDownload to fetch the file.
A POST form is pretty much the same thing, except you need to specify that you are sending a POST, and you need to stick your variables into the http body instead of tacking them onto the end of the URL.

Here's how to setup the URL request for a POST:

NSString *post = @"key1=val1&key2=val2";
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];

NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:@"http://www.nowhere.com/sendFormHere.php"]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

And you can use this to setup your NSURLDownload. Pretty simple huh?

There is one last thing we need to tackle before you can start using this in your finished product. You need to make sure that the strings you use are properly encoded to go into your string. For example, imagine that one of your keys is called "name" and it's value is "Bob & Cindy". You can't have spaces like that in your URL (or inside the post string), so you'd have to escape the spaces like this "Bob%20&%20Cindy". But that's not all...

You would also need to encode the '&' symbol, because it has special meaning. (To seperate one key,value pair from another). And you'd also need to encode any '?' symbols, and any '=' symbols, and so on. But we don't want to have to write a custom method to do all this. Not when apple has already provided such a huge API for use to use. So what can we use to do this for use in 1 or 2 lines of code?

At first glance it looks like NSString's stringByAddingPercentEscapesUsingEncoding: method might work. But if you try it with the "Bob & Cindy" string above you'll find that it only encodes the spaces, and leaves the '&' symbol. But we see from the method's documentation that it uses Core Foundations CFURLCreateStringByAddingPercentEscapes method to do it's dirty work. And we can use this to do ours as well. Here is a method to do what we want:

- (NSString *)urlEncodeValue:(NSString *)str
{
NSString *result = (NSString *) CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)str, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8);
return [result autorelease];
}

So to use the method you would write something like this:

NSString *post = [NSString stringWithFormat:@"key1=%@&key2=%@",
[self urlEncodeValue:val1]
[self urlEncodeValue:val2]];

 

37 comments:

Ankur Kothari said...

A simple and excellent guide. I was about to write a post on the exact same thing, but it looks like you beat me to it!

I'm glad you included information on how to encode strings for GET and POST as omitting this can cause frustrating errors if you don't know what's going on.

Richard A. Muscat said...

Very good post. I found a 6-page article split in 3 parts on mactech.com about the same subject but this says exactly the same things in 1/6th the length ... and much simpler too.

Thanks for the tips :-)

Jediknil said...

Nice...although it seems like urlEncodeValue: should really be a category method on NSString...like stringByAddingQueryPercentEscapes. (Or leave in the encoding parameter... stringByAddingQueryPercentEscapesUsingEncoding:)

Or pick a nicer, less corresponding name.

Andrew Paul Simmons said...

Thanks that really helps. I am developing an iPhone app and I couldn't find anything on useful on HTTP POST with Objective-C and COCOA. Thanks again. Andrew Paul Simmons

Johannes Fahrenkrug said...

Thanks a lot for the post info, it was just what I needed for my iPhone app.

inZania said...

Excellent, just what I was looking for. Thanks!

Trevor said...

Typos:
"and it's value" --> "and its value"
"do it's dirty work" --> "do its dirty work"

Panoma said...

Thanks a lot! Just needed for my iPhone app.

Infinity said...

Ugh. I know I had this working but now I've been fighting with it all day and it is broken. Could I send you what I have? I am sure it is just something totally basic that I am missing.

MrRubato said...

Thank you! Just what I'm looking for!

richards said...

You rock man, after 3 years...
Many thanx for the post.

Barry said...

Thanx for this!
more then 3 years later and it still helped me out a great deal...

Jessica said...

This works perfectly.

Thank you so much!

Anonymous said...

Simple and great post.. thanks!

Anonymous said...

Thanks Alot Really was Helpful :D

Anonymous said...

Incredibly helpful. Apple should have a reference to this in the developer docs. Would save a lot of time for a lot of people.

Dave said...

Your URL encoding is wrong. You're not supposed to encode the "&" and "=" characters. Only the keys and values should be encoded.

Anonymous said...

Thanks a lot!

Anonymous said...

@ Dave, I am having trouble getting this working and wondered if it's for the reason you state (i.e. the & and = should not be encoded). But that begs the question how do I append them to the data if these characters are not encoded?

I have the key and argument "posts[quote]=somequote" as my key and argument. So how do make a data obj from this without encoding the whole lot or splitting it and appending [posts[quote] datausingencoding... and then dataByAppending: @"=" ?!?

I am confused how this would work or is that perfectly OK? Sorry I am a bit of a newbie :-) cheers

onaissi said...

Thank you for this!!! I've been looking for this for some time.

People are rarely as straight forward in their explanations as you are.

aimstar said...

Excellent guide. Thanks a lot!

Clay said...

Thank you for the tip!

pelleter said...

Good to see that you encoded it, too bad there isn't already a decent selector that I know of for creating http request arguments. I had to write a bit more code to completed your selector, as follows:
- (NSString *)httpArgumentsFromDictionary:(NSDictionary *)dict {
NSString *argumentList = [[NSString new] autorelease];
for (NSString *aKey in dict) {
NSString *rawValue = [dict valueForKey:aKey];
NSString *encodedvalue = [self urlEncodeValue:rawValue];
NSString *kvPair;
if([argumentList length]==0) {
kvPair = @"%@?%@=%@";
} else {
kvPair = @"%@&%@=%@";
}
argumentList = [NSString stringWithFormat:kvPair,argumentList,aKey,encodedvalue];
}
return argumentList;
}

pelleter said...

an example of using the selector i wrote is as follows:
NSString *hogTestScript = @"hogTest.php";
NSArray *keys = [NSArray arrayWithObjects:@"fileName",@"bytesBeforeSleep",@"sleepSeconds",˙nil];
NSArray *values = [NSArray arrayWithObjects:mp4FileName,@"2048",@"5",nil];
NSDictionary *requestArgumentsDictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys];
NSString *reqArguments = [self.utils httpArgumentsFromDictionary:requestArgumentsDictionary];
NSString *sourceURL = [NSString stringWithFormat:@"%@%@%@",webDocumentBase,hogTestScript,reqArguments];

What I do is I create a dictionary of keys and values to use in my http request then I run my selector on it to return a string which I concatenate onto my php script web address.
I hope you find it useful!

Anonymous said...

For anyone else who runs into this, the original code + encoding is just fine! You encode & = INSIDE the values, which is exactly what the code is doing.

key1=val1&key2=value2&key3=val3

Here, key1,val1,key2,val2,key3,val3 should have & or =encoded, otherwise their = & will get confused with the above string that separates the keys and values. Makes sense?

But the problem with the code is that you need to tack on

[[NSURLDownload alloc] initWithRequest:request delegate:self];

to the end, otherwise it doesn't actually do anything. :P
Also configure a delegate as here: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLDownload.html

Anonymous said...

Many thanks..

I want to post the values and open the url (for proxy pay). Is it possible?

Jabz padoor

Ali said...

A few years late but it has to be said...
Trevor you are wrong about the typos.

peahen said...

just wanted to say thanks - a real time-saver.

Anonymous said...

Trevor is correct about the typos. "it's" == "it is"

So the uses don't make any sense:

"...and it is value is..."
"...do it is dirty work..."

http://garyes.stormloader.com/its.html

Anonymous said...

Oh, brother. Is this an English class now? I don't think the post is any less understandable with the typos. I suppose us coders are trained to be syntax junkies!

For what it's worth, possessive personal pronouns, serving as either noun-equivalents or adjective-equivalents, do not use an apostrophe, even when they end in s. The complete list of those ending in the letter s or the corresponding sound /s/ or /z/ but not taking an apostrophe is ours, yours, his, hers, its, theirs, and whose. That's a sneaky one, isn't it?

But we digress... ;)

Thanks for the excellent post. Very helpful indeed.

Anonymous said...

It's not working for me.
I've copied your Objective-C code but in PHP I'm not getting any _POST variable at all, I'm dumping, all _POST, _SERVER, _GET, _ENV, and so on variables to a file but no data at all is posted.
And the _ENV variable says that it's a GET request.
Any ideas what it can be?

Coffee said...

http://192.168.1.6:8800/osgibroker/event?topic=topic1&clientID=iOS&_method=POST

I would like to send that event. I tried to follow that tutorial but it's not working, someone can help me?

omniasoft said...

If you're working with iOS solutions you can't use NSURLDownload. See the iOS note on https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLDownload.html

Instead use NSURLConnection.

Anonymous said...

Hm, link in Coffee's post isn't working, but I think I'm having a similar problem. I'm working on an iPad app, and I've set it up according to the tutorial, but I'm getting a response from my web service saying it isn't getting any information. I'm very new to Xcode/Cocoa/Obj-C, but it looks to me like the tutorial is just setting up an object with a bunch of properties, but I don't see anything that looks like "send request". Is that correct? So I'm suppose to use NSURLConnection to actually send the request?

Anonymous said...

Can you help me please to tell when i am creating POST method .Is it Necessary to convert all POST request to JSON or XML in asynchronous case

zalak said...

thank you :) your post helped me a lot..

Anonymous said...

It seems this post was made in 2006, an update would be fantastic.