mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
391 lines
15 KiB
Objective-C
391 lines
15 KiB
Objective-C
// 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/FIRNetwork.h"
|
|
#import "Private/FIRNetworkMessageCode.h"
|
|
|
|
#import "Private/FIRLogger.h"
|
|
#import "Private/FIRMutableDictionary.h"
|
|
#import "Private/FIRNetworkConstants.h"
|
|
#import "Private/FIRReachabilityChecker.h"
|
|
|
|
#import <GoogleToolboxForMac/GTMNSData+zlib.h>
|
|
|
|
/// Constant string for request header Content-Encoding.
|
|
static NSString *const kFIRNetworkContentCompressionKey = @"Content-Encoding";
|
|
|
|
/// Constant string for request header Content-Encoding value.
|
|
static NSString *const kFIRNetworkContentCompressionValue = @"gzip";
|
|
|
|
/// Constant string for request header Content-Length.
|
|
static NSString *const kFIRNetworkContentLengthKey = @"Content-Length";
|
|
|
|
/// Constant string for request header Content-Type.
|
|
static NSString *const kFIRNetworkContentTypeKey = @"Content-Type";
|
|
|
|
/// Constant string for request header Content-Type value.
|
|
static NSString *const kFIRNetworkContentTypeValue = @"application/x-www-form-urlencoded";
|
|
|
|
/// Constant string for GET request method.
|
|
static NSString *const kFIRNetworkGETRequestMethod = @"GET";
|
|
|
|
/// Constant string for POST request method.
|
|
static NSString *const kFIRNetworkPOSTRequestMethod = @"POST";
|
|
|
|
/// Default constant string as a prefix for network logger.
|
|
static NSString *const kFIRNetworkLogTag = @"Firebase/Network";
|
|
|
|
@interface FIRNetwork () <FIRReachabilityDelegate, FIRNetworkLoggerDelegate>
|
|
@end
|
|
|
|
@implementation FIRNetwork {
|
|
/// Network reachability.
|
|
FIRReachabilityChecker *_reachability;
|
|
|
|
/// The dictionary of requests by session IDs { NSString : id }.
|
|
FIRMutableDictionary *_requests;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
return [self initWithReachabilityHost:kFIRNetworkReachabilityHost];
|
|
}
|
|
|
|
- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {
|
|
self = [super init];
|
|
if (self) {
|
|
// Setup reachability.
|
|
_reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self
|
|
loggerDelegate:self
|
|
withHost:reachabilityHost];
|
|
if (![_reachability start]) {
|
|
return nil;
|
|
}
|
|
|
|
_requests = [[FIRMutableDictionary alloc] init];
|
|
_timeoutInterval = kFIRNetworkTimeOutInterval;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
_reachability.reachabilityDelegate = nil;
|
|
[_reachability stop];
|
|
}
|
|
|
|
#pragma mark - External Methods
|
|
|
|
+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
|
|
completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler {
|
|
[FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
|
|
completionHandler:completionHandler];
|
|
}
|
|
|
|
- (NSString *)postURL:(NSURL *)url
|
|
payload:(NSData *)payload
|
|
queue:(dispatch_queue_t)queue
|
|
usingBackgroundSession:(BOOL)usingBackgroundSession
|
|
completionHandler:(FIRNetworkCompletionHandler)handler {
|
|
if (!url.absoluteString.length) {
|
|
[self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
|
|
|
|
NSMutableURLRequest *request =
|
|
[[NSMutableURLRequest alloc] initWithURL:url
|
|
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
|
timeoutInterval:timeOutInterval];
|
|
|
|
if (!request) {
|
|
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
|
|
queue:queue
|
|
withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
NSError *compressError = nil;
|
|
NSData *compressedData = [NSData gtm_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:FIRErrorCodeNetworkPayloadCompression
|
|
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:kFIRNetworkContentLengthKey];
|
|
request.HTTPBody = compressedData;
|
|
request.HTTPMethod = kFIRNetworkPOSTRequestMethod;
|
|
[request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey];
|
|
[request setValue:kFIRNetworkContentCompressionValue
|
|
forHTTPHeaderField:kFIRNetworkContentCompressionKey];
|
|
|
|
FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
|
|
fetcher.backgroundNetworkEnabled = usingBackgroundSession;
|
|
|
|
__weak FIRNetwork *weakSelf = self;
|
|
NSString *requestID = [fetcher
|
|
sessionIDFromAsyncPOSTRequest:request
|
|
completionHandler:^(NSHTTPURLResponse *response, NSData *data,
|
|
NSString *sessionID, NSError *error) {
|
|
FIRNetwork *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:FIRErrorCodeNetworkSessionTaskCreation
|
|
queue:queue
|
|
withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
[self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
|
|
messageCode:kFIRNetworkMessageCodeNetwork000
|
|
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:(FIRNetworkCompletionHandler)handler {
|
|
if (!url.absoluteString.length) {
|
|
[self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
|
|
NSMutableURLRequest *request =
|
|
[[NSMutableURLRequest alloc] initWithURL:url
|
|
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
|
timeoutInterval:timeOutInterval];
|
|
|
|
if (!request) {
|
|
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
|
|
queue:queue
|
|
withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
request.HTTPMethod = kFIRNetworkGETRequestMethod;
|
|
request.allHTTPHeaderFields = headers;
|
|
|
|
FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
|
|
fetcher.backgroundNetworkEnabled = usingBackgroundSession;
|
|
|
|
__weak FIRNetwork *weakSelf = self;
|
|
NSString *requestID = [fetcher
|
|
sessionIDFromAsyncGETRequest:request
|
|
completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
|
|
NSError *error) {
|
|
FIRNetwork *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:FIRErrorCodeNetworkSessionTaskCreation
|
|
queue:queue
|
|
withHandler:handler];
|
|
return nil;
|
|
}
|
|
|
|
[self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
|
|
messageCode:kFIRNetworkMessageCodeNetwork001
|
|
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:(FIRReachabilityChecker *)reachability
|
|
statusChanged:(FIRReachabilityStatus)status {
|
|
_networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi);
|
|
[_reachabilityDelegate reachabilityDidChange];
|
|
}
|
|
|
|
#pragma mark - Network logger delegate
|
|
|
|
- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)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(firNetwork_logWithLevel:messageCode:message:contexts:)] ||
|
|
![loggerDelegate
|
|
respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] ||
|
|
!
|
|
[loggerDelegate respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) {
|
|
FIRLogError(kFIRLoggerAnalytics,
|
|
[NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002],
|
|
@"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:(FIRNetworkCompletionHandler)handler {
|
|
NSDictionary *userInfo = @{kFIRNetworkErrorContext : @"Failed to create network request"};
|
|
NSError *error =
|
|
[[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo];
|
|
[self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
|
|
messageCode:kFIRNetworkMessageCodeNetwork002
|
|
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)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
|
|
messageCode:(FIRNetworkMessageCode)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 firNetwork_logWithLevel:logLevel
|
|
messageCode:messageCode
|
|
message:message
|
|
contexts:contexts];
|
|
return;
|
|
}
|
|
if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError ||
|
|
logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) {
|
|
NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts);
|
|
NSLog(@"%@", formattedMessage);
|
|
FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore,
|
|
[NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
|
|
messageCode:(FIRNetworkMessageCode)messageCode
|
|
message:(NSString *)message
|
|
context:(id)context {
|
|
if (_loggerDelegate) {
|
|
[_loggerDelegate firNetwork_logWithLevel:logLevel
|
|
messageCode:messageCode
|
|
message:message
|
|
context:context];
|
|
return;
|
|
}
|
|
NSArray *contexts = context ? @[ context ] : @[];
|
|
[self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
|
|
}
|
|
|
|
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
|
|
messageCode:(FIRNetworkMessageCode)messageCode
|
|
message:(NSString *)message {
|
|
if (_loggerDelegate) {
|
|
[_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
|
|
return;
|
|
}
|
|
[self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
|
|
}
|
|
|
|
/// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR").
|
|
static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) {
|
|
static NSDictionary *levelNames = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
levelNames = @{
|
|
@(kFIRNetworkLogLevelError) : @"ERROR",
|
|
@(kFIRNetworkLogLevelWarning) : @"WARNING",
|
|
@(kFIRNetworkLogLevelInfo) : @"INFO",
|
|
@(kFIRNetworkLogLevelDebug) : @"DEBUG"
|
|
};
|
|
});
|
|
return levelNames[@(logLevel)];
|
|
}
|
|
|
|
/// Returns a formatted string to be used for console logging.
|
|
static NSString *FIRStringWithLogMessage(NSString *message,
|
|
FIRNetworkLogLevel logLevel,
|
|
NSArray *contexts) {
|
|
if (!message) {
|
|
message = @"(Message was nil)";
|
|
} else if (!message.length) {
|
|
message = @"(Message was empty)";
|
|
}
|
|
NSMutableString *result = [[NSMutableString alloc]
|
|
initWithFormat:@"<%@/%@> %@", kFIRNetworkLogTag, FIRLogLevelDescriptionFromLogLevel(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
|