GULNetwork.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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 "GoogleUtilities/Network/Private/GULNetwork.h"
  15. #import "GoogleUtilities/Network/Private/GULNetworkMessageCode.h"
  16. #if SWIFT_PACKAGE
  17. @import GoogleUtilities_NSData;
  18. @import GoogleUtilities_Reachability;
  19. // SwiftPM doesn't work with private headers in a framework.
  20. #import "GoogleUtilities/Logger/Private/GULLogger.h"
  21. #else
  22. #import <GoogleUtilities/GULLogger.h>
  23. #import <GoogleUtilities/GULNSData+zlib.h>
  24. #import <GoogleUtilities/GULReachabilityChecker.h>
  25. #endif
  26. #import "GoogleUtilities/Network/Private/GULMutableDictionary.h"
  27. #import "GoogleUtilities/Network/Private/GULNetworkConstants.h"
  28. /// Constant string for request header Content-Encoding.
  29. static NSString *const kGULNetworkContentCompressionKey = @"Content-Encoding";
  30. /// Constant string for request header Content-Encoding value.
  31. static NSString *const kGULNetworkContentCompressionValue = @"gzip";
  32. /// Constant string for request header Content-Length.
  33. static NSString *const kGULNetworkContentLengthKey = @"Content-Length";
  34. /// Constant string for request header Content-Type.
  35. static NSString *const kGULNetworkContentTypeKey = @"Content-Type";
  36. /// Constant string for request header Content-Type value.
  37. static NSString *const kGULNetworkContentTypeValue = @"application/x-www-form-urlencoded";
  38. /// Constant string for GET request method.
  39. static NSString *const kGULNetworkGETRequestMethod = @"GET";
  40. /// Constant string for POST request method.
  41. static NSString *const kGULNetworkPOSTRequestMethod = @"POST";
  42. /// Default constant string as a prefix for network logger.
  43. static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
  44. @interface GULNetwork () <GULReachabilityDelegate, GULNetworkLoggerDelegate>
  45. @end
  46. @implementation GULNetwork {
  47. /// Network reachability.
  48. GULReachabilityChecker *_reachability;
  49. /// The dictionary of requests by session IDs { NSString : id }.
  50. GULMutableDictionary *_requests;
  51. }
  52. - (instancetype)init {
  53. return [self initWithReachabilityHost:kGULNetworkReachabilityHost];
  54. }
  55. - (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {
  56. self = [super init];
  57. if (self) {
  58. // Setup reachability.
  59. _reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self
  60. withHost:reachabilityHost];
  61. if (![_reachability start]) {
  62. return nil;
  63. }
  64. _requests = [[GULMutableDictionary alloc] init];
  65. _timeoutInterval = kGULNetworkTimeOutInterval;
  66. }
  67. return self;
  68. }
  69. - (void)dealloc {
  70. _reachability.reachabilityDelegate = nil;
  71. [_reachability stop];
  72. }
  73. #pragma mark - External Methods
  74. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
  75. completionHandler:(GULNetworkSystemCompletionHandler)completionHandler {
  76. [GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
  77. completionHandler:completionHandler];
  78. }
  79. - (NSString *)postURL:(NSURL *)url
  80. payload:(NSData *)payload
  81. queue:(dispatch_queue_t)queue
  82. usingBackgroundSession:(BOOL)usingBackgroundSession
  83. completionHandler:(GULNetworkCompletionHandler)handler {
  84. if (!url.absoluteString.length) {
  85. [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  86. return nil;
  87. }
  88. NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;
  89. NSMutableURLRequest *request =
  90. [[NSMutableURLRequest alloc] initWithURL:url
  91. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  92. timeoutInterval:timeOutInterval];
  93. if (!request) {
  94. [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
  95. queue:queue
  96. withHandler:handler];
  97. return nil;
  98. }
  99. NSError *compressError = nil;
  100. NSData *compressedData = [NSData gul_dataByGzippingData:payload error:&compressError];
  101. if (!compressedData || compressError) {
  102. if (compressError || payload.length > 0) {
  103. // If the payload is not empty but it fails to compress the payload, something has been wrong.
  104. [self handleErrorWithCode:GULErrorCodeNetworkPayloadCompression
  105. queue:queue
  106. withHandler:handler];
  107. return nil;
  108. }
  109. compressedData = [[NSData alloc] init];
  110. }
  111. NSString *postLength = @(compressedData.length).stringValue;
  112. // Set up the request with the compressed data.
  113. [request setValue:postLength forHTTPHeaderField:kGULNetworkContentLengthKey];
  114. request.HTTPBody = compressedData;
  115. request.HTTPMethod = kGULNetworkPOSTRequestMethod;
  116. [request setValue:kGULNetworkContentTypeValue forHTTPHeaderField:kGULNetworkContentTypeKey];
  117. [request setValue:kGULNetworkContentCompressionValue
  118. forHTTPHeaderField:kGULNetworkContentCompressionKey];
  119. GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  120. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  121. __weak GULNetwork *weakSelf = self;
  122. NSString *requestID = [fetcher
  123. sessionIDFromAsyncPOSTRequest:request
  124. completionHandler:^(NSHTTPURLResponse *response, NSData *data,
  125. NSString *sessionID, NSError *error) {
  126. GULNetwork *strongSelf = weakSelf;
  127. if (!strongSelf) {
  128. return;
  129. }
  130. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  131. dispatch_async(queueToDispatch, ^{
  132. if (sessionID.length) {
  133. [strongSelf->_requests removeObjectForKey:sessionID];
  134. }
  135. if (handler) {
  136. handler(response, data, error);
  137. }
  138. });
  139. }];
  140. if (!requestID) {
  141. [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
  142. queue:queue
  143. withHandler:handler];
  144. return nil;
  145. }
  146. [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
  147. messageCode:kGULNetworkMessageCodeNetwork000
  148. message:@"Uploading data. Host"
  149. context:url];
  150. _requests[requestID] = fetcher;
  151. return requestID;
  152. }
  153. - (NSString *)getURL:(NSURL *)url
  154. headers:(NSDictionary *)headers
  155. queue:(dispatch_queue_t)queue
  156. usingBackgroundSession:(BOOL)usingBackgroundSession
  157. completionHandler:(GULNetworkCompletionHandler)handler {
  158. if (!url.absoluteString.length) {
  159. [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
  160. return nil;
  161. }
  162. NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval;
  163. NSMutableURLRequest *request =
  164. [[NSMutableURLRequest alloc] initWithURL:url
  165. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  166. timeoutInterval:timeOutInterval];
  167. if (!request) {
  168. [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
  169. queue:queue
  170. withHandler:handler];
  171. return nil;
  172. }
  173. request.HTTPMethod = kGULNetworkGETRequestMethod;
  174. request.allHTTPHeaderFields = headers;
  175. GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
  176. fetcher.backgroundNetworkEnabled = usingBackgroundSession;
  177. __weak GULNetwork *weakSelf = self;
  178. NSString *requestID = [fetcher
  179. sessionIDFromAsyncGETRequest:request
  180. completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
  181. NSError *error) {
  182. GULNetwork *strongSelf = weakSelf;
  183. if (!strongSelf) {
  184. return;
  185. }
  186. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  187. dispatch_async(queueToDispatch, ^{
  188. if (sessionID.length) {
  189. [strongSelf->_requests removeObjectForKey:sessionID];
  190. }
  191. if (handler) {
  192. handler(response, data, error);
  193. }
  194. });
  195. }];
  196. if (!requestID) {
  197. [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation
  198. queue:queue
  199. withHandler:handler];
  200. return nil;
  201. }
  202. [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
  203. messageCode:kGULNetworkMessageCodeNetwork001
  204. message:@"Downloading data. Host"
  205. context:url];
  206. _requests[requestID] = fetcher;
  207. return requestID;
  208. }
  209. - (BOOL)hasUploadInProgress {
  210. return _requests.count > 0;
  211. }
  212. #pragma mark - Network Reachability
  213. /// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network
  214. /// reachability has changed.
  215. - (void)reachability:(GULReachabilityChecker *)reachability
  216. statusChanged:(GULReachabilityStatus)status {
  217. _networkConnected = (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi);
  218. [_reachabilityDelegate reachabilityDidChange];
  219. }
  220. #pragma mark - Network logger delegate
  221. - (void)setLoggerDelegate:(id<GULNetworkLoggerDelegate>)loggerDelegate {
  222. // Explicitly check whether the delegate responds to the methods because conformsToProtocol does
  223. // not work correctly even though the delegate does respond to the methods.
  224. if (!loggerDelegate ||
  225. ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:
  226. messageCode:message:contexts:)] ||
  227. ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:
  228. messageCode:message:context:)] ||
  229. ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:
  230. messageCode:message:)]) {
  231. GULLogError(kGULLoggerNetwork, NO,
  232. [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002],
  233. @"Cannot set the network logger delegate: delegate does not conform to the network "
  234. "logger protocol.");
  235. return;
  236. }
  237. _loggerDelegate = loggerDelegate;
  238. }
  239. #pragma mark - Private methods
  240. /// Handles network error and calls completion handler with the error.
  241. - (void)handleErrorWithCode:(NSInteger)code
  242. queue:(dispatch_queue_t)queue
  243. withHandler:(GULNetworkCompletionHandler)handler {
  244. NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"};
  245. NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain
  246. code:code
  247. userInfo:userInfo];
  248. [self GULNetwork_logWithLevel:kGULNetworkLogLevelWarning
  249. messageCode:kGULNetworkMessageCodeNetwork002
  250. message:@"Failed to create network request. Code, error"
  251. contexts:@[ @(code), error ]];
  252. if (handler) {
  253. dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
  254. dispatch_async(queueToDispatch, ^{
  255. handler(nil, nil, error);
  256. });
  257. }
  258. }
  259. #pragma mark - Network logger
  260. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel
  261. messageCode:(GULNetworkMessageCode)messageCode
  262. message:(NSString *)message
  263. contexts:(NSArray *)contexts {
  264. // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log
  265. // errors/warnings/info messages to the console log.
  266. if (_loggerDelegate) {
  267. [_loggerDelegate GULNetwork_logWithLevel:logLevel
  268. messageCode:messageCode
  269. message:message
  270. contexts:contexts];
  271. return;
  272. }
  273. if (_isDebugModeEnabled || logLevel == kGULNetworkLogLevelError ||
  274. logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) {
  275. NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts);
  276. NSLog(@"%@", formattedMessage);
  277. GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO,
  278. [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
  279. NULL);
  280. }
  281. }
  282. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel
  283. messageCode:(GULNetworkMessageCode)messageCode
  284. message:(NSString *)message
  285. context:(id)context {
  286. if (_loggerDelegate) {
  287. [_loggerDelegate GULNetwork_logWithLevel:logLevel
  288. messageCode:messageCode
  289. message:message
  290. context:context];
  291. return;
  292. }
  293. NSArray *contexts = context ? @[ context ] : @[];
  294. [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
  295. }
  296. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel
  297. messageCode:(GULNetworkMessageCode)messageCode
  298. message:(NSString *)message {
  299. if (_loggerDelegate) {
  300. [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
  301. return;
  302. }
  303. [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
  304. }
  305. /// Returns a string for the given log level (e.g. kGULNetworkLogLevelError -> @"ERROR").
  306. static NSString *GULLogLevelDescriptionFromLogLevel(GULNetworkLogLevel logLevel) {
  307. static NSDictionary *levelNames = nil;
  308. static dispatch_once_t onceToken;
  309. dispatch_once(&onceToken, ^{
  310. levelNames = @{
  311. @(kGULNetworkLogLevelError) : @"ERROR",
  312. @(kGULNetworkLogLevelWarning) : @"WARNING",
  313. @(kGULNetworkLogLevelInfo) : @"INFO",
  314. @(kGULNetworkLogLevelDebug) : @"DEBUG"
  315. };
  316. });
  317. return levelNames[@(logLevel)];
  318. }
  319. /// Returns a formatted string to be used for console logging.
  320. static NSString *GULStringWithLogMessage(NSString *message,
  321. GULNetworkLogLevel logLevel,
  322. NSArray *contexts) {
  323. if (!message) {
  324. message = @"(Message was nil)";
  325. } else if (!message.length) {
  326. message = @"(Message was empty)";
  327. }
  328. NSMutableString *result = [[NSMutableString alloc]
  329. initWithFormat:@"<%@/%@> %@", kGULNetworkLogTag, GULLogLevelDescriptionFromLogLevel(logLevel),
  330. message];
  331. if (!contexts.count) {
  332. return result;
  333. }
  334. NSMutableArray *formattedContexts = [[NSMutableArray alloc] init];
  335. for (id item in contexts) {
  336. [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")];
  337. }
  338. [result appendString:@": "];
  339. [result appendString:[formattedContexts componentsJoinedByString:@", "]];
  340. return result;
  341. }
  342. @end