You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

290 lines
10 KiB

  1. // Copyright 2019 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 "FIRCLSNetworkResponseHandler.h"
  15. @implementation FIRCLSNetworkResponseHandler
  16. static const NSTimeInterval kFIRCLSNetworkResponseHandlerDefaultRetryInterval = 2.0;
  17. static NSString *const kFIRCLSNetworkResponseHandlerContentType = @"Content-Type";
  18. NSString *const FIRCLSNetworkErrorDomain = @"FIRCLSNetworkError";
  19. NSInteger const FIRCLSNetworkErrorUnknownURLCancelReason = -1;
  20. #pragma mark - Header Handling
  21. + (NSString *)headerForResponse:(NSURLResponse *)response withKey:(NSString *)key {
  22. if (![response respondsToSelector:@selector(allHeaderFields)]) {
  23. return nil;
  24. }
  25. return [((NSHTTPURLResponse *)response).allHeaderFields objectForKey:key];
  26. }
  27. + (NSTimeInterval)retryValueForResponse:(NSURLResponse *)response {
  28. NSString *retryValueString = [self headerForResponse:response withKey:@"Retry-After"];
  29. if (!retryValueString) {
  30. return kFIRCLSNetworkResponseHandlerDefaultRetryInterval;
  31. }
  32. NSTimeInterval value = retryValueString.doubleValue;
  33. if (value < 0.0) {
  34. return kFIRCLSNetworkResponseHandlerDefaultRetryInterval;
  35. }
  36. return value;
  37. }
  38. + (NSString *)requestIdForResponse:(NSURLResponse *)response {
  39. return [self headerForResponse:response withKey:@"X-Request-Id"];
  40. }
  41. + (BOOL)contentTypeForResponse:(NSURLResponse *)response matchesRequest:(NSURLRequest *)request {
  42. NSString *accept = [request.allHTTPHeaderFields objectForKey:@"Accept"];
  43. if (!accept) {
  44. // An omitted accept header is defined to match everything
  45. return YES;
  46. }
  47. NSString *contentHeader = [self.class headerForResponse:response
  48. withKey:kFIRCLSNetworkResponseHandlerContentType];
  49. if (!contentHeader) {
  50. // FIRCLSDeveloperLog("Network", @"Content-Type not present in response");
  51. return NO;
  52. }
  53. NSString *acceptCharset = request.allHTTPHeaderFields[@"Accept-Charset"];
  54. NSArray *parts = [contentHeader componentsSeparatedByString:@"; charset="];
  55. if (!parts) {
  56. parts = @[ contentHeader ];
  57. }
  58. if ([[parts objectAtIndex:0] caseInsensitiveCompare:accept] != NSOrderedSame) {
  59. // FIRCLSDeveloperLog("Network", @"Content-Type does not match Accept");
  60. return NO;
  61. }
  62. if (!acceptCharset) {
  63. return YES;
  64. }
  65. if (parts.count < 2) {
  66. return YES;
  67. }
  68. return [[parts objectAtIndex:1] caseInsensitiveCompare:acceptCharset] == NSOrderedSame;
  69. }
  70. + (NSInteger)cancelReasonFromURLError:(NSError *)error {
  71. if (![[error domain] isEqualToString:NSURLErrorDomain]) {
  72. return FIRCLSNetworkErrorUnknownURLCancelReason;
  73. }
  74. if ([error code] != NSURLErrorCancelled) {
  75. return FIRCLSNetworkErrorUnknownURLCancelReason;
  76. }
  77. NSNumber *reason = [[error userInfo] objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey];
  78. if (reason == nil) {
  79. return FIRCLSNetworkErrorUnknownURLCancelReason;
  80. }
  81. return [reason integerValue];
  82. }
  83. + (BOOL)retryableURLError:(NSError *)error {
  84. // So far, the only task errors seen are NSURLErrorDomain. For others, we're not
  85. // sure what to do.
  86. if (![[error domain] isEqualToString:NSURLErrorDomain]) {
  87. return NO;
  88. }
  89. // cases that we know are definitely not retryable
  90. switch ([error code]) {
  91. case NSURLErrorBadURL:
  92. case NSURLErrorUnsupportedURL:
  93. case NSURLErrorHTTPTooManyRedirects:
  94. case NSURLErrorRedirectToNonExistentLocation:
  95. case NSURLErrorUserCancelledAuthentication:
  96. case NSURLErrorUserAuthenticationRequired:
  97. case NSURLErrorAppTransportSecurityRequiresSecureConnection:
  98. case NSURLErrorFileDoesNotExist:
  99. case NSURLErrorFileIsDirectory:
  100. case NSURLErrorDataLengthExceedsMaximum:
  101. case NSURLErrorSecureConnectionFailed:
  102. case NSURLErrorServerCertificateHasBadDate:
  103. case NSURLErrorServerCertificateUntrusted:
  104. case NSURLErrorServerCertificateHasUnknownRoot:
  105. case NSURLErrorServerCertificateNotYetValid:
  106. case NSURLErrorClientCertificateRejected:
  107. case NSURLErrorClientCertificateRequired:
  108. case NSURLErrorBackgroundSessionRequiresSharedContainer:
  109. return NO;
  110. }
  111. // All other errors, as far as I can tell, are things that could clear up
  112. // without action on the part of the client.
  113. // NSURLErrorCancelled is a potential special-case. I believe there are
  114. // situations where a cancelled request cannot be successfully restarted. But,
  115. // until I can prove it, we'll retry. There are defnitely many cases where
  116. // a cancelled request definitely can be restarted and will work.
  117. return YES;
  118. }
  119. #pragma mark - Error Creation
  120. + (NSError *)errorForCode:(NSInteger)code userInfo:(NSDictionary *)userInfo {
  121. return [NSError errorWithDomain:FIRCLSNetworkErrorDomain code:code userInfo:userInfo];
  122. }
  123. + (NSError *)errorForResponse:(NSURLResponse *)response
  124. ofType:(FIRCLSNetworkClientResponseType)type
  125. status:(NSInteger)status {
  126. if (type == FIRCLSNetworkClientResponseSuccess) {
  127. return nil;
  128. }
  129. NSString *requestId = [self requestIdForResponse:response];
  130. NSString *contentType = [self headerForResponse:response
  131. withKey:kFIRCLSNetworkResponseHandlerContentType];
  132. // this could be nil, so be careful
  133. requestId = requestId ? requestId : @"";
  134. contentType = contentType ? contentType : @"";
  135. NSDictionary *userInfo = @{
  136. @"type" : @(type),
  137. @"status_code" : @(status),
  138. @"request_id" : requestId,
  139. @"content_type" : contentType
  140. };
  141. // compute a reasonable error code type
  142. NSInteger errorCode = FIRCLSNetworkErrorUnknown;
  143. switch (type) {
  144. case FIRCLSNetworkClientResponseFailure:
  145. errorCode = FIRCLSNetworkErrorRequestFailed;
  146. break;
  147. case FIRCLSNetworkClientResponseInvalid:
  148. errorCode = FIRCLSNetworkErrorResponseInvalid;
  149. break;
  150. default:
  151. break;
  152. }
  153. return [self errorForCode:errorCode userInfo:userInfo];
  154. }
  155. + (void)clientResponseType:(NSURLResponse *)response
  156. handler:(void (^)(FIRCLSNetworkClientResponseType type,
  157. NSInteger statusCode))responseTypeAndStatusCodeHandlerBlock {
  158. if (![response respondsToSelector:@selector(statusCode)]) {
  159. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, 0);
  160. return;
  161. }
  162. NSInteger code = ((NSHTTPURLResponse *)response).statusCode;
  163. switch (code) {
  164. case 200:
  165. case 201:
  166. case 202:
  167. case 204:
  168. case 304:
  169. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseSuccess, code);
  170. return;
  171. case 420:
  172. case 429:
  173. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseBackOff, code);
  174. return;
  175. case 408:
  176. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code);
  177. return;
  178. case 400:
  179. case 401:
  180. case 403:
  181. case 404:
  182. case 406:
  183. case 410:
  184. case 411:
  185. case 413:
  186. case 419:
  187. case 422:
  188. case 431:
  189. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseFailure, code);
  190. return;
  191. }
  192. // check for a 5xx
  193. if (code >= 500 && code <= 599) {
  194. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code);
  195. return;
  196. }
  197. responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, code);
  198. }
  199. + (void)handleCompletedResponse:(NSURLResponse *)response
  200. forOriginalRequest:(NSURLRequest *)originalRequest
  201. error:(NSError *)originalError
  202. block:
  203. (FIRCLSNetworkResponseCompletionHandlerBlock)completionHandlerBlock {
  204. // if we have an error, we can just continue
  205. if (originalError) {
  206. BOOL retryable = [self retryableURLError:originalError];
  207. completionHandlerBlock(retryable, originalError);
  208. return;
  209. }
  210. [self.class clientResponseType:response
  211. handler:^(FIRCLSNetworkClientResponseType type, NSInteger statusCode) {
  212. NSError *error = nil;
  213. switch (type) {
  214. case FIRCLSNetworkClientResponseInvalid:
  215. error = [self errorForResponse:response
  216. ofType:type
  217. status:statusCode];
  218. break;
  219. case FIRCLSNetworkClientResponseBackOff:
  220. case FIRCLSNetworkClientResponseRetry:
  221. error = [self errorForResponse:response
  222. ofType:type
  223. status:statusCode];
  224. completionHandlerBlock(YES, error);
  225. return;
  226. case FIRCLSNetworkClientResponseFailure:
  227. error = [self errorForResponse:response
  228. ofType:type
  229. status:statusCode];
  230. break;
  231. case FIRCLSNetworkClientResponseSuccess:
  232. if (![self contentTypeForResponse:response
  233. matchesRequest:originalRequest]) {
  234. error = [self errorForResponse:response
  235. ofType:FIRCLSNetworkClientResponseInvalid
  236. status:statusCode];
  237. break;
  238. }
  239. break;
  240. }
  241. completionHandlerBlock(NO, error);
  242. }];
  243. }
  244. @end