Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  RetryPolicy.swift
3
//
4
//  Copyright (c) 2019-2020 Alamofire Software Foundation (http://alamofire.org/)
5
//
6
//  Permission is hereby granted, free of charge, to any person obtaining a copy
7
//  of this software and associated documentation files (the "Software"), to deal
8
//  in the Software without restriction, including without limitation the rights
9
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
//  copies of the Software, and to permit persons to whom the Software is
11
//  furnished to do so, subject to the following conditions:
12
//
13
//  The above copyright notice and this permission notice shall be included in
14
//  all copies or substantial portions of the Software.
15
//
16
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
//  THE SOFTWARE.
23
//
24
 
25
import Foundation
26
 
27
/// A retry policy that retries requests using an exponential backoff for allowed HTTP methods and HTTP status codes
28
/// as well as certain types of networking errors.
29
open class RetryPolicy: RequestInterceptor {
30
    /// The default retry limit for retry policies.
31
    public static let defaultRetryLimit: UInt = 2
32
 
33
    /// The default exponential backoff base for retry policies (must be a minimum of 2).
34
    public static let defaultExponentialBackoffBase: UInt = 2
35
 
36
    /// The default exponential backoff scale for retry policies.
37
    public static let defaultExponentialBackoffScale: Double = 0.5
38
 
39
    /// The default HTTP methods to retry.
40
    /// See [RFC 2616 - Section 9.1.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) for more information.
41
    public static let defaultRetryableHTTPMethods: Set<HTTPMethod> = [.delete, // [Delete](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7) - not always idempotent
42
                                                                      .get, // [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) - generally idempotent
43
                                                                      .head, // [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) - generally idempotent
44
                                                                      .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inherently idempotent
45
                                                                      .put, // [PUT](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) - not always idempotent
46
                                                                      .trace // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inherently idempotent
47
    ]
48
 
49
    /// The default HTTP status codes to retry.
50
    /// See [RFC 2616 - Section 10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10) for more information.
51
    public static let defaultRetryableHTTPStatusCodes: Set<Int> = [408, // [Request Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.9)
52
                                                                   500, // [Internal Server Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1)
53
                                                                   502, // [Bad Gateway](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3)
54
                                                                   503, // [Service Unavailable](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4)
55
                                                                   504 // [Gateway Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.5)
56
    ]
57
 
58
    /// The default URL error codes to retry.
59
    public static let defaultRetryableURLErrorCodes: Set<URLError.Code> = [// [Security] App Transport Security disallowed a connection because there is no secure network connection.
60
        //   - [Disabled] ATS settings do not change at runtime.
61
        // .appTransportSecurityRequiresSecureConnection,
62
 
63
        // [System] An app or app extension attempted to connect to a background session that is already connected to a
64
        // process.
65
        //   - [Enabled] The other process could release the background session.
66
        .backgroundSessionInUseByAnotherProcess,
67
 
68
        // [System] The shared container identifier of the URL session configuration is needed but has not been set.
69
        //   - [Disabled] Cannot change at runtime.
70
        // .backgroundSessionRequiresSharedContainer,
71
 
72
        // [System] The app is suspended or exits while a background data task is processing.
73
        //   - [Enabled] App can be foregrounded or launched to recover.
74
        .backgroundSessionWasDisconnected,
75
 
76
        // [Network] The URL Loading system received bad data from the server.
77
        //   - [Enabled] Server could return valid data when retrying.
78
        .badServerResponse,
79
 
80
        // [Resource] A malformed URL prevented a URL request from being initiated.
81
        //   - [Disabled] URL was most likely constructed incorrectly.
82
        // .badURL,
83
 
84
        // [System] A connection was attempted while a phone call is active on a network that does not support
85
        // simultaneous phone and data communication (EDGE or GPRS).
86
        //   - [Enabled] Phone call could be ended to allow request to recover.
87
        .callIsActive,
88
 
89
        // [Client] An asynchronous load has been canceled.
90
        //   - [Disabled] Request was cancelled by the client.
91
        // .cancelled,
92
 
93
        // [File System] A download task couldn’t close the downloaded file on disk.
94
        //   - [Disabled] File system error is unlikely to recover with retry.
95
        // .cannotCloseFile,
96
 
97
        // [Network] An attempt to connect to a host failed.
98
        //   - [Enabled] Server or DNS lookup could recover during retry.
99
        .cannotConnectToHost,
100
 
101
        // [File System] A download task couldn’t create the downloaded file on disk because of an I/O failure.
102
        //   - [Disabled] File system error is unlikely to recover with retry.
103
        // .cannotCreateFile,
104
 
105
        // [Data] Content data received during a connection request had an unknown content encoding.
106
        //   - [Disabled] Server is unlikely to modify the content encoding during a retry.
107
        // .cannotDecodeContentData,
108
 
109
        // [Data] Content data received during a connection request could not be decoded for a known content encoding.
110
        //   - [Disabled] Server is unlikely to modify the content encoding during a retry.
111
        // .cannotDecodeRawData,
112
 
113
        // [Network] The host name for a URL could not be resolved.
114
        //   - [Enabled] Server or DNS lookup could recover during retry.
115
        .cannotFindHost,
116
 
117
        // [Network] A request to load an item only from the cache could not be satisfied.
118
        //   - [Enabled] Cache could be populated during a retry.
119
        .cannotLoadFromNetwork,
120
 
121
        // [File System] A download task was unable to move a downloaded file on disk.
122
        //   - [Disabled] File system error is unlikely to recover with retry.
123
        // .cannotMoveFile,
124
 
125
        // [File System] A download task was unable to open the downloaded file on disk.
126
        //   - [Disabled] File system error is unlikely to recover with retry.
127
        // .cannotOpenFile,
128
 
129
        // [Data] A task could not parse a response.
130
        //   - [Disabled] Invalid response is unlikely to recover with retry.
131
        // .cannotParseResponse,
132
 
133
        // [File System] A download task was unable to remove a downloaded file from disk.
134
        //   - [Disabled] File system error is unlikely to recover with retry.
135
        // .cannotRemoveFile,
136
 
137
        // [File System] A download task was unable to write to the downloaded file on disk.
138
        //   - [Disabled] File system error is unlikely to recover with retry.
139
        // .cannotWriteToFile,
140
 
141
        // [Security] A client certificate was rejected.
142
        //   - [Disabled] Client certificate is unlikely to change with retry.
143
        // .clientCertificateRejected,
144
 
145
        // [Security] A client certificate was required to authenticate an SSL connection during a request.
146
        //   - [Disabled] Client certificate is unlikely to be provided with retry.
147
        // .clientCertificateRequired,
148
 
149
        // [Data] The length of the resource data exceeds the maximum allowed.
150
        //   - [Disabled] Resource will likely still exceed the length maximum on retry.
151
        // .dataLengthExceedsMaximum,
152
 
153
        // [System] The cellular network disallowed a connection.
154
        //   - [Enabled] WiFi connection could be established during retry.
155
        .dataNotAllowed,
156
 
157
        // [Network] The host address could not be found via DNS lookup.
158
        //   - [Enabled] DNS lookup could succeed during retry.
159
        .dnsLookupFailed,
160
 
161
        // [Data] A download task failed to decode an encoded file during the download.
162
        //   - [Enabled] Server could correct the decoding issue with retry.
163
        .downloadDecodingFailedMidStream,
164
 
165
        // [Data] A download task failed to decode an encoded file after downloading.
166
        //   - [Enabled] Server could correct the decoding issue with retry.
167
        .downloadDecodingFailedToComplete,
168
 
169
        // [File System] A file does not exist.
170
        //   - [Disabled] File system error is unlikely to recover with retry.
171
        // .fileDoesNotExist,
172
 
173
        // [File System] A request for an FTP file resulted in the server responding that the file is not a plain file,
174
        // but a directory.
175
        //   - [Disabled] FTP directory is not likely to change to a file during a retry.
176
        // .fileIsDirectory,
177
 
178
        // [Network] A redirect loop has been detected or the threshold for number of allowable redirects has been
179
        // exceeded (currently 16).
180
        //   - [Disabled] The redirect loop is unlikely to be resolved within the retry window.
181
        // .httpTooManyRedirects,
182
 
183
        // [System] The attempted connection required activating a data context while roaming, but international roaming
184
        // is disabled.
185
        //   - [Enabled] WiFi connection could be established during retry.
186
        .internationalRoamingOff,
187
 
188
        // [Connectivity] A client or server connection was severed in the middle of an in-progress load.
189
        //   - [Enabled] A network connection could be established during retry.
190
        .networkConnectionLost,
191
 
192
        // [File System] A resource couldn’t be read because of insufficient permissions.
193
        //   - [Disabled] Permissions are unlikely to be granted during retry.
194
        // .noPermissionsToReadFile,
195
 
196
        // [Connectivity] A network resource was requested, but an internet connection has not been established and
197
        // cannot be established automatically.
198
        //   - [Enabled] A network connection could be established during retry.
199
        .notConnectedToInternet,
200
 
201
        // [Resource] A redirect was specified by way of server response code, but the server did not accompany this
202
        // code with a redirect URL.
203
        //   - [Disabled] The redirect URL is unlikely to be supplied during a retry.
204
        // .redirectToNonExistentLocation,
205
 
206
        // [Client] A body stream is needed but the client did not provide one.
207
        //   - [Disabled] The client will be unlikely to supply a body stream during retry.
208
        // .requestBodyStreamExhausted,
209
 
210
        // [Resource] A requested resource couldn’t be retrieved.
211
        //   - [Disabled] The resource is unlikely to become available during the retry window.
212
        // .resourceUnavailable,
213
 
214
        // [Security] An attempt to establish a secure connection failed for reasons that can’t be expressed more
215
        // specifically.
216
        //   - [Enabled] The secure connection could be established during a retry given the lack of specificity
217
        //     provided by the error.
218
        .secureConnectionFailed,
219
 
220
        // [Security] A server certificate had a date which indicates it has expired, or is not yet valid.
221
        //   - [Enabled] The server certificate could become valid within the retry window.
222
        .serverCertificateHasBadDate,
223
 
224
        // [Security] A server certificate was not signed by any root server.
225
        //   - [Disabled] The server certificate is unlikely to change during the retry window.
226
        // .serverCertificateHasUnknownRoot,
227
 
228
        // [Security] A server certificate is not yet valid.
229
        //   - [Enabled] The server certificate could become valid within the retry window.
230
        .serverCertificateNotYetValid,
231
 
232
        // [Security] A server certificate was signed by a root server that isn’t trusted.
233
        //   - [Disabled] The server certificate is unlikely to become trusted within the retry window.
234
        // .serverCertificateUntrusted,
235
 
236
        // [Network] An asynchronous operation timed out.
237
        //   - [Enabled] The request timed out for an unknown reason and should be retried.
238
        .timedOut
239
 
240
        // [System] The URL Loading System encountered an error that it can’t interpret.
241
        //   - [Disabled] The error could not be interpreted and is unlikely to be recovered from during a retry.
242
        // .unknown,
243
 
244
        // [Resource] A properly formed URL couldn’t be handled by the framework.
245
        //   - [Disabled] The URL is unlikely to change during a retry.
246
        // .unsupportedURL,
247
 
248
        // [Client] Authentication is required to access a resource.
249
        //   - [Disabled] The user authentication is unlikely to be provided by retrying.
250
        // .userAuthenticationRequired,
251
 
252
        // [Client] An asynchronous request for authentication has been canceled by the user.
253
        //   - [Disabled] The user cancelled authentication and explicitly took action to not retry.
254
        // .userCancelledAuthentication,
255
 
256
        // [Resource] A server reported that a URL has a non-zero content length, but terminated the network connection
257
        // gracefully without sending any data.
258
        //   - [Disabled] The server is unlikely to provide data during the retry window.
259
        // .zeroByteResource,
260
    ]
261
 
262
    /// The total number of times the request is allowed to be retried.
263
    public let retryLimit: UInt
264
 
265
    /// The base of the exponential backoff policy (should always be greater than or equal to 2).
266
    public let exponentialBackoffBase: UInt
267
 
268
    /// The scale of the exponential backoff.
269
    public let exponentialBackoffScale: Double
270
 
271
    /// The HTTP methods that are allowed to be retried.
272
    public let retryableHTTPMethods: Set<HTTPMethod>
273
 
274
    /// The HTTP status codes that are automatically retried by the policy.
275
    public let retryableHTTPStatusCodes: Set<Int>
276
 
277
    /// The URL error codes that are automatically retried by the policy.
278
    public let retryableURLErrorCodes: Set<URLError.Code>
279
 
280
    /// Creates a `RetryPolicy` from the specified parameters.
281
    ///
282
    /// - Parameters:
283
    ///   - retryLimit:               The total number of times the request is allowed to be retried. `2` by default.
284
    ///   - exponentialBackoffBase:   The base of the exponential backoff policy. `2` by default.
285
    ///   - exponentialBackoffScale:  The scale of the exponential backoff. `0.5` by default.
286
    ///   - retryableHTTPMethods:     The HTTP methods that are allowed to be retried.
287
    ///                               `RetryPolicy.defaultRetryableHTTPMethods` by default.
288
    ///   - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy.
289
    ///                               `RetryPolicy.defaultRetryableHTTPStatusCodes` by default.
290
    ///   - retryableURLErrorCodes:   The URL error codes that are automatically retried by the policy.
291
    ///                               `RetryPolicy.defaultRetryableURLErrorCodes` by default.
292
    public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit,
293
                exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase,
294
                exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale,
295
                retryableHTTPMethods: Set<HTTPMethod> = RetryPolicy.defaultRetryableHTTPMethods,
296
                retryableHTTPStatusCodes: Set<Int> = RetryPolicy.defaultRetryableHTTPStatusCodes,
297
                retryableURLErrorCodes: Set<URLError.Code> = RetryPolicy.defaultRetryableURLErrorCodes) {
298
        precondition(exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2.")
299
 
300
        self.retryLimit = retryLimit
301
        self.exponentialBackoffBase = exponentialBackoffBase
302
        self.exponentialBackoffScale = exponentialBackoffScale
303
        self.retryableHTTPMethods = retryableHTTPMethods
304
        self.retryableHTTPStatusCodes = retryableHTTPStatusCodes
305
        self.retryableURLErrorCodes = retryableURLErrorCodes
306
    }
307
 
308
    open func retry(_ request: Request,
309
                    for session: Session,
310
                    dueTo error: Error,
311
                    completion: @escaping (RetryResult) -> Void) {
312
        if request.retryCount < retryLimit, shouldRetry(request: request, dueTo: error) {
313
            completion(.retryWithDelay(pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale))
314
        } else {
315
            completion(.doNotRetry)
316
        }
317
    }
318
 
319
    /// Determines whether or not to retry the provided `Request`.
320
    ///
321
    /// - Parameters:
322
    ///     - request: `Request` that failed due to the provided `Error`.
323
    ///     - error:   `Error` encountered while executing the `Request`.
324
    ///
325
    /// - Returns:     `Bool` determining whether or not to retry the `Request`.
326
    open func shouldRetry(request: Request, dueTo error: Error) -> Bool {
327
        guard let httpMethod = request.request?.method, retryableHTTPMethods.contains(httpMethod) else { return false }
328
 
329
        if let statusCode = request.response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) {
330
            return true
331
        } else {
332
            let errorCode = (error as? URLError)?.code
333
            let afErrorCode = (error.asAFError?.underlyingError as? URLError)?.code
334
 
335
            guard let code = errorCode ?? afErrorCode else { return false }
336
 
337
            return retryableURLErrorCodes.contains(code)
338
        }
339
    }
340
}
341
 
342
#if swift(>=5.5)
343
extension RequestInterceptor where Self == RetryPolicy {
344
    /// Provides a default `RetryPolicy` instance.
345
    public static var retryPolicy: RetryPolicy { RetryPolicy() }
346
 
347
    /// Creates an `RetryPolicy` from the specified parameters.
348
    ///
349
    /// - Parameters:
350
    ///   - retryLimit:               The total number of times the request is allowed to be retried. `2` by default.
351
    ///   - exponentialBackoffBase:   The base of the exponential backoff policy. `2` by default.
352
    ///   - exponentialBackoffScale:  The scale of the exponential backoff. `0.5` by default.
353
    ///   - retryableHTTPMethods:     The HTTP methods that are allowed to be retried.
354
    ///                               `RetryPolicy.defaultRetryableHTTPMethods` by default.
355
    ///   - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy.
356
    ///                               `RetryPolicy.defaultRetryableHTTPStatusCodes` by default.
357
    ///   - retryableURLErrorCodes:   The URL error codes that are automatically retried by the policy.
358
    ///                               `RetryPolicy.defaultRetryableURLErrorCodes` by default.
359
    ///
360
    /// - Returns:                    The `RetryPolicy`
361
    public static func retryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit,
362
                                   exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase,
363
                                   exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale,
364
                                   retryableHTTPMethods: Set<HTTPMethod> = RetryPolicy.defaultRetryableHTTPMethods,
365
                                   retryableHTTPStatusCodes: Set<Int> = RetryPolicy.defaultRetryableHTTPStatusCodes,
366
                                   retryableURLErrorCodes: Set<URLError.Code> = RetryPolicy.defaultRetryableURLErrorCodes) -> RetryPolicy {
367
        RetryPolicy(retryLimit: retryLimit,
368
                    exponentialBackoffBase: exponentialBackoffBase,
369
                    exponentialBackoffScale: exponentialBackoffScale,
370
                    retryableHTTPMethods: retryableHTTPMethods,
371
                    retryableHTTPStatusCodes: retryableHTTPStatusCodes,
372
                    retryableURLErrorCodes: retryableURLErrorCodes)
373
    }
374
}
375
#endif
376
 
377
// MARK: -
378
 
379
/// A retry policy that automatically retries idempotent requests for network connection lost errors. For more
380
/// information about retrying network connection lost errors, please refer to Apple's
381
/// [technical document](https://developer.apple.com/library/content/qa/qa1941/_index.html).
382
open class ConnectionLostRetryPolicy: RetryPolicy {
383
    /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters.
384
    ///
385
    /// - Parameters:
386
    ///   - retryLimit:              The total number of times the request is allowed to be retried.
387
    ///                              `RetryPolicy.defaultRetryLimit` by default.
388
    ///   - exponentialBackoffBase:  The base of the exponential backoff policy.
389
    ///                              `RetryPolicy.defaultExponentialBackoffBase` by default.
390
    ///   - exponentialBackoffScale: The scale of the exponential backoff.
391
    ///                              `RetryPolicy.defaultExponentialBackoffScale` by default.
392
    ///   - retryableHTTPMethods:    The idempotent http methods to retry.
393
    ///                              `RetryPolicy.defaultRetryableHTTPMethods` by default.
394
    public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit,
395
                exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase,
396
                exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale,
397
                retryableHTTPMethods: Set<HTTPMethod> = RetryPolicy.defaultRetryableHTTPMethods) {
398
        super.init(retryLimit: retryLimit,
399
                   exponentialBackoffBase: exponentialBackoffBase,
400
                   exponentialBackoffScale: exponentialBackoffScale,
401
                   retryableHTTPMethods: retryableHTTPMethods,
402
                   retryableHTTPStatusCodes: [],
403
                   retryableURLErrorCodes: [.networkConnectionLost])
404
    }
405
}
406
 
407
#if swift(>=5.5)
408
extension RequestInterceptor where Self == ConnectionLostRetryPolicy {
409
    /// Provides a default `ConnectionLostRetryPolicy` instance.
410
    public static var connectionLostRetryPolicy: ConnectionLostRetryPolicy { ConnectionLostRetryPolicy() }
411
 
412
    /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters.
413
    ///
414
    /// - Parameters:
415
    ///   - retryLimit:              The total number of times the request is allowed to be retried.
416
    ///                              `RetryPolicy.defaultRetryLimit` by default.
417
    ///   - exponentialBackoffBase:  The base of the exponential backoff policy.
418
    ///                              `RetryPolicy.defaultExponentialBackoffBase` by default.
419
    ///   - exponentialBackoffScale: The scale of the exponential backoff.
420
    ///                              `RetryPolicy.defaultExponentialBackoffScale` by default.
421
    ///   - retryableHTTPMethods:    The idempotent http methods to retry.
422
    ///
423
    /// - Returns:                   The `ConnectionLostRetryPolicy`.
424
    public static func connectionLostRetryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit,
425
                                                 exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase,
426
                                                 exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale,
427
                                                 retryableHTTPMethods: Set<HTTPMethod> = RetryPolicy.defaultRetryableHTTPMethods) -> ConnectionLostRetryPolicy {
428
        ConnectionLostRetryPolicy(retryLimit: retryLimit,
429
                                  exponentialBackoffBase: exponentialBackoffBase,
430
                                  exponentialBackoffScale: exponentialBackoffScale,
431
                                  retryableHTTPMethods: retryableHTTPMethods)
432
    }
433
}
434
#endif