Files
GiGaMall/Pods/JSONModel/JSONModel/JSONModelNetworking/JSONHTTPClient.m
T
2019-03-07 22:12:14 +08:00

362 lines
14 KiB
Objective-C

//
// JSONModelHTTPClient.m
// JSONModel
//
#import "JSONHTTPClient.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
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