FIRNetwork.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "Private/FIRNetwork.h"
  15. #import "Private/FIRNetworkMessageCode.h"
  16. #import "Private/FIRLogger.h"
  17. #import "Private/FIRMutableDictionary.h"
  18. #import "Private/FIRNetworkConstants.h"
  19. #import "Private/FIRReachabilityChecker.h"
  20. #import <GoogleToolboxForMac/GTMNSData+zlib.h>
  21. /// Constant string for request header Content-Encoding.
  22. static NSString *const kFIRNetworkContentCompressionKey = @"Content-Encoding";
  23. /// Constant string for request header Content-Encoding value.
  24. static NSString *const kFIRNetworkContentCompressionValue = @"gzip";
  25. /// Constant string for request header Content-Length.
  26. static NSString *const kFIRNetworkContentLengthKey = @"Content-Length";
  27. /// Constant string for request header Content-Type.
  28. static NSString *const kFIRNetworkContentTypeKey = @"Content-Type";
  29. /// Constant string for request header Content-Type value.
  30. static NSString *const kFIRNetworkContentTypeValue = @"application/x-www-form-urlencoded";
  31. /// Constant string for GET request method.
  32. static NSString *const kFIRNetworkGETRequestMethod = @"GET";
  33. /// Constant string for POST request method.
  34. static NSString *const kFIRNetworkPOSTRequestMethod = @"POST";
  35. /// Default constant string as a prefix for network logger.
  36. static NSString *const kFIRNetworkLogTag = @"Firebase/Network";
  37. @interface FIRNetwork () <FIRReachabilityDelegate, FIRNetworkLoggerDelegate>
  38. @end
  39. @implementation FIRNetwork {
  40. /// Network reachability.
  41. FIRReachabilityChecker *_reachability;
  42. /// The dictionary of requests by session IDs { NSString : id }.
  43. FIRMutableDictionary *_requests;
  44. }
  45. - (instancetype)init {
  46. return [self initWithReachabilityHost:kFIRNetworkReachabilityHost];
  47. }
  48. - (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {
  49. self = [super init];
  50. if (self) {
  51. // Setup reachability.
  52. _reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self
  53. loggerDelegate:self
  54. withHost:reachabilityHost];
  55. if (![_reachability start]) {
  56. return nil;
  57. }
  58. _requests = [[FIRMutableDictionary alloc] init];
  59. _timeoutInterval = kFIRNetworkTimeOutInterval;
  60. }
  61. return self;
  62. }
  63. - (void)dealloc {
  64. _reachability.reachabilityDelegate = nil;
  65. [_reachability stop];
  66. }
  67. #pragma mark - External Methods
  68. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
  69. completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler {
  70. [FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
  71. completionHandler:completionHandler];
  72. }
  73. - (NSString *)postURL:(NSURL *)url
  74. payload:(NSData *)payload
  75. queue:(dispatch_queue_t)queue
  76. usingBackgroundSession:(BOOL)usingBackgroundSession
  77. completionHandler:(FIRNetworkCompletionHandler)handler {
  78. if (!url.absoluteString.length) {
  79. [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  80. return nil;
  81. }
  82. NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
  83. NSMutableURLRequest *request =
  84. [[NSMutableURLRequest alloc] initWithURL:url
  85. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  86. timeoutInterval:timeOutInterval];
  87. if (!request) {
  88. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  89. queue:queue
  90. withHandler:handler];
  91. return nil;
  92. }
  93. NSError *compressError = nil;
  94. NSData *compressedData = [NSData gtm_dataByGzippingData:payload error:&compressError];
  95. if (!compressedData || compressError) {
  96. if (compressError || payload.length > 0) {
  97. // If the payload is not empty but it fails to compress the payload, something has been wrong.
  98. [self handleErrorWithCode:FIRErrorCodeNetworkPayloadCompression
  99. queue:queue
  100. withHandler:handler];
  101. return nil;
  102. }
  103. compressedData = [[NSData alloc] init];
  104. }
  105. NSString *postLength = @(compressedData.length).stringValue;
  106. // Set up the request with the compressed data.
  107. [request setValue:postLength forHTTPHeaderField:kFIRNetworkContentLengthKey];
  108. request.HTTPBody = compressedData;
  109. request.HTTPMethod = kFIRNetworkPOSTRequestMethod;
  110. [request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey];
  111. [request setValue:kFIRNetworkContentCompressionValue
  112. forHTTPHeaderField:kFIRNetworkContentCompressionKey];
  113. FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  114. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  115. __weak FIRNetwork *weakSelf = self;
  116. NSString *requestID = [fetcher
  117. sessionIDFromAsyncPOSTRequest:request
  118. completionHandler:^(NSHTTPURLResponse *response, NSData *data,
  119. NSString *sessionID, NSError *error) {
  120. FIRNetwork *strongSelf = weakSelf;
  121. if (!strongSelf) {
  122. return;
  123. }
  124. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  125. dispatch_async(queueToDispatch, ^{
  126. if (sessionID.length) {
  127. [strongSelf->_requests removeObjectForKey:sessionID];
  128. }
  129. if (handler) {
  130. handler(response, data, error);
  131. }
  132. });
  133. }];
  134. if (!requestID) {
  135. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  136. queue:queue
  137. withHandler:handler];
  138. return nil;
  139. }
  140. [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  141. messageCode:kFIRNetworkMessageCodeNetwork000
  142. message:@"Uploading data. Host"
  143. context:url];
  144. _requests[requestID] = fetcher;
  145. return requestID;
  146. }
  147. - (NSString *)getURL:(NSURL *)url
  148. headers:(NSDictionary *)headers
  149. queue:(dispatch_queue_t)queue
  150. usingBackgroundSession:(BOOL)usingBackgroundSession
  151. completionHandler:(FIRNetworkCompletionHandler)handler {
  152. if (!url.absoluteString.length) {
  153. [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  154. return nil;
  155. }
  156. NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
  157. NSMutableURLRequest *request =
  158. [[NSMutableURLRequest alloc] initWithURL:url
  159. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  160. timeoutInterval:timeOutInterval];
  161. if (!request) {
  162. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  163. queue:queue
  164. withHandler:handler];
  165. return nil;
  166. }
  167. request.HTTPMethod = kFIRNetworkGETRequestMethod;
  168. request.allHTTPHeaderFields = headers;
  169. FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  170. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  171. __weak FIRNetwork *weakSelf = self;
  172. NSString *requestID = [fetcher
  173. sessionIDFromAsyncGETRequest:request
  174. completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
  175. NSError *error) {
  176. FIRNetwork *strongSelf = weakSelf;
  177. if (!strongSelf) {
  178. return;
  179. }
  180. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  181. dispatch_async(queueToDispatch, ^{
  182. if (sessionID.length) {
  183. [strongSelf->_requests removeObjectForKey:sessionID];
  184. }
  185. if (handler) {
  186. handler(response, data, error);
  187. }
  188. });
  189. }];
  190. if (!requestID) {
  191. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  192. queue:queue
  193. withHandler:handler];
  194. return nil;
  195. }
  196. [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  197. messageCode:kFIRNetworkMessageCodeNetwork001
  198. message:@"Downloading data. Host"
  199. context:url];
  200. _requests[requestID] = fetcher;
  201. return requestID;
  202. }
  203. - (BOOL)hasUploadInProgress {
  204. return _requests.count > 0;
  205. }
  206. #pragma mark - Network Reachability
  207. /// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network
  208. /// reachability has changed.
  209. - (void)reachability:(FIRReachabilityChecker *)reachability
  210. statusChanged:(FIRReachabilityStatus)status {
  211. _networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi);
  212. [_reachabilityDelegate reachabilityDidChange];
  213. }
  214. #pragma mark - Network logger delegate
  215. - (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
  216. // Explicitly check whether the delegate responds to the methods because conformsToProtocol does
  217. // not work correctly even though the delegate does respond to the methods.
  218. if (!loggerDelegate ||
  219. ![loggerDelegate
  220. respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:contexts:)] ||
  221. ![loggerDelegate
  222. respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] ||
  223. !
  224. [loggerDelegate respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) {
  225. FIRLogError(kFIRLoggerAnalytics,
  226. [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002],
  227. @"Cannot set the network logger delegate: delegate does not conform to the network "
  228. "logger protocol.");
  229. return;
  230. }
  231. _loggerDelegate = loggerDelegate;
  232. }
  233. #pragma mark - Private methods
  234. /// Handles network error and calls completion handler with the error.
  235. - (void)handleErrorWithCode:(NSInteger)code
  236. queue:(dispatch_queue_t)queue
  237. withHandler:(FIRNetworkCompletionHandler)handler {
  238. NSDictionary *userInfo = @{kFIRNetworkErrorContext : @"Failed to create network request"};
  239. NSError *error =
  240. [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo];
  241. [self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
  242. messageCode:kFIRNetworkMessageCodeNetwork002
  243. message:@"Failed to create network request. Code, error"
  244. contexts:@[ @(code), error ]];
  245. if (handler) {
  246. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  247. dispatch_async(queueToDispatch, ^{
  248. handler(nil, nil, error);
  249. });
  250. }
  251. }
  252. #pragma mark - Network logger
  253. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  254. messageCode:(FIRNetworkMessageCode)messageCode
  255. message:(NSString *)message
  256. contexts:(NSArray *)contexts {
  257. // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log
  258. // errors/warnings/info messages to the console log.
  259. if (_loggerDelegate) {
  260. [_loggerDelegate firNetwork_logWithLevel:logLevel
  261. messageCode:messageCode
  262. message:message
  263. contexts:contexts];
  264. return;
  265. }
  266. if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError ||
  267. logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) {
  268. NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts);
  269. NSLog(@"%@", formattedMessage);
  270. FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore,
  271. [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
  272. NULL);
  273. }
  274. }
  275. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  276. messageCode:(FIRNetworkMessageCode)messageCode
  277. message:(NSString *)message
  278. context:(id)context {
  279. if (_loggerDelegate) {
  280. [_loggerDelegate firNetwork_logWithLevel:logLevel
  281. messageCode:messageCode
  282. message:message
  283. context:context];
  284. return;
  285. }
  286. NSArray *contexts = context ? @[ context ] : @[];
  287. [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
  288. }
  289. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  290. messageCode:(FIRNetworkMessageCode)messageCode
  291. message:(NSString *)message {
  292. if (_loggerDelegate) {
  293. [_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
  294. return;
  295. }
  296. [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
  297. }
  298. /// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR").
  299. static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) {
  300. static NSDictionary *levelNames = nil;
  301. static dispatch_once_t onceToken;
  302. dispatch_once(&onceToken, ^{
  303. levelNames = @{
  304. @(kFIRNetworkLogLevelError) : @"ERROR",
  305. @(kFIRNetworkLogLevelWarning) : @"WARNING",
  306. @(kFIRNetworkLogLevelInfo) : @"INFO",
  307. @(kFIRNetworkLogLevelDebug) : @"DEBUG"
  308. };
  309. });
  310. return levelNames[@(logLevel)];
  311. }
  312. /// Returns a formatted string to be used for console logging.
  313. static NSString *FIRStringWithLogMessage(NSString *message,
  314. FIRNetworkLogLevel logLevel,
  315. NSArray *contexts) {
  316. if (!message) {
  317. message = @"(Message was nil)";
  318. } else if (!message.length) {
  319. message = @"(Message was empty)";
  320. }
  321. NSMutableString *result = [[NSMutableString alloc]
  322. initWithFormat:@"<%@/%@> %@", kFIRNetworkLogTag, FIRLogLevelDescriptionFromLogLevel(logLevel),
  323. message];
  324. if (!contexts.count) {
  325. return result;
  326. }
  327. NSMutableArray *formattedContexts = [[NSMutableArray alloc] init];
  328. for (id item in contexts) {
  329. [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")];
  330. }
  331. [result appendString:@": "];
  332. [result appendString:[formattedContexts componentsJoinedByString:@", "]];
  333. return result;
  334. }
  335. @end