Wednesday, June 16, 2010

 

HttpHelper for all

Well, that's flattering: the lead developer of WordPress for iOS (WordPress is the world's most popular blogging software) just emailed me, saying nice things about my previously-posted HttpHelper class, and asking for permission to use it in their project.

And why stop with them? So I hereby formally release HttpHelper under the Apache License 2.0, for anyone to use. I've made a few tweaks to the class since I last posted it - one bug fix, and some synchronous accessors for use when you're already in a background thread - so here it is in its entirety:

HttpHelper.h


// HttpHelper.h
// Created by Jonathan Evans on 04/09/09.
// Copyright 2009 Jonathan Evans.

#import


@interface HttpHelper : NSObject {
}
+(NSURLRequest *) buildRequestWithPostKeys:(NSArray *) postKeys postValues:(NSArray *) postValues urlString:(NSString *)urlString;
+(BOOL) doPost:(NSURLRequest *)request forCaller:(id)caller onSuccess:(SEL)successSelector onFailure:(SEL)failSelector;
-(BOOL) doHttp:(NSDictionary *)args;

+(NSURLRequestCachePolicy)getCachePolicyFor:(NSString *)urlString;
+(NSURLRequest *)getURLRequestFor:(NSString *)urlString;
+(BOOL) doGet:(NSString *)urlString forCaller:(id)caller onSuccess:(SEL)successSelector onFailure:(SEL)failSelector;
+(NSString *) doSynchronizedGet:(NSString *)urlString;
+(NSData *) doSynchronizedDataGet:(NSString *)urlString;
+(NSString *) doSynchronizedPostTo:(NSString *)urlString withKeys:(NSArray*)postKeys andValues:(NSArray*)postValues;

@end


HttpHelper.m


// HttpHelper.m
// Created by Jonathan Evans on 04/09/09.
// Copyright 2009 Jonathan Evans.
//

#import "HttpHelper.h"


@implementation HttpHelper

static HttpHelper *singleton=nil;

#pragma mark -
#pragma mark Singleton methods

+(HttpHelper *) getInstance {
if (singleton==nil)
singleton = [[[HttpHelper alloc] init] autorelease];
return singleton;
}

+(id)allocWithZone:(NSZone *)zone {
if (singleton == nil) {
singleton = [super allocWithZone:zone];
return singleton;
}
return nil;
}

-(id)copyWithZone:(NSZone *)zone {
return self;
}

-(id)retain {
return self;
}

-(unsigned)retainCount {
return UINT_MAX;
}

-(void)release {
//pass
}

-(id)autorelease {
return self;
}

- (void)dealloc {
[super dealloc];
}

#pragma mark -
#pragma mark Business logic

+(NSURLRequest*) buildRequestWithPostKeys:(NSArray *) postKeys postValues:(NSArray *) postValues urlString:(NSString *)urlString {
NSURL * url = [NSURL URLWithString:urlString];
NSURLRequestCachePolicy policy = NSURLRequestReloadIgnoringCacheData; // we never want a cache of a post response
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:policy timeoutInterval:30.0]; //TODO: extend timeout
[request setHTTPMethod:@"POST"];

NSMutableData *paramData = [NSMutableData dataWithCapacity:8192];

NSMutableString *params=[[NSMutableString alloc] initWithCapacity:1024];
for (int i=0; i<[postValues count]; i++) {
NSObject* valueToPost = [postValues objectAtIndex:i];
if (i>0)
[params appendString:@"&"];
[params appendString:[postKeys objectAtIndex:i]];
[params appendString:@"="];
[params appendString:[NSString stringWithFormat:@"%@",valueToPost]];
}

[paramData appendData:[params dataUsingEncoding:NSUTF8StringEncoding]];

NSString *msgLength = [NSString stringWithFormat:@"%d", [paramData length]];
[request addValue: msgLength forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody: paramData];
[params release];

return request;
}

+(BOOL) doPost:(NSURLRequest *)request forCaller:(id)caller onSuccess:(SEL)onSuccess onFailure:(SEL)onFailure {

NSArray *keys = [NSArray arrayWithObjects:@"request", @"caller", @"onSuccess", @"onFailure", nil];
NSArray *values = [NSArray arrayWithObjects:request, caller,
[NSValue valueWithBytes:&onSuccess objCType:@encode(SEL)],
[NSValue valueWithBytes:&onFailure objCType:@encode(SEL)],
nil];
NSDictionary *args = [NSDictionary dictionaryWithObjects:values forKeys: keys];
NSThread* uploadThread = [[NSThread alloc] initWithTarget:[self getInstance] selector:@selector(doHttp:) object:args];
[uploadThread start];
[uploadThread release];
return TRUE;
}

