diff options
author | o.bolovintseva <olesya.bolovintseva@mapswithme.com> | 2018-10-11 15:19:28 +0300 |
---|---|---|
committer | Olesia Bolovintseva <o.bolovintseva@mapswithme.com> | 2019-01-29 21:24:42 +0300 |
commit | 197cdcd52976105ad0568782971da15673a649f1 (patch) | |
tree | afbb7e0e7903d650fa176bacefcfaa6db34506a2 /platform | |
parent | a9d08cbd47cbcfd5ec71f943c1e995ba39908d86 (diff) |
[ios] replace deprecated NSURLConnection with NSURLSession
Diffstat (limited to 'platform')
-rw-r--r-- | platform/CMakeLists.txt | 1 | ||||
-rw-r--r-- | platform/http_client_apple.mm | 40 | ||||
-rw-r--r-- | platform/http_session_manager.h | 12 | ||||
-rw-r--r-- | platform/http_session_manager.mm | 183 | ||||
-rw-r--r-- | platform/http_thread_apple.h | 17 | ||||
-rw-r--r-- | platform/http_thread_apple.mm | 270 |
6 files changed, 361 insertions, 162 deletions
diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 5f66116e94..e95d80aac6 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -127,6 +127,7 @@ else() # neither iPhone nor Android platform_unix_impl.hpp secure_storage_qt.cpp socket_apple.mm + http_session_manager.mm ) elseif(${PLATFORM_LINUX}) append( diff --git a/platform/http_client_apple.mm b/platform/http_client_apple.mm index 4367ded321..ff2720f2b4 100644 --- a/platform/http_client_apple.mm +++ b/platform/http_client_apple.mm @@ -34,10 +34,11 @@ extern NSString * gBrowserUserAgent; #endif #include "platform/http_client.hpp" +#import "platform/http_session_manager.h" #include "base/logging.hpp" -@interface Connection: NSObject<NSURLSessionDelegate> +@interface Connection : NSObject + (nullable NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error; @@ -55,43 +56,30 @@ extern NSString * gBrowserUserAgent; - (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse * __autoreleasing *)response - error:(NSError * __autoreleasing *)error -{ - NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] - delegate:self - delegateQueue:nil]; + error:(NSError * __autoreleasing *)error { __block NSData * resultData = nil; __block NSURLResponse * resultResponse = nil; __block NSError * resultError = nil; dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); - [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, - NSURLResponse * _Nullable response, - NSError * _Nullable error) - { - resultData = data; - resultResponse = response; - resultError = error; - dispatch_group_leave(group); - }] resume]; + + [[[HttpSessionManager sharedManager] + dataTaskWithRequest:request + delegate:nil + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, + NSError * _Nullable error) { + resultData = data; + resultResponse = response; + resultError = error; + dispatch_group_leave(group); + }] resume]; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); *response = resultResponse; *error = resultError; return resultData; } - -#if DEBUG -- (void)URLSession:(NSURLSession *)session -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * _Nullable credential))completionHandler -{ - NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust]; - completionHandler(NSURLSessionAuthChallengeUseCredential, credential); -} -#endif @end namespace platform diff --git a/platform/http_session_manager.h b/platform/http_session_manager.h new file mode 100644 index 0000000000..74f4b9b0dd --- /dev/null +++ b/platform/http_session_manager.h @@ -0,0 +1,12 @@ +#import <Foundation/Foundation.h> + +@interface HttpSessionManager : NSObject + ++ (HttpSessionManager *)sharedManager; + +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + delegate:(id<NSURLSessionDataDelegate>)delegate + completionHandler:(void (^)(NSData * data, NSURLResponse * response, + NSError * error))completionHandler; + +@end diff --git a/platform/http_session_manager.mm b/platform/http_session_manager.mm new file mode 100644 index 0000000000..1aaebf93fd --- /dev/null +++ b/platform/http_session_manager.mm @@ -0,0 +1,183 @@ +#import "platform/http_session_manager.h" + +@interface DataTaskInfo : NSObject + +@property(nonatomic, weak) id<NSURLSessionDataDelegate> delegate; +@property(nonatomic) NSURLSessionDataTask * task; + +- (instancetype)initWithTask:(NSURLSessionDataTask *)task + delegate:(id<NSURLSessionDataDelegate>)delegate; + +@end + +@implementation DataTaskInfo + +- (instancetype)initWithTask:(NSURLSessionDataTask *)task + delegate:(id<NSURLSessionDataDelegate>)delegate +{ + self = [super init]; + if (self) + { + _task = task; + _delegate = delegate; + } + + return self; +} + +@end + +@interface HttpSessionManager ()<NSURLSessionDataDelegate> + +@property(nonatomic) NSURLSession * session; +@property(nonatomic) NSMutableDictionary * taskInfoByTaskID; +@property(nonatomic) dispatch_queue_t taskInfoQueue; + +@end + +@implementation HttpSessionManager + ++ (HttpSessionManager *)sharedManager +{ + static dispatch_once_t sOnceToken; + static HttpSessionManager * sManager; + dispatch_once(&sOnceToken, ^{ + sManager = [[HttpSessionManager alloc] init]; + }); + + return sManager; +} + +- (instancetype)init +{ + self = [super init]; + if (self) + { + _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] + delegate:self + delegateQueue:nil]; + _taskInfoByTaskID = [NSMutableDictionary dictionary]; + _taskInfoQueue = dispatch_queue_create("http_session_manager.queue", DISPATCH_QUEUE_CONCURRENT); + } + + return self; +} + +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + delegate:(id<NSURLSessionDataDelegate>)delegate + completionHandler:(void (^)(NSData * data, NSURLResponse * response, + NSError * error))completionHandler +{ + NSURLSessionDataTask * task = [self.session dataTaskWithRequest:request + completionHandler:completionHandler]; + + DataTaskInfo * taskInfo = [[DataTaskInfo alloc] initWithTask:task delegate:delegate]; + [self setDataTaskInfo:taskInfo forTask:task]; + + return task; +} + +- (void)setDataTaskInfo:(DataTaskInfo *)taskInfo forTask:(NSURLSessionTask *)task +{ + dispatch_barrier_sync(self.taskInfoQueue, ^{ + self.taskInfoByTaskID[task] = taskInfo; + }); +} + +- (DataTaskInfo *)taskInfoForTask:(NSURLSessionTask *)task +{ + __block DataTaskInfo * taskInfo = nil; + dispatch_sync(self.taskInfoQueue, ^{ + taskInfo = self.taskInfoByTaskID[task]; + }); + + return taskInfo; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)newRequest + completionHandler:(void (^)(NSURLRequest *))completionHandler +{ + DataTaskInfo * taskInfo = [self taskInfoForTask:task]; + if ([taskInfo.delegate + respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [taskInfo.delegate URLSession:session + task:task + willPerformHTTPRedirection:response + newRequest:newRequest + completionHandler:completionHandler]; + }); + } + else + { + completionHandler(newRequest); + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error +{ + DataTaskInfo * taskInfo = [self taskInfoForTask:task]; + [self.taskInfoByTaskID removeObjectForKey:@(taskInfo.task.taskIdentifier)]; + + if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [taskInfo.delegate URLSession:session task:task didCompleteWithError:error]; + }); + } +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler +{ + DataTaskInfo * taskInfo = [self taskInfoForTask:dataTask]; + if ([taskInfo.delegate + respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [taskInfo.delegate URLSession:session + dataTask:dataTask + didReceiveResponse:response + completionHandler:completionHandler]; + }); + } + else + { + completionHandler(NSURLSessionResponseAllow); + } +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data +{ + DataTaskInfo * taskInfo = [self taskInfoForTask:dataTask]; + if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveData:data]; + }); + } +} + +#if DEBUG +- (void)URLSession:(NSURLSession *)session + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * _Nullable credential))completionHandler +{ + NSURLCredential * credential = + [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust]; + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); +} +#endif + +@end diff --git a/platform/http_thread_apple.h b/platform/http_thread_apple.h index 19cc40e0ea..e511d35165 100644 --- a/platform/http_thread_apple.h +++ b/platform/http_thread_apple.h @@ -12,18 +12,11 @@ namespace downloader { class IHttpThreadCallback; } #endif @interface HttpThread : NSObject -{ - downloader::IHttpThreadCallback * m_callback; - NSURLConnection * m_connection; - int64_t m_begRange, m_endRange; - int64_t m_downloadedBytes; - int64_t m_expectedSize; -} - -- (id) initWith:(string const &)url callback:(downloader::IHttpThreadCallback &)cb begRange:(int64_t)beg - endRange:(int64_t)end expectedSize:(int64_t)size postBody:(string const &)pb; - -- (void) cancel; + +- (instancetype)initWithURL:(string const &)url callback:(downloader::IHttpThreadCallback &)cb + begRange:(int64_t)beg endRange:(int64_t)end expectedSize:(int64_t)size postBody:(string const &)pb; + +- (void)cancel; #ifdef OMIM_OS_IPHONE + (void)setDownloadIndicatorProtocol:(id<DownloadIndicatorProtocol>)indicator; diff --git a/platform/http_thread_apple.mm b/platform/http_thread_apple.mm index 5a7d8412a9..f37b9b241a 100644 --- a/platform/http_thread_apple.mm +++ b/platform/http_thread_apple.mm @@ -1,6 +1,7 @@ #import "platform/http_thread_apple.h" #include "platform/http_request.hpp" +#import "platform/http_session_manager.h" #include "platform/http_thread_callback.hpp" #include "platform/platform.hpp" @@ -9,6 +10,19 @@ #define TIMEOUT_IN_SECONDS 60.0 +@interface HttpThread ()<NSURLSessionDataDelegate> +{ + downloader::IHttpThreadCallback * m_callback; + NSURLSessionDataTask * m_dataTask; + int64_t m_begRange; + int64_t m_endRange; + int64_t m_downloadedBytes; + int64_t m_expectedSize; + BOOL m_cancelRequested; +} + +@end + @implementation HttpThread #ifdef OMIM_OS_IPHONE @@ -20,126 +34,132 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil; } #endif -- (void) dealloc +- (void)dealloc { LOG(LDEBUG, ("ID:", [self hash], "Connection is destroyed")); - [m_connection cancel]; + [m_dataTask cancel]; #ifdef OMIM_OS_IPHONE [downloadIndicator enableStandby]; [downloadIndicator disableDownloadIndicator]; #endif } -- (void) cancel +- (void)cancel { - [m_connection cancel]; + [m_dataTask cancel]; + m_cancelRequested = true; } -- (id) initWith:(string const &)url callback:(downloader::IHttpThreadCallback &)cb begRange:(int64_t)beg - endRange:(int64_t)end expectedSize:(int64_t)size postBody:(string const &)pb +- (instancetype)initWithURL:(string const &)url + callback:(downloader::IHttpThreadCallback &)cb + begRange:(int64_t)beg + endRange:(int64_t)end + expectedSize:(int64_t)size + postBody:(string const &)pb { self = [super init]; - - m_callback = &cb; - m_begRange = beg; - m_endRange = end; - m_downloadedBytes = 0; - m_expectedSize = size; - - NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL: - static_cast<NSURL *>([NSURL URLWithString:@(url.c_str())]) - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:TIMEOUT_IN_SECONDS]; - - // use Range header only if we don't download whole file from start - if (!(beg == 0 && end < 0)) - { - NSString * val; - if (end > 0) - { - LOG(LDEBUG, (url, "downloading range [", beg, ",", end, "]")); - val = [[NSString alloc] initWithFormat: @"bytes=%qi-%qi", beg, end]; - } - else - { - LOG(LDEBUG, (url, "resuming download from position", beg)); - val = [[NSString alloc] initWithFormat: @"bytes=%qi-", beg]; - } - [request addValue:val forHTTPHeaderField:@"Range"]; - } - - if (!pb.empty()) - { - NSData * postData = [NSData dataWithBytes:pb.data() length:pb.size()]; - [request setHTTPBody:postData]; - [request setHTTPMethod:@"POST"]; - [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - } - // set user-agent with unique client id only for mapswithme requests - if (url.find("mapswithme.com") != string::npos) - { - static string const uid = GetPlatform().UniqueClientId(); - [request addValue:@(uid.c_str()) forHTTPHeaderField:@"User-Agent"]; - } - + + m_callback = &cb; + m_begRange = beg; + m_endRange = end; + m_downloadedBytes = 0; + m_expectedSize = size; + + NSMutableURLRequest * request = + [NSMutableURLRequest requestWithURL:static_cast<NSURL *>([NSURL URLWithString:@(url.c_str())]) + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:TIMEOUT_IN_SECONDS]; + + // use Range header only if we don't download whole file from start + if (!(beg == 0 && end < 0)) + { + NSString * val; + if (end > 0) + { + LOG(LDEBUG, (url, "downloading range [", beg, ",", end, "]")); + val = [[NSString alloc] initWithFormat: @"bytes=%qi-%qi", beg, end]; + } + else + { + LOG(LDEBUG, (url, "resuming download from position", beg)); + val = [[NSString alloc] initWithFormat: @"bytes=%qi-", beg]; + } + [request addValue:val forHTTPHeaderField:@"Range"]; + } + + if (!pb.empty()) + { + NSData * postData = [NSData dataWithBytes:pb.data() length:pb.size()]; + [request setHTTPBody:postData]; + [request setHTTPMethod:@"POST"]; + [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + } + // set user-agent with unique client id only for mapswithme requests + if (url.find("mapswithme.com") != string::npos) + { + static string const uid = GetPlatform().UniqueClientId(); + [request addValue:@(uid.c_str()) forHTTPHeaderField:@"User-Agent"]; + } + #ifdef OMIM_OS_IPHONE [downloadIndicator disableStandby]; [downloadIndicator enableDownloadIndicator]; #endif - - // create the connection with the request and start loading the data - m_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - - if (m_connection == 0) + + // create the task with the request and start loading the data + m_dataTask = [[HttpSessionManager sharedManager] dataTaskWithRequest:request + delegate:self + completionHandler:nil]; + + if (m_dataTask) { - LOG(LERROR, ("Can't create connection for", url)); - return nil; + [m_dataTask resume]; + LOG(LDEBUG, ("ID:", [self hash], "Starting data task for", url)); } else - LOG(LDEBUG, ("ID:", [self hash], "Starting connection to", url)); - + { + LOG(LERROR, ("Can't create data task for", url)); + return nil; + } + return self; } /// We cancel and don't support any redirects to avoid data corruption /// @TODO Display content to user - router is redirecting us somewhere --(NSURLRequest *)connection:(NSURLConnection *)connection - willSendRequest:(NSURLRequest *)request - redirectResponse:(NSURLResponse *)redirectResponse +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { - if (!redirectResponse) - { - // Special case, system just normalizes request, it's not a real redirect - return request; - } - // In all other cases we are cancelling redirects - LOG(LWARNING, - ("Canceling because of redirect from", redirectResponse.URL.absoluteString.UTF8String, "to", - request.URL.absoluteString.UTF8String)); - [connection cancel]; - m_callback->OnFinish(static_cast<NSHTTPURLResponse *>(redirectResponse).statusCode, - m_begRange, m_endRange); - return nil; + LOG(LWARNING, ("Canceling because of redirect from", response.URL.absoluteString.UTF8String, "to", + request.URL.absoluteString.UTF8String)); + completionHandler(nil); + m_callback->OnFinish(static_cast<NSHTTPURLResponse *>(response).statusCode, m_begRange, + m_endRange); } /// @return -1 if can't decode -+ (int64_t) getContentRange:(NSDictionary *)httpHeader ++ (int64_t)getContentRange:(NSDictionary *)httpHeader { - NSString * cr = [httpHeader valueForKey:@"Content-Range"]; - if (cr) - { - NSArray * arr = [cr componentsSeparatedByString:@"/"]; - if (arr && [arr count]) - return [(NSString *)[arr objectAtIndex:[arr count] - 1] longLongValue]; - } - return -1; + if (NSString * cr = [httpHeader valueForKey:@"Content-Range"]) + { + NSArray * arr = [cr componentsSeparatedByString:@"/"]; + if ([arr count]) + return [(NSString *)[arr objectAtIndex:[arr count] - 1] longLongValue]; + } + + return -1; } -- (void) connection: (NSURLConnection *)connection didReceiveResponse: (NSURLResponse *)response +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { - UNUSED_VALUE(connection); - // This method is called when the server has determined that it - // has enough information to create the NSURLResponse. - + // This method is called when the server has determined that it + // has enough information to create the NSURLResponse. + // check if this is OK (not a 404 or the like) if ([response isKindOfClass:[NSHTTPURLResponse class]]) { @@ -151,7 +171,7 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil; if ((isChunk && statusCode != 206) || (!isChunk && statusCode != 200)) { LOG(LWARNING, ("Received invalid HTTP status code, canceling download", statusCode)); - [m_connection cancel]; + completionHandler(NSURLSessionResponseCancel); m_callback->OnFinish(statusCode, m_begRange, m_endRange); return; } @@ -165,47 +185,49 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil; // We should always check returned size, even if it's invalid (-1) if (m_expectedSize != sizeOnServer) { - - LOG(LWARNING, ("Canceling download - server replied with invalid size", - sizeOnServer, "!=", m_expectedSize)); - [m_connection cancel]; + LOG(LWARNING, ("Canceling download - server replied with invalid size", sizeOnServer, + "!=", m_expectedSize)); + completionHandler(NSURLSessionResponseCancel); m_callback->OnFinish(downloader::non_http_error_code::kInconsistentFileSize, m_begRange, m_endRange); return; } } + + completionHandler(NSURLSessionResponseAllow); } else { // In theory, we should never be here. ASSERT(false, ("Invalid non-http response, aborting request")); - [m_connection cancel]; + completionHandler(NSURLSessionResponseCancel); m_callback->OnFinish(downloader::non_http_error_code::kNonHttpResponse, m_begRange, m_endRange); } } -- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { - UNUSED_VALUE(connection); int64_t const length = [data length]; m_downloadedBytes += length; if(!m_callback->OnWrite(m_begRange + m_downloadedBytes - length, [data bytes], length)) { - [m_connection cancel]; + [m_dataTask cancel]; m_callback->OnFinish(downloader::non_http_error_code::kWriteException, m_begRange, m_endRange); } } -- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error -{ - UNUSED_VALUE(connection); - LOG(LWARNING, ("Connection failed", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding])); - m_callback->OnFinish([error code], m_begRange, m_endRange); -} - -- (void) connectionDidFinishLoading:(NSURLConnection *)connection +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { - UNUSED_VALUE(connection); - m_callback->OnFinish(200, m_begRange, m_endRange); + if (error.code == NSURLErrorCancelled || m_cancelRequested) + return; + + if (error) + m_callback->OnFinish([error code], m_begRange, m_endRange); + else + m_callback->OnFinish(200, m_begRange, m_endRange); } @end @@ -213,22 +235,22 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil; /////////////////////////////////////////////////////////////////////////////////////// namespace downloader { -HttpThread * CreateNativeHttpThread(string const & url, - downloader::IHttpThreadCallback & cb, - int64_t beg, - int64_t end, - int64_t size, - string const & pb) -{ - HttpThread * request = [[HttpThread alloc] initWith:url callback:cb begRange:beg endRange:end expectedSize:size postBody:pb]; - CFRetain(reinterpret_cast<void *>(request)); - return request; -} - -void DeleteNativeHttpThread(HttpThread * request) -{ - [request cancel]; - CFRelease(reinterpret_cast<void *>(request)); -} - + HttpThread * CreateNativeHttpThread(string const & url, + downloader::IHttpThreadCallback & cb, + int64_t beg, + int64_t end, + int64_t size, + string const & pb) + { + HttpThread * request = [[HttpThread alloc] initWithURL:url callback:cb begRange:beg endRange:end expectedSize:size postBody:pb]; + CFRetain(reinterpret_cast<void *>(request)); + return request; + } + + void DeleteNativeHttpThread(HttpThread * request) + { + [request cancel]; + CFRelease(reinterpret_cast<void *>(request)); + } + } // namespace downloader |