123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- //
- // JSONModelHTTPClient.m
- //
- // @version 1.2
- // @author Marin Todorov (http://www.underplot.com) and contributors
- //
- // Copyright (c) 2012-2015 Marin Todorov, Underplot ltd.
- // This code is distributed under the terms and conditions of the MIT license.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- #import "JSONHTTPClient.h"
- typedef void (^RequestResultBlock)(NSData *data, JSONModelError *error);
- #pragma mark - constants
- NSString* const kHTTPMethodGET = @"GET";
- NSString* const kHTTPMethodPOST = @"POST";
- NSString* const kContentTypeAutomatic = @"jsonmodel/automatic";
- NSString* const kContentTypeJSON = @"application/json";
- NSString* const kContentTypeWWWEncoded = @"application/x-www-form-urlencoded";
- #pragma mark - static variables
- /**
- * Defaults for HTTP requests
- */
- static NSStringEncoding defaultTextEncoding = NSUTF8StringEncoding;
- static NSURLRequestCachePolicy defaultCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
- static int defaultTimeoutInSeconds = 60;
- /**
- * Custom HTTP headers to send over with *each* request
- */
- static NSMutableDictionary* requestHeaders = nil;
- /**
- * Default request content type
- */
- static NSString* requestContentType = nil;
- #pragma mark - implementation
- @implementation JSONHTTPClient
- #pragma mark - initialization
- +(void)initialize
- {
- static dispatch_once_t once;
- dispatch_once(&once, ^{
- requestHeaders = [NSMutableDictionary dictionary];
- requestContentType = kContentTypeAutomatic;
- });
- }
- #pragma mark - configuration methods
- +(NSMutableDictionary*)requestHeaders
- {
- return requestHeaders;
- }
- +(void)setDefaultTextEncoding:(NSStringEncoding)encoding
- {
- defaultTextEncoding = encoding;
- }
- +(void)setCachingPolicy:(NSURLRequestCachePolicy)policy
- {
- defaultCachePolicy = policy;
- }
- +(void)setTimeoutInSeconds:(int)seconds
- {
- defaultTimeoutInSeconds = seconds;
- }
- +(void)setRequestContentType:(NSString*)contentTypeString
- {
- requestContentType = contentTypeString;
- }
- #pragma mark - helper methods
- +(NSString*)contentTypeForRequestString:(NSString*)requestString
- {
- //fetch the charset name from the default string encoding
- NSString* contentType = requestContentType;
- if (requestString.length>0 && [contentType isEqualToString:kContentTypeAutomatic]) {
- //check for "eventual" JSON array or dictionary
- NSString* firstAndLastChar = [NSString stringWithFormat:@"%@%@",
- [requestString substringToIndex:1],
- [requestString substringFromIndex: requestString.length -1]
- ];
-
- if ([firstAndLastChar isEqualToString:@"{}"] || [firstAndLastChar isEqualToString:@"[]"]) {
- //guessing for a JSON request
- contentType = kContentTypeJSON;
- } else {
- //fallback to www form encoded params
- contentType = kContentTypeWWWEncoded;
- }
- }
- //type is set, just add charset
- NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
- return [NSString stringWithFormat:@"%@; charset=%@", contentType, charset];
- }
- +(NSString*)urlEncode:(id<NSObject>)value
- {
- //make sure param is a string
- if ([value isKindOfClass:[NSNumber class]]) {
- value = [(NSNumber*)value stringValue];
- }
-
- NSAssert([value isKindOfClass:[NSString class]], @"request parameters can be only of NSString or NSNumber classes. '%@' is of class %@.", value, [value class]);
- NSString *str = (NSString *)value;
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
- return [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
- #else
- return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
- NULL,
- (__bridge CFStringRef)str,
- NULL,
- (CFStringRef)@"!*'();:@&=+$,/?%#[]",
- kCFStringEncodingUTF8));
- #endif
- }
- #pragma mark - networking worker methods
- +(void)requestDataFromURL:(NSURL*)url method:(NSString*)method requestBody:(NSData*)bodyData headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
- {
- NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url
- cachePolicy: defaultCachePolicy
- timeoutInterval: defaultTimeoutInSeconds];
- [request setHTTPMethod:method];
- if ([requestContentType isEqualToString:kContentTypeAutomatic]) {
- //automatic content type
- if (bodyData) {
- NSString *bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding];
- [request setValue: [self contentTypeForRequestString: bodyString] forHTTPHeaderField:@"Content-type"];
- }
- } else {
- //user set content type
- [request setValue: requestContentType forHTTPHeaderField:@"Content-type"];
- }
-
- //add all the custom headers defined
- for (NSString* key in [requestHeaders allKeys]) {
- [request setValue:requestHeaders[key] forHTTPHeaderField:key];
- }
-
- //add the custom headers
- for (NSString* key in [headers allKeys]) {
- [request setValue:headers[key] forHTTPHeaderField:key];
- }
-
- if (bodyData) {
- [request setHTTPBody: bodyData];
- [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)bodyData.length] forHTTPHeaderField:@"Content-Length"];
- }
- void (^completionHandler)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *origResponse, NSError *origError) {
- NSHTTPURLResponse *response = (NSHTTPURLResponse *)origResponse;
- JSONModelError *error = nil;
- //convert an NSError to a JSONModelError
- if (origError) {
- error = [JSONModelError errorWithDomain:origError.domain code:origError.code userInfo:origError.userInfo];
- }
- //special case for http error code 401
- if (error.code == NSURLErrorUserCancelledAuthentication) {
- response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:401 HTTPVersion:@"HTTP/1.1" headerFields:@{}];
- }
- //if not OK status set the err to a JSONModelError instance
- if (!error && (response.statusCode >= 300 || response.statusCode < 200)) {
- error = [JSONModelError errorBadResponse];
- }
- //if there was an error, assign the response to the JSONModel instance
- if (error) {
- error.httpResponse = [response copy];
- }
- //empty respone, return nil instead
- if (!data.length) {
- data = nil;
- }
-
- handler(data, error);
- };
- //fire the request
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
- NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:completionHandler];
- [task resume];
- #else
- NSOperationQueue *queue = [NSOperationQueue new];
- [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
- completionHandler(data, response, error);
- }];
- #endif
- }
- +(void)requestDataFromURL:(NSURL*)url method:(NSString*)method params:(NSDictionary*)params headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
- {
- //create the request body
- NSMutableString* paramsString = nil;
- if (params) {
- //build a simple url encoded param string
- paramsString = [NSMutableString stringWithString:@""];
- for (NSString* key in [[params allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
- [paramsString appendFormat:@"%@=%@&", key, [self urlEncode:params[key]] ];
- }
- if ([paramsString hasSuffix:@"&"]) {
- paramsString = [[NSMutableString alloc] initWithString: [paramsString substringToIndex: paramsString.length-1]];
- }
- }
-
- //set the request params
- if ([method isEqualToString:kHTTPMethodGET] && params) {
- //add GET params to the query string
- url = [NSURL URLWithString:[NSString stringWithFormat: @"%@%@%@",
- [url absoluteString],
- [url query] ? @"&" : @"?",
- paramsString
- ]];
- }
-
- //call the more general synq request method
- [self requestDataFromURL: url
- method: method
- requestBody: [method isEqualToString:kHTTPMethodPOST]?[paramsString dataUsingEncoding:NSUTF8StringEncoding]:nil
- headers: headers
- handler:handler];
- }
- #pragma mark - Async network request
- +(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary*)params orBodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString
- method:method
- params:params
- orBodyString:bodyString
- headers:nil
- completion:completeBlock];
- }
- +(void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString
- method:method
- params:params
- orBodyData:[bodyString dataUsingEncoding:NSUTF8StringEncoding]
- headers:headers
- completion:completeBlock];
- }
- +(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary *)params orBodyData:(NSData*)bodyData headers:(NSDictionary*)headers completion:(JSONObjectBlock)completeBlock
- {
- RequestResultBlock handler = ^(NSData *responseData, JSONModelError *error) {
- id jsonObject = nil;
- //step 3: if there's no response so far, return a basic error
- if (!responseData && !error) {
- //check for false response, but no network error
- error = [JSONModelError errorBadResponse];
- }
- //step 4: if there's a response at this and no errors, convert to object
- if (error==nil) {
- // Note: it is possible to have a valid response with empty response data (204 No Content).
- // So only create the JSON object if there is some response data.
- if(responseData.length > 0)
- {
- //convert to an object
- jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
- }
- }
- //step 4.5: cover an edge case in which meaningful content is return along an error HTTP status code
- else if (error && responseData && jsonObject==nil) {
- //try to get the JSON object, while preserving the original error object
- jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
- //keep responseData just in case it contains error information
- error.responseData = responseData;
- }
-
- //step 5: invoke the complete block
- dispatch_async(dispatch_get_main_queue(), ^{
- if (completeBlock) {
- completeBlock(jsonObject, error);
- }
- });
- };
- NSURL *url = [NSURL URLWithString:urlString];
- if (bodyData) {
- [self requestDataFromURL:url method:method requestBody:bodyData headers:headers handler:handler];
- } else {
- [self requestDataFromURL:url method:method params:params headers:headers handler:handler];
- }
- }
- #pragma mark - request aliases
- +(void)getJSONFromURLWithString:(NSString*)urlString completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString method:kHTTPMethodGET
- params:nil
- orBodyString:nil completion:^(id json, JSONModelError* e) {
- if (completeBlock) completeBlock(json, e);
- }];
- }
- +(void)getJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString method:kHTTPMethodGET
- params:params
- orBodyString:nil completion:^(id json, JSONModelError* e) {
- if (completeBlock) completeBlock(json, e);
- }];
- }
- +(void)postJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString method:kHTTPMethodPOST
- params:params
- orBodyString:nil completion:^(id json, JSONModelError* e) {
- if (completeBlock) completeBlock(json, e);
- }];
- }
- +(void)postJSONFromURLWithString:(NSString*)urlString bodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString method:kHTTPMethodPOST
- params:nil
- orBodyString:bodyString completion:^(id json, JSONModelError* e) {
- if (completeBlock) completeBlock(json, e);
- }];
- }
- +(void)postJSONFromURLWithString:(NSString*)urlString bodyData:(NSData*)bodyData completion:(JSONObjectBlock)completeBlock
- {
- [self JSONFromURLWithString:urlString method:kHTTPMethodPOST
- params:nil
- orBodyString:[[NSString alloc] initWithData:bodyData encoding:defaultTextEncoding]
- completion:^(id json, JSONModelError* e) {
- if (completeBlock) completeBlock(json, e);
- }];
- }
- @end
|