+(NSURLRequestCachePolicy)getCachePolicyFor:(NSString *)urlString {
//Hived out to a separate method because we might fine-tune this later.
return NSURLRequestUseProtocolCachePolicy;
// return NSURLRequestReloadIgnoringCacheData;
}

+(NSURLRequest *)getURLRequestFor:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequestCachePolicy policy = [self getCachePolicyFor:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:policy timeoutInterval:30.0];
return request;
}

+(BOOL) doGet:(NSString *)urlString forCaller:(id)caller onSuccess:(SEL)onSuccess onFailure:(SEL)onFailure {
NSURLRequest *request = [self getURLRequestFor:urlString];
NSArray *keys = [NSArray arrayWithObjects:@"request", @"caller", @"onSuccess", @"onFailure", nil];
NSArray *values = [NSArray arrayWithObjects:request, caller,
[NSValue valueWithBytes:&onSuccess objCType:@encode(SEL)],
[NSValue valueWithBytes:&onFailure objCType:@encode(SEL)],
nil];
NSDictionary *args = [NSDictionary dictionaryWithObjects:values forKeys: keys];
NSThread* uploadThread = [[NSThread alloc] initWithTarget:[self getInstance] selector:@selector(doHttp:) object:args];
[uploadThread start];
[uploadThread release];
return TRUE;
}

-(BOOL) doHttp:(NSDictionary *)args
{
@synchronized (self) {
//autorelease pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSURLRequest *request = [args objectForKey:@"request"];
NSObject *caller = [args objectForKey:@"caller"];

SEL onSuccess;
[[args objectForKey:@"onSuccess"] getValue:&onSuccess];
SEL onFailure;
[[args objectForKey:@"onFailure"] getValue:&onFailure];

NSError *error=nil;
NSHTTPURLResponse *response=nil;
NSData *returnData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error ];
NSString * responseString = [[[NSString alloc] initWithData: returnData encoding: NSUTF8StringEncoding] autorelease];

NSString *errorString=nil;

if (error)
errorString = [NSString stringWithFormat:@"HTTP error from %@: %@", [request URL], [error localizedDescription]];
else if (response==nil || returnData==nil)
errorString = [NSString stringWithFormat:@"No response from URL %@",[request URL]];
else if (response.statusCode!=200)
errorString = [NSString stringWithFormat:@"Server error from URL %@",[request URL]];

if (errorString && [errorString length]>0) {
NSLog(@"iTravel Error ",errorString);
[caller performSelectorOnMainThread: onFailure withObject:errorString waitUntilDone:NO];
}
else
[caller performSelectorOnMainThread: onSuccess withObject:responseString waitUntilDone:YES];
[pool release];
}
return TRUE;
}

+(NSString *) doSynchronizedGet:(NSString *)urlString {
NSData *returnData = [HttpHelper doSynchronizedDataGet:urlString];
if (!returnData)
return [NSString stringWithFormat: @"Error: No data received from server",urlString];

NSString * responseString = [[[NSString alloc] initWithData: returnData encoding: NSUTF8StringEncoding] autorelease];
return responseString;
}

+(NSData *) doSynchronizedDataGet:(NSString *)urlString {
NSURLRequest *request = [self getURLRequestFor:urlString];
NSHTTPURLResponse *response=nil;
NSError *error=nil;
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

NSString *errorString=@"";
if (error)
errorString = [NSString stringWithFormat:@"Error loading page: %@", urlString, [error localizedDescription]];
else if (response==nil || returnData==nil)
errorString = [NSString stringWithFormat:@"Error - No response from URL %@", urlString];
else if (response.statusCode!=200)
errorString = [NSString stringWithFormat:@"Error - HTTP Error %i from URL %@", response.statusCode, urlString];

if ([errorString length]>0)
return [errorString dataUsingEncoding:NSUTF8StringEncoding];

return returnData;
}

