aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Utilities/Network/GULNetwork.m
diff options
context:
space:
mode:
Diffstat (limited to 'Firebase/Utilities/Network/GULNetwork.m')
-rw-r--r--Firebase/Utilities/Network/GULNetwork.m388
1 files changed, 388 insertions, 0 deletions
diff --git a/Firebase/Utilities/Network/GULNetwork.m b/Firebase/Utilities/Network/GULNetwork.m
new file mode 100644
index 0000000..233500b
--- /dev/null
+++ b/Firebase/Utilities/Network/GULNetwork.m
@@ -0,0 +1,388 @@
+// 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 "Private/GULNetwork.h"
+#import "Private/GULNetworkMessageCode.h"
+
+#import <GoogleUtilities/GULLogger.h>
+#import <GoogleUtilities/GULNSData+zlib.h>
+#import <GoogleUtilities/GULReachabilityChecker.h>
+#import "Private/GULMutableDictionary.h"
+#import "Private/GULNetworkConstants.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:self
+ withHost: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 *)sessionID
+ completionHandler:(GULNetworkSystemCompletionHandler)completionHandler {
+ [GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
+ completionHandler:completionHandler];
+}
+
+- (NSString *)postURL:(NSURL *)url
+ payload:(NSData *)payload
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(GULNetworkCompletionHandler)handler {
+ if (!url.absoluteString.length) {
+ [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
+ return nil;
+ }
+
+ NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;
+
+ NSMutableURLRequest *request =
+ [[NSMutableURLRequest alloc] initWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:timeOutInterval];
+
+ if (!request) {
+ [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler: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:GULErrorCodeNetworkPayloadCompression
+ queue:queue
+ withHandler: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:kGULNetworkContentCompressionValue
+ forHTTPHeaderField:kGULNetworkContentCompressionKey];
+
+ GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
+ fetcher.backgroundNetworkEnabled = usingBackgroundSession;
+
+ __weak GULNetwork *weakSelf = self;
+ NSString *requestID = [fetcher
+ sessionIDFromAsyncPOSTRequest:request
+ completionHandler:^(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:GULErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
+ messageCode:kGULNetworkMessageCodeNetwork000
+ message:@"Uploading data. Host"
+ context:url];
+ _requests[requestID] = fetcher;
+ return requestID;
+}
+
+- (NSString *)getURL:(NSURL *)url
+ headers:(NSDictionary *)headers
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(GULNetworkCompletionHandler)handler {
+ if (!url.absoluteString.length) {
+ [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
+ return nil;
+ }
+
+ NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;
+ NSMutableURLRequest *request =
+ [[NSMutableURLRequest alloc] initWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:timeOutInterval];
+
+ if (!request) {
+ [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ request.HTTPMethod = kGULNetworkGETRequestMethod;
+ request.allHTTPHeaderFields = headers;
+
+ GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
+ fetcher.backgroundNetworkEnabled = usingBackgroundSession;
+
+ __weak GULNetwork *weakSelf = self;
+ NSString *requestID = [fetcher
+ sessionIDFromAsyncGETRequest:request
+ completionHandler:^(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:GULErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
+ messageCode:kGULNetworkMessageCodeNetwork001
+ message:@"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 *)reachability
+ statusChanged:(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)code
+ queue:(dispatch_queue_t)queue
+ withHandler:(GULNetworkCompletionHandler)handler {
+ NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"};
+ NSError *error =
+ [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:code userInfo:userInfo];
+ [self GULNetwork_logWithLevel:kGULNetworkLogLevelWarning
+ messageCode:kGULNetworkMessageCodeNetwork002
+ message:@"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)logLevel
+ messageCode:(GULNetworkMessageCode)messageCode
+ message:(NSString *)message
+ contexts:(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:logLevel
+ messageCode:messageCode
+ message:message
+ contexts: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)logLevel
+ messageCode:(GULNetworkMessageCode)messageCode
+ message:(NSString *)message
+ context:(id)context {
+ if (_loggerDelegate) {
+ [_loggerDelegate GULNetwork_logWithLevel:logLevel
+ messageCode:messageCode
+ message:message
+ context:context];
+ return;
+ }
+ NSArray *contexts = context ? @[ context ] : @[];
+ [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
+}
+
+- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel
+ messageCode:(GULNetworkMessageCode)messageCode
+ message:(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