Created
January 15, 2019 13:09
-
-
Save jidolstar/9ae39ea6cdfacf4a59e15f132f3e942c to your computer and use it in GitHub Desktop.
bsHttp : objective-c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import "bsHttpFile.h" | |
#import "bsError.h" | |
#define HTTP_METHOD_POST @"POST" | |
#define HTTP_METHOD_GET @"GET" | |
#define HTTP_METHOD_DELETE @"DELETE" | |
#define HTTP_METHOD_PUT @"PUT" | |
typedef void (^bsHttpCallback)(NSData * _Nullable data, NSURLResponse * _Nullable response, bsError * _Nullable bsError); | |
@interface bsHttp : NSObject | |
NS_ASSUME_NONNULL_BEGIN | |
+(NSString*)sendWithUrl:(NSString* _Nonnull)url method:(NSString* _Nonnull)method param:(NSDictionary* _Nonnull)param timeout:(NSUInteger)timeout end:(bsHttpCallback)end; | |
+(void)cancel:(NSString * _Nonnull)taskKey; | |
NS_ASSUME_NONNULL_END | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "bsHttp.h" | |
#import "bsMacro.h" | |
#import "bsStr.h" | |
@interface bsHttp() | |
@end | |
@implementation bsHttp | |
NSMutableDictionary *__bsHttp_tasks = nil; | |
+(NSMutableDictionary*)__headerFromParam:(NSDictionary*)param{ //key의 첫글자가 @이면 header로 본다. | |
NSMutableDictionary *dic = [[NSMutableDictionary alloc]init]; | |
if( param == nil || [param count] == 0 ) return dic; | |
[param enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL *stop) { | |
if( key.length > 0 && [key isEqualToString:@"@@ignoreMainThread"]) return; | |
if( key.length > 1 && [[key substringToIndex:1] isEqualToString:@"@"] ) { | |
key = [key substringFromIndex:1]; | |
if( key.length == 0 ) return; | |
if( [obj isKindOfClass:[bsHttpFile class]] ) return; //@가 있더라도 파일이면 header가 될 수 없음! | |
if( [obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] ) obj = [[bsStr sharedInstance] jsonDecode:obj]; | |
else if( ![obj isKindOfClass:[NSString class]] ) obj = [[bsStr sharedInstance] str:obj]; | |
[dic setObject:obj forKey:key]; | |
} | |
}]; | |
return dic; | |
} | |
+(NSMutableDictionary*)__dataFromParam:(NSDictionary*)param{ //key의 첫글자가 @가 아니면 data로 본다. | |
NSMutableDictionary *dic = [[NSMutableDictionary alloc]init]; | |
if( param == nil || [param count] == 0 ) return dic; | |
[param enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL *stop) { | |
if( key.length > 0 && ![[key substringToIndex:1] isEqualToString:@"@"] ) { | |
if( [obj isKindOfClass:[bsHttpFile class]] ) return; | |
else if( [obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] ) obj = [[bsStr sharedInstance] jsonDecode:obj]; | |
else if( ![obj isKindOfClass:[NSString class]] ) obj = [[bsStr sharedInstance] str:obj]; | |
[dic setObject:obj forKey:key]; | |
} | |
}]; | |
return dic; | |
} | |
+(NSMutableDictionary*)__fileFromParam:(NSDictionary*)param{ | |
NSMutableDictionary *dic = [[NSMutableDictionary alloc]init]; | |
if( param == nil || [param count] == 0 ) return dic; | |
[param enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL *stop) { | |
if( [[key substringToIndex:1] isEqualToString:@"@"] ) return; //key에 @가 첫글자이면 무시함! | |
if( key.length == 0 ) return; | |
if( [obj isKindOfClass:[bsHttpFile class]] && [(bsHttpFile*)obj count] > 0 ){ | |
[dic setObject:obj forKey:key]; | |
} | |
}]; | |
return dic; | |
} | |
+(NSString*)__queryStringFromData:(NSDictionary*)data{ | |
if( data == nil || [data count] == 0 ) return @""; | |
NSMutableString *str = [[NSMutableString alloc] init]; | |
__block unsigned long i, j; | |
i = 0; | |
j = [data count]; | |
[data enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL *stop) { | |
[str appendString:[[bsStr sharedInstance] urlEncode:key]]; | |
[str appendString:@"="]; | |
[str appendString:[[bsStr sharedInstance] urlEncode:value]]; | |
if( i < j - 1 ) [str appendString:@"&"]; | |
i++; | |
}]; | |
return [NSString stringWithString:str]; | |
} | |
+(NSString*)__method:(NSString*)method{ | |
method = [method uppercaseString]; | |
if( [method isEqualToString:HTTP_METHOD_GET] || [method isEqualToString:HTTP_METHOD_POST] || [method isEqualToString:HTTP_METHOD_PUT] || [method isEqualToString:HTTP_METHOD_DELETE] ){ | |
return method; | |
} | |
bsException(@"http method는 GET, POST, PUT, DELETE중 하나이어야만 합니다."); | |
return nil; | |
} | |
+(NSString*)sendWithUrl:(NSString* _Nonnull)url method:(NSString* _Nonnull)method param:(NSDictionary* _Nonnull)param timeout:(NSUInteger)timeout end:(bsHttpCallback _Nonnull)end{ | |
if( timeout < 5 ) timeout = 5; | |
else if( timeout > 60 ) timeout = 60; | |
NSString *taskKey = [[bsStr sharedInstance]UUID]; | |
method = [self __method:method]; | |
@try { | |
BOOL ignoreMainThread = [param objectForKey:@"@@ignoreMainThread"] ? YES : NO; | |
NSDictionary *header = [self __headerFromParam:param]; | |
NSDictionary *data = [self __dataFromParam:param]; | |
NSDictionary *file = [self __fileFromParam:param]; | |
NSString *book = nil; | |
if ( [method isEqualToString:HTTP_METHOD_GET] ) { | |
NSMutableString *str = [[NSMutableString alloc] init]; | |
if( [url rangeOfString:@"#"].length > 0 ) { | |
NSArray *urlMark = [[bsStr sharedInstance] split:url seperator:@"#" trim:YES]; | |
url = urlMark[0]; | |
book = urlMark[1]; | |
} | |
[str appendString:url]; | |
NSString *qs = [self __queryStringFromData:data]; | |
if(qs != nil && qs.length > 0){ | |
if( [url rangeOfString:@"?"].length > 0 ) [str appendString:@"&"]; | |
else [str appendString:@"?"]; | |
[str appendString:qs]; | |
} | |
if( book != nil ) { | |
[str appendString:@"#"]; | |
[str appendString:book]; | |
} | |
url = [NSString stringWithString:str]; | |
data = nil; | |
} | |
if ( [method isEqualToString:HTTP_METHOD_GET] || [method isEqualToString:HTTP_METHOD_DELETE] ){ //(?)GET, DELETE 방식은 파일업로드 지원 안함 | |
file = nil; //GET은 File을 보낼 수 없음 | |
} | |
NSMutableData *body = [NSMutableData data]; | |
NSMutableURLRequest *request = [NSMutableURLRequest | |
requestWithURL:[NSURL URLWithString:url] | |
cachePolicy:NSURLRequestReloadIgnoringCacheData | |
timeoutInterval:timeout]; | |
[request setHTTPMethod:method]; | |
[request setValue:@"Keep-Alive" forHTTPHeaderField:@"Connection"]; | |
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; //gzip 받아줌 | |
if( header && [header count] > 0 ){ //header셋팅 | |
[header enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL *stop) { | |
[request setValue:value forHTTPHeaderField:key]; | |
}]; | |
} | |
if( file && [file count] > 0 ) { //file이 있다면 파일처리하자. | |
NSString *BOUNDARY = @"-----------------bs-----"; | |
[request setValue:[NSString stringWithFormat:@"multipart/form-data, boundary=%@", BOUNDARY] forHTTPHeaderField:@"Content-Type"]; | |
NSData *crlf =[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]; | |
NSData *boundary = [[NSString stringWithFormat:@"--%@", BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]; | |
NSData *disp0 = [@"Content-Disposition: form-data; name=\"" dataUsingEncoding:NSUTF8StringEncoding]; | |
NSData *disp1 = [@"\"; filename=\"" dataUsingEncoding:NSUTF8StringEncoding]; | |
NSData *disp2 = [@"\"" dataUsingEncoding:NSUTF8StringEncoding]; | |
NSData *octet = [@"Content-Type: application/octet-stream" dataUsingEncoding:NSUTF8StringEncoding]; | |
if( data != nil ) { | |
[data enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL *stop) { | |
[body appendData:boundary]; | |
[body appendData:crlf]; | |
[body appendData:disp0]; | |
[body appendData:[key dataUsingEncoding:NSUTF8StringEncoding]]; | |
[body appendData:disp2]; | |
[body appendData:crlf]; | |
[body appendData:crlf]; | |
if( [obj isKindOfClass:[NSString class]] ) { | |
[body appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]]; | |
} else { | |
[body appendData:[[[bsStr sharedInstance] str:obj] dataUsingEncoding:NSUTF8StringEncoding]]; | |
} | |
[body appendData:crlf]; | |
}]; | |
} | |
[file enumerateKeysAndObjectsUsingBlock:^(NSString *key, bsHttpFile *f, BOOL *stop) { | |
[f loop:^(NSString *name, NSData *data) { | |
[body appendData:boundary]; | |
[body appendData:crlf]; | |
[body appendData:disp0]; | |
[body appendData:[key dataUsingEncoding:NSUTF8StringEncoding]]; | |
[body appendData:disp1]; | |
[body appendData:[name dataUsingEncoding:NSUTF8StringEncoding]]; | |
[body appendData:disp2]; | |
[body appendData:crlf]; | |
[body appendData:octet]; | |
[body appendData:crlf]; | |
[body appendData:crlf]; | |
[body appendData:data]; | |
[body appendData:crlf]; | |
}]; | |
[bsHttpFile PUT:f]; //pool로 돌려줌 | |
}]; | |
[body appendData:boundary]; | |
[body appendData:[@"--" dataUsingEncoding:NSUTF8StringEncoding]]; | |
[body appendData:crlf]; | |
[body appendData:crlf]; | |
}else if( data && [data count] > 0 ){ | |
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; | |
[body appendData:[[self __queryStringFromData:data] dataUsingEncoding:NSUTF8StringEncoding]]; | |
} | |
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[body length]] forHTTPHeaderField:@"Content-Length"]; | |
[request setHTTPBody:body]; | |
if(!__bsHttp_tasks){ | |
@synchronized (__bsHttp_tasks) { | |
__bsHttp_tasks = [[NSMutableDictionary alloc]init]; | |
} | |
} | |
NSURLSessionTask* task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable e) { | |
@synchronized (__bsHttp_tasks){ | |
NSURLSessionTask *task = [__bsHttp_tasks objectForKey:taskKey]; | |
if(task) [__bsHttp_tasks removeObjectForKey:taskKey]; | |
} | |
void (^report)(void) = ^(){ | |
NSLog(@"%@", response); | |
NSUInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; | |
bsError *error = nil; | |
if(e) error = [bsError POPWithMsg:e.localizedFailureReason data:[e userInfo] func:__FUNCTION__ line:__LINE__]; | |
else if(statusCode >= 400) error = [bsError POPWithMsg:[NSString stringWithFormat:@"Status=%ld Error Occured", statusCode] data:nil func:__FUNCTION__ line:__LINE__]; | |
end(data, response, error); | |
if(error != nil) [bsError PUT:error]; | |
}; | |
if([NSThread isMainThread] || ignoreMainThread) { | |
report(); | |
}else{ | |
dispatch_sync(dispatch_get_main_queue(), report); | |
} | |
}]; | |
[task resume]; | |
@synchronized (__bsHttp_tasks) { | |
[__bsHttp_tasks setObject:task forKey:taskKey]; | |
} | |
} | |
@catch (NSException *e) { | |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ | |
end(nil, nil, [bsError POPWithMsg:[NSString stringWithFormat:@"[%@]%@", [e name], [e reason]] data:[e userInfo] func:__FUNCTION__ line:__LINE__]); | |
}); | |
} | |
return taskKey; | |
} | |
+(void)cancel:(NSString* _Nonnull)taskKey{ | |
@synchronized (__bsHttp_tasks){ | |
NSURLSessionTask *task = [__bsHttp_tasks objectForKey:taskKey]; | |
if(task) [task cancel]; | |
} | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@interface bsHttpFile : NSObject | |
+(bsHttpFile*)POP; | |
+(void)PUT:(bsHttpFile*)file; | |
-(void)addWithName:(NSString*)name data:(NSData*)data; | |
-(void)loop:(void (^)(NSString *name, NSData *data))block; | |
-(NSUInteger)count; | |
-(void)clear; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "bsHttpFile.h" | |
#import "bsMacro.h" | |
@interface bsHttpFile(){ | |
NSMutableDictionary *_files; | |
} | |
@end | |
@implementation bsHttpFile | |
static NSMutableArray *__bsHttpFile_pool = nil; | |
+(bsHttpFile*)POP { | |
__block bsHttpFile *r; | |
@synchronized( __bsHttpFile_pool ) { | |
if( __bsHttpFile_pool == nil ) { | |
__bsHttpFile_pool = [[NSMutableArray alloc]init]; | |
} | |
if( [__bsHttpFile_pool count] > 0 ) { | |
r = [__bsHttpFile_pool lastObject]; | |
[__bsHttpFile_pool removeLastObject]; | |
} else { | |
r = [[bsHttpFile alloc] init]; | |
} | |
}; | |
return r; | |
} | |
+(void)PUT:(bsHttpFile*)file { | |
if( file == nil ) return; | |
@synchronized( __bsHttpFile_pool ) { | |
if( __bsHttpFile_pool == nil ) { | |
__bsHttpFile_pool = [[NSMutableArray alloc] init]; | |
} | |
[file clear]; | |
[__bsHttpFile_pool addObject:file]; | |
}; | |
} | |
-(void)addWithName:(NSString*)name data:(NSData*)data { | |
if( name == nil ) bsException( @"name is nil" ); | |
if( data == nil ) bsException( @"data is nil" ); | |
@synchronized( _files ) { | |
if( _files == nil ) { | |
_files = [[NSMutableDictionary alloc]init]; | |
} | |
_files[[name copy]] = data; | |
} | |
} | |
-(void)loop:(void (^)(NSString *name, NSData *data))block { | |
@synchronized( _files ) { | |
[_files enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSData *data, BOOL *stop) { | |
if( block ) block( name, data ); | |
}]; | |
} | |
} | |
-(NSUInteger)count { | |
@synchronized( _files ) { | |
if( _files ) { | |
return [_files count]; | |
} | |
} | |
return 0; | |
} | |
-(void)clear { | |
@synchronized( _files ){ | |
[_files removeAllObjects]; | |
}; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment