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