+(NSString *) doSynchronizedPostTo:(NSString *)urlString withKeys:(NSArray*)postKeys andValues:(NSArray*)postValues {
NSURLRequest *request = [self buildRequestWithPostKeys:postKeys postValues:postValues urlString:urlString];
NSHTTPURLResponse *response=nil;
NSError *error=nil;
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

NSString *errorString=@"";
if (error)
errorString = [NSString stringWithFormat:@"Error - HTTP Error posting to URL %@: %@", urlString, [error localizedDescription]];
else if (response==nil || returnData==nil)
errorString = [NSString stringWithFormat:@"Error - No response from URL %@", urlString];
else if (response.statusCode!=200)
errorString = [NSString stringWithFormat:@"Error - HTTP Error %i from URL %@", response.statusCode, urlString];

if ([errorString length]>0)
return errorString;

NSString * responseString = [[[NSString alloc] initWithData: returnData encoding: NSUTF8StringEncoding] autorelease];

if ([[responseString lowercaseString] hasPrefix:@"error"])
errorString = [NSString stringWithFormat:@"Error message %@ from URL %@",responseString, urlString];
else if ([[responseString lowercaseString] hasPrefix:@"fail"])
errorString = [NSString stringWithFormat:@"Error - Failure message '%@' when posting to URL %@", responseString, urlString];

if ([errorString length]>0)
return errorString;

return nil; //indicates success
}

@end


How To Use It

The three chief asynchronous interface methods are, for a GET -


+(BOOL) doGet:(NSString *)urlString forCaller:(id)caller onSuccess:(SEL)onSuccess onFailure:(SEL)onFailure


and for a POST -


+(NSURLRequest*) buildRequestWithPostKeys:(NSArray *) postKeys postValues:(NSArray *) postValues urlString:(NSString *)urlString

+(BOOL) doPost:(NSURLRequest *)request forCaller:(id)caller onSuccess:(SEL)onSuccess onFailure:(SEL)onFailure {


I'll give you an example of the former first, as it's easier. The call itself is perfectly straightforward:


NSMutableString *urlString = [NSMutableString stringWithCapacity:128];
[urlString appendString:[Util getSearchPageURL]];
[urlString appendString:@"?locale="];
[urlString appendString:[UserSettings getLanguage]];
[urlString appendString:@"&searchTerms="];
[urlString appendString:[searchBar.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[HttpHelper doGet:urlString forCaller:self onSuccess:@selector(parseSearchResults:) onFailure:@selector(searchFailed:)];


Not, however, the two "@selector" arguments. These must be methods on the caller, and they better expect an NSString* and an NSError*, respectively, as arguments. (Or they can take an NSObject* and cast from there, I suppose, but why bother, right?)

Inside HttpHelper we have to wrap the selectors in NSValues to pass them from method to method, which is a bit annoying, but hey, we only have to do it once.

To call a POST, by comparison:

NSArray *postKeys = [NSArray arrayWithObjects:@"title", @"location", @"comments", @"pageUri", @"sectionName", @"sectionNumber", @"listingName", nil];
NSArray *postValues = [NSArray arrayWithObjects:note.title, [note locationString], note.body, note.pageUri, note.sectionName, note.sectionNumber, note.listingName, nil];
NSURLRequest *request = [HttpHelper buildRequestWithPostKeys:postKeys postValues:postValues urlString:[Util getUploadNoteURL]];
[HttpHelper doPost:request forCaller:self onSuccess:@selector(onUploadSuccess) onFailure:@selector(onUploadError:)];


Note that "onUploadSuccess" here doesn't take an argument - I don't care about the web site's response, the fact of success is all that matters. OK, dubious wisdom that, but the example is relevant to show that the selector methods don't actually have to accept arguments.

We launch a new thread every time we call these methods, so we have to be careful lest we run into concurrency problems. So we synchronize the actual HTTP calls in the "doHttp:" method, which is the one place in the app where we actually go out and connect to the big bad scary Internet.

The synchronous methods are simple enough that I don't think they require examples:


+(NSString *) doSynchronizedGet:(NSString *)urlString;
+(NSData *) doSynchronizedDataGet:(NSString *)urlString;
+(NSString *) doSynchronizedPostTo:(NSString *)urlString withKeys:(NSArray*)postKeys andValues:(NSArray*)postValues;


The DataGet is in case you want NSData instead of an NSString - if you're loading an image, for instance.

Comments:
Your site is truly cool and this is an extraordinary moving article and If it's not too much trouble share more like that. Thank You..
Digital Marketing Course in Hyderabad
 
What an incredible message this is. Truly one of the best posts I have ever seen in my life. Wow, keep it up.
AI Courses in Bangalore
 
Thank a lot. You have done excellent job. I enjoyed your blog . Nice efforts
Data Science Certification in Hyderabad
 
Awesome article. I enjoyed reading your articles. this can be really a good scan for me. wanting forward to reading new articles. maintain the nice work!
Data Science Courses in Bangalore

 
Wow, happy to see this awesome post. I hope this think help any newbie for their awesome work and by the way thanks for share this awesomeness, i thought this was a pretty interesting read when it comes to this topic. Thank you..
Artificial Intelligence Course
 
Excellent Blog! I would like to thank you for the efforts you have made in writing this post. Gained lots of knowledge.
Data Analytics Course
 
I need to thank you for this very good read and i have bookmarked to check out new things from your post. Thank you very much for sharing such a useful article and will definitely saved and revisit your site.
Data Science Course
 
I am sure it will help many people. Keep up the good work. It's very compelling and I enjoyed browsing the entire blog.
Business Analytics Course in Bangalore
 
Thanks for the good news! As far as I know, localization is the process where you create support for other languages ​​for your application. Often, you first make an application with an English interface and then localize it into other languages, such as Japanese. The localization process is time consuming, and the steps slowly change as Xcode is updated. The Localizable.strings file is where you add translation data as key / value pairs. Earlier versions of Xcode generated a Localizable.strings file by default, which could be easily copied for other languages. Recent versions of Xcode do not create a Localizable.strings file by default. I learned this information from posts on Instagram in which the authors talk about the localization of applications for the iPhone. I've seen enough posts on this topic there and almost all of them had at least 60 thousand likes! I am sure in order to achieve such indicators, their authors resorted to using the services of https://soclikes.com/buy-instagram-likes to quickly cheat the number of likes.
 
Hello everyone, this site is awesome! I express my gratitude for this opportunity to express my thoughts. I wanted to tell you about one difficulty that I had to face recently and how I solved this problem. Recently I had to register for Linked in order to find a good job, and I was pushed by the fact that it is impossible to write to employers there directly if I have no general contacts with them, and recruiters do not really pay attention to my profile if I have few subscribers. After a little search on the Internet, I found an excellent service that helped me acquire subscribers on linkdin, it worked quickly and reliably, and most importantly, not expensive. I recommend all beginner job seekers to use the services of https://viplikes.net/buy-linkedin-followers in order to quickly increase the number of subscribers.
 
You have completed certain reliable points there. I did some research on the subject and found that almost everyone will agree with your blog.

Data Science Training in Bangalore

 
I have voiced some of the posts on your website now, and I really like your blogging style. I added it to my list of favorite blogging sites and will be back soon ...

Digital Marketing Training in Bangalore
 
I wanted to leave a little comment to support you and wish you the best of luck. We wish you the best of luck in all of your blogging endeavors.

Artificial Intelligence Training in Bangalore
 
You actually make it seem like it's really easy with your acting, but I think it's something I think I would never understand. I find that too complicated and extremely broad. I look forward to your next message. I'll try to figure it out!

Machine Learning Course in Bangalore
 
I want to leave a little comment to support and wish you the best of luck.we wish you the best of luck in all your blogging enedevors.
data science course in jaipur
 
Excellent blog and article. I enjoy looking at your posts. Thanks for sharing on the web. I am hoping the same best work from you in the future as well. Thank you for sharing great blogging with us!
Data Science Training in Hyderabad
Data Science Course in Hyderabad

 
Reading this article was very useful-- particularly for me, as it gave me some more insight into writing tips. I wanted to add that the author did a good job of explaining those points clearly. This is particularly helpful for new entrepreneurs because it shows how important it is to consider how everyone perceives your business from the outside. I'm looking forward reading other articles soon.
AWS Training in Hyderabad
AWS Course in Hyderabad

 
I want to leave a little comment to support and wish you the best of luck.we wish you the best of luck in all your blogging enedevors.
aws training in hyderabad
 
This is a brilliant article, Given such a great amount of data in it, These kind of articles keeps the clients enthusiasm for the site, and continue sharing more ... good karma. ai courses in chennai
 
I am glad to discover this page. I have to thank you for the time I spent on this especially great reading !! I really liked each part and also bookmarked you for new information on your site.
Data Science Course Syllabus
 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.

Agile team facilitation

ICAGILE ATF
 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.

ICP ATF

Agile facilitator
 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.
Team facilitator in Agile

ICP ATF

 
I want to leave a little comment to support and wish you the best of luck.we wish you the best of luck in all your blogging enedevors.
data science course
 
I am glad to discover this page. I have to thank you for the time I spent on this especially great reading !! I really liked each part and also bookmarked you for new information on your site.
Data Science Training in Chennai
 

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]