Skip to content

Instantly share code, notes, and snippets.

@jidolstar
Created January 15, 2019 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jidolstar/9ae39ea6cdfacf4a59e15f132f3e942c to your computer and use it in GitHub Desktop.
Save jidolstar/9ae39ea6cdfacf4a59e15f132f3e942c to your computer and use it in GitHub Desktop.
bsHttp : objective-c
#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
#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
#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
#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