AutorÃa | Ultima modificación | Ver Log |
// Copyright 2017 Google//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h"#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h"#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"#import "GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h"#import "GoogleUtilities/Network/GULNetworkInternal.h"#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h"#import "GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h"/// Constant string for request header Content-Encoding.static NSString *const kGULNetworkContentCompressionKey = @"Content-Encoding";/// Constant string for request header Content-Encoding value.static NSString *const kGULNetworkContentCompressionValue = @"gzip";/// Constant string for request header Content-Length.static NSString *const kGULNetworkContentLengthKey = @"Content-Length";/// Constant string for request header Content-Type.static NSString *const kGULNetworkContentTypeKey = @"Content-Type";/// Constant string for request header Content-Type value.static NSString *const kGULNetworkContentTypeValue = @"application/x-www-form-urlencoded";/// Constant string for GET request method.static NSString *const kGULNetworkGETRequestMethod = @"GET";/// Constant string for POST request method.static NSString *const kGULNetworkPOSTRequestMethod = @"POST";/// Default constant string as a prefix for network logger.static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";@interface GULNetwork () <GULReachabilityDelegate, GULNetworkLoggerDelegate>@end@implementation GULNetwork {/// Network reachability.GULReachabilityChecker *_reachability;/// The dictionary of requests by session IDs { NSString : id }.GULMutableDictionary *_requests;}- (instancetype)init {return [self initWithReachabilityHost:kGULNetworkReachabilityHost];}- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {self = [super init];if (self) {// Setup reachability._reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:selfwithHost:reachabilityHost];if (![_reachability start]) {return nil;}_requests = [[GULMutableDictionary alloc] init];_timeoutInterval = kGULNetworkTimeOutInterval;}return self;}- (void)dealloc {_reachability.reachabilityDelegate = nil;[_reachability stop];}#pragma mark - External Methods+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionIDcompletionHandler:(GULNetworkSystemCompletionHandler)completionHandler {[GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionIDcompletionHandler:completionHandler];}- (NSString *)postURL:(NSURL *)urlpayload:(NSData *)payloadqueue:(dispatch_queue_t)queueusingBackgroundSession:(BOOL)usingBackgroundSessioncompletionHandler:(GULNetworkCompletionHandler)handler {if (!url.absoluteString.length) {[self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];return nil;}NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;NSMutableURLRequest *request =[[NSMutableURLRequest alloc] initWithURL:urlcachePolicy:NSURLRequestReloadIgnoringLocalCacheDatatimeoutInterval:timeOutInterval];if (!request) {[self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreationqueue:queuewithHandler:handler];return nil;}NSError *compressError = nil;NSData *compressedData = [NSData gul_dataByGzippingData:payload error:&compressError];if (!compressedData || compressError) {if (compressError || payload.length > 0) {// If the payload is not empty but it fails to compress the payload, something has been wrong.[self handleErrorWithCode:GULErrorCodeNetworkPayloadCompressionqueue:queuewithHandler:handler];return nil;}compressedData = [[NSData alloc] init];}NSString *postLength = @(compressedData.length).stringValue;// Set up the request with the compressed data.[request setValue:postLength forHTTPHeaderField:kGULNetworkContentLengthKey];request.HTTPBody = compressedData;request.HTTPMethod = kGULNetworkPOSTRequestMethod;[request setValue:kGULNetworkContentTypeValue forHTTPHeaderField:kGULNetworkContentTypeKey];[request setValue:kGULNetworkContentCompressionValueforHTTPHeaderField:kGULNetworkContentCompressionKey];GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];fetcher.backgroundNetworkEnabled = usingBackgroundSession;__weak GULNetwork *weakSelf = self;NSString *requestID = [fetchersessionIDFromAsyncPOSTRequest:requestcompletionHandler:^(NSHTTPURLResponse *response, NSData *data,NSString *sessionID, NSError *error) {GULNetwork *strongSelf = weakSelf;if (!strongSelf) {return;}dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();dispatch_async(queueToDispatch, ^{if (sessionID.length) {[strongSelf->_requests removeObjectForKey:sessionID];}if (handler) {handler(response, data, error);}});}];if (!requestID) {[self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreationqueue:queuewithHandler:handler];return nil;}[self GULNetwork_logWithLevel:kGULNetworkLogLevelDebugmessageCode:kGULNetworkMessageCodeNetwork000message:@"Uploading data. Host"context:url];_requests[requestID] = fetcher;return requestID;}- (NSString *)getURL:(NSURL *)urlheaders:(NSDictionary *)headersqueue:(dispatch_queue_t)queueusingBackgroundSession:(BOOL)usingBackgroundSessioncompletionHandler:(GULNetworkCompletionHandler)handler {if (!url.absoluteString.length) {[self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];return nil;}NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;NSMutableURLRequest *request =[[NSMutableURLRequest alloc] initWithURL:urlcachePolicy:NSURLRequestReloadIgnoringLocalCacheDatatimeoutInterval:timeOutInterval];if (!request) {[self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreationqueue:queuewithHandler:handler];return nil;}request.HTTPMethod = kGULNetworkGETRequestMethod;request.allHTTPHeaderFields = headers;GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];fetcher.backgroundNetworkEnabled = usingBackgroundSession;__weak GULNetwork *weakSelf = self;NSString *requestID = [fetchersessionIDFromAsyncGETRequest:requestcompletionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,NSError *error) {GULNetwork *strongSelf = weakSelf;if (!strongSelf) {return;}dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();dispatch_async(queueToDispatch, ^{if (sessionID.length) {[strongSelf->_requests removeObjectForKey:sessionID];}if (handler) {handler(response, data, error);}});}];if (!requestID) {[self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreationqueue:queuewithHandler:handler];return nil;}[self GULNetwork_logWithLevel:kGULNetworkLogLevelDebugmessageCode:kGULNetworkMessageCodeNetwork001message:@"Downloading data. Host"context:url];_requests[requestID] = fetcher;return requestID;}- (BOOL)hasUploadInProgress {return _requests.count > 0;}#pragma mark - Network Reachability/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network/// reachability has changed.- (void)reachability:(GULReachabilityChecker *)reachabilitystatusChanged:(GULReachabilityStatus)status {_networkConnected = (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi);[_reachabilityDelegate reachabilityDidChange];}#pragma mark - Network logger delegate- (void)setLoggerDelegate:(id<GULNetworkLoggerDelegate>)loggerDelegate {// Explicitly check whether the delegate responds to the methods because conformsToProtocol does// not work correctly even though the delegate does respond to the methods.if (!loggerDelegate ||![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:contexts:)] ||![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:context:)] ||![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:)]) {GULLogError(kGULLoggerNetwork, NO,[NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002],@"Cannot set the network logger delegate: delegate does not conform to the network ""logger protocol.");return;}_loggerDelegate = loggerDelegate;}#pragma mark - Private methods/// Handles network error and calls completion handler with the error.- (void)handleErrorWithCode:(NSInteger)codequeue:(dispatch_queue_t)queuewithHandler:(GULNetworkCompletionHandler)handler {NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"};NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomaincode:codeuserInfo:userInfo];[self GULNetwork_logWithLevel:kGULNetworkLogLevelWarningmessageCode:kGULNetworkMessageCodeNetwork002message:@"Failed to create network request. Code, error"contexts:@[ @(code), error ]];if (handler) {dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();dispatch_async(queueToDispatch, ^{handler(nil, nil, error);});}}#pragma mark - Network logger- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevelmessageCode:(GULNetworkMessageCode)messageCodemessage:(NSString *)messagecontexts:(NSArray *)contexts {// Let the delegate log the message if there is a valid logger delegate. Otherwise, just log// errors/warnings/info messages to the console log.if (_loggerDelegate) {[_loggerDelegate GULNetwork_logWithLevel:logLevelmessageCode:messageCodemessage:messagecontexts:contexts];return;}if (_isDebugModeEnabled || logLevel == kGULNetworkLogLevelError ||logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) {NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts);NSLog(@"%@", formattedMessage);GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO,[NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,NULL);}}- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevelmessageCode:(GULNetworkMessageCode)messageCodemessage:(NSString *)messagecontext:(id)context {if (_loggerDelegate) {[_loggerDelegate GULNetwork_logWithLevel:logLevelmessageCode:messageCodemessage:messagecontext:context];return;}NSArray *contexts = context ? @[ context ] : @[];[self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];}- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevelmessageCode:(GULNetworkMessageCode)messageCodemessage:(NSString *)message {if (_loggerDelegate) {[_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message];return;}[self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];}/// Returns a string for the given log level (e.g. kGULNetworkLogLevelError -> @"ERROR").static NSString *GULLogLevelDescriptionFromLogLevel(GULNetworkLogLevel logLevel) {static NSDictionary *levelNames = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{levelNames = @{@(kGULNetworkLogLevelError) : @"ERROR",@(kGULNetworkLogLevelWarning) : @"WARNING",@(kGULNetworkLogLevelInfo) : @"INFO",@(kGULNetworkLogLevelDebug) : @"DEBUG"};});return levelNames[@(logLevel)];}/// Returns a formatted string to be used for console logging.static NSString *GULStringWithLogMessage(NSString *message,GULNetworkLogLevel logLevel,NSArray *contexts) {if (!message) {message = @"(Message was nil)";} else if (!message.length) {message = @"(Message was empty)";}NSMutableString *result = [[NSMutableString alloc]initWithFormat:@"<%@/%@> %@", kGULNetworkLogTag, GULLogLevelDescriptionFromLogLevel(logLevel),message];if (!contexts.count) {return result;}NSMutableArray *formattedContexts = [[NSMutableArray alloc] init];for (id item in contexts) {[formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")];}[result appendString:@": "];[result appendString:[formattedContexts componentsJoinedByString:@", "]];return result;}@end