GULNetwork.m 15 KB

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