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/FIRMutableDictionary.h"
  17. #import "Private/FIRNetworkConstants.h"
  18. #import "Private/FIRReachabilityChecker.h"
  19. #import "Private/FIRLogger.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 =
  53. [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self
  54. loggerDelegate:self
  55. withHost:reachabilityHost];
  56. if (![_reachability start]) {
  57. return nil;
  58. }
  59. _requests = [[FIRMutableDictionary alloc] init];
  60. _timeoutInterval = kFIRNetworkTimeOutInterval;
  61. }
  62. return self;
  63. }
  64. - (void)dealloc {
  65. _reachability.reachabilityDelegate = nil;
  66. [_reachability stop];
  67. }
  68. #pragma mark - External Methods
  69. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
  70. completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler {
  71. [FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
  72. completionHandler:completionHandler];
  73. }
  74. - (NSString *)postURL:(NSURL *)url
  75. payload:(NSData *)payload
  76. queue:(dispatch_queue_t)queue
  77. usingBackgroundSession:(BOOL)usingBackgroundSession
  78. completionHandler:(FIRNetworkCompletionHandler)handler {
  79. if (!url.absoluteString.length) {
  80. [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  81. return nil;
  82. }
  83. NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
  84. NSMutableURLRequest *request =
  85. [[NSMutableURLRequest alloc] initWithURL:url
  86. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  87. timeoutInterval:timeOutInterval];
  88. if (!request) {
  89. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  90. queue:queue
  91. withHandler:handler];
  92. return nil;
  93. }
  94. NSError *compressError = nil;
  95. NSData *compressedData = [NSData gtm_dataByGzippingData:payload error:&compressError];
  96. if (!compressedData || compressError) {
  97. if (compressError || payload.length > 0) {
  98. // If the payload is not empty but it fails to compress the payload, something has been wrong.
  99. [self handleErrorWithCode:FIRErrorCodeNetworkPayloadCompression
  100. queue:queue
  101. withHandler:handler];
  102. return nil;
  103. }
  104. compressedData = [[NSData alloc] init];
  105. }
  106. NSString *postLength = @(compressedData.length).stringValue;
  107. // Set up the request with the compressed data.
  108. [request setValue:postLength forHTTPHeaderField:kFIRNetworkContentLengthKey];
  109. request.HTTPBody = compressedData;
  110. request.HTTPMethod = kFIRNetworkPOSTRequestMethod;
  111. [request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey];
  112. [request setValue:kFIRNetworkContentCompressionValue
  113. forHTTPHeaderField:kFIRNetworkContentCompressionKey];
  114. FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  115. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  116. __weak FIRNetwork *weakSelf = self;
  117. NSString *requestID = [fetcher
  118. sessionIDFromAsyncPOSTRequest:request
  119. completionHandler:^(NSHTTPURLResponse *response, NSData *data,
  120. NSString *sessionID, NSError *error) {
  121. FIRNetwork *strongSelf = weakSelf;
  122. if (!strongSelf) {
  123. return;
  124. }
  125. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  126. dispatch_async(queueToDispatch, ^{
  127. if (sessionID.length) {
  128. [strongSelf->_requests removeObjectForKey:sessionID];
  129. }
  130. if (handler) {
  131. handler(response, data, error);
  132. }
  133. });
  134. }];
  135. if (!requestID) {
  136. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  137. queue:queue
  138. withHandler:handler];
  139. return nil;
  140. }
  141. [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  142. messageCode:kFIRNetworkMessageCodeNetwork000
  143. message:@"Uploading data. Host"
  144. context:url];
  145. _requests[requestID] = fetcher;
  146. return requestID;
  147. }
  148. - (NSString *)getURL:(NSURL *)url
  149. headers:(NSDictionary *)headers
  150. queue:(dispatch_queue_t)queue
  151. usingBackgroundSession:(BOOL)usingBackgroundSession
  152. completionHandler:(FIRNetworkCompletionHandler)handler {
  153. if (!url.absoluteString.length) {
  154. [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  155. return nil;
  156. }
  157. NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
  158. NSMutableURLRequest *request =
  159. [[NSMutableURLRequest alloc] initWithURL:url
  160. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  161. timeoutInterval:timeOutInterval];
  162. if (!request) {
  163. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  164. queue:queue
  165. withHandler:handler];
  166. return nil;
  167. }
  168. request.HTTPMethod = kFIRNetworkGETRequestMethod;
  169. request.allHTTPHeaderFields = headers;
  170. FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  171. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  172. __weak FIRNetwork *weakSelf = self;
  173. NSString *requestID = [fetcher
  174. sessionIDFromAsyncGETRequest:request
  175. completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
  176. NSError *error) {
  177. FIRNetwork *strongSelf = weakSelf;
  178. if (!strongSelf) {
  179. return;
  180. }
  181. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  182. dispatch_async(queueToDispatch, ^{
  183. if (sessionID.length) {
  184. [strongSelf->_requests removeObjectForKey:sessionID];
  185. }
  186. if (handler) {
  187. handler(response, data, error);
  188. }
  189. });
  190. }];
  191. if (!requestID) {
  192. [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
  193. queue:queue
  194. withHandler:handler];
  195. return nil;
  196. }
  197. [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  198. messageCode:kFIRNetworkMessageCodeNetwork001
  199. message:@"Downloading data. Host"
  200. context:url];
  201. _requests[requestID] = fetcher;
  202. return requestID;
  203. }
  204. - (BOOL)hasUploadInProgress {
  205. return _requests.count > 0;
  206. }
  207. #pragma mark - Network Reachability
  208. /// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network
  209. /// reachability has changed.
  210. - (void)reachability:(FIRReachabilityChecker *)reachability
  211. statusChanged:(FIRReachabilityStatus)status {
  212. _networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi);
  213. [_reachabilityDelegate reachabilityDidChange];
  214. }
  215. #pragma mark - Network logger delegate
  216. - (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
  217. // Explicitly check whether the delegate responds to the methods because conformsToProtocol does
  218. // not work correctly even though the delegate does respond to the methods.
  219. if (!loggerDelegate ||
  220. ![loggerDelegate
  221. respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:contexts:)] ||
  222. ![loggerDelegate
  223. respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] ||
  224. ![loggerDelegate
  225. respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) {
  226. FIRLogError(kFIRLoggerAnalytics,
  227. [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002],
  228. @"Cannot set the network logger delegate: delegate does not conform to the network "
  229. "logger protocol.");
  230. return;
  231. }
  232. _loggerDelegate = loggerDelegate;
  233. }
  234. #pragma mark - Private methods
  235. /// Handles network error and calls completion handler with the error.
  236. - (void)handleErrorWithCode:(NSInteger)code
  237. queue:(dispatch_queue_t)queue
  238. withHandler:(FIRNetworkCompletionHandler)handler {
  239. NSDictionary *userInfo = @{ kFIRNetworkErrorContext : @"Failed to create network request" };
  240. NSError *error =
  241. [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo];
  242. [self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
  243. messageCode:kFIRNetworkMessageCodeNetwork002
  244. message:@"Failed to create network request. Code, error"
  245. contexts:@[ @(code), error ]];
  246. if (handler) {
  247. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  248. dispatch_async(queueToDispatch, ^{
  249. handler(nil, nil, error);
  250. });
  251. }
  252. }
  253. #pragma mark - Network logger
  254. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  255. messageCode:(FIRNetworkMessageCode)messageCode
  256. message:(NSString *)message
  257. contexts:(NSArray *)contexts {
  258. // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log
  259. // errors/warnings/info messages to the console log.
  260. if (_loggerDelegate) {
  261. [_loggerDelegate firNetwork_logWithLevel:logLevel
  262. messageCode:messageCode
  263. message:message
  264. contexts:contexts];
  265. return;
  266. }
  267. if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError ||
  268. logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) {
  269. NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts);
  270. NSLog(@"%@", formattedMessage);
  271. FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore,
  272. [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
  273. NULL);
  274. }
  275. }
  276. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  277. messageCode:(FIRNetworkMessageCode)messageCode
  278. message:(NSString *)message
  279. context:(id)context {
  280. if (_loggerDelegate) {
  281. [_loggerDelegate firNetwork_logWithLevel:logLevel
  282. messageCode:messageCode
  283. message:message
  284. context:context];
  285. return;
  286. }
  287. NSArray *contexts = context ? @[ context ] : @[];
  288. [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
  289. }
  290. - (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
  291. messageCode:(FIRNetworkMessageCode)messageCode
  292. message:(NSString *)message {
  293. if (_loggerDelegate) {
  294. [_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
  295. return;
  296. }
  297. [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
  298. }
  299. /// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR").
  300. static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) {
  301. static NSDictionary *levelNames = nil;
  302. static dispatch_once_t onceToken;
  303. dispatch_once(&onceToken, ^{
  304. levelNames = @{
  305. @(kFIRNetworkLogLevelError) : @"ERROR",
  306. @(kFIRNetworkLogLevelWarning) : @"WARNING",
  307. @(kFIRNetworkLogLevelInfo) : @"INFO",
  308. @(kFIRNetworkLogLevelDebug) : @"DEBUG"
  309. };
  310. });
  311. return levelNames[@(logLevel)];
  312. }
  313. /// Returns a formatted string to be used for console logging.
  314. static NSString *FIRStringWithLogMessage(NSString *message, 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