Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  SessionDelegate.swift
3
//
4
//  Copyright (c) 2014-2018 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
/// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features.
28
open class SessionDelegate: NSObject {
29
    private let fileManager: FileManager
30
 
31
    weak var stateProvider: SessionStateProvider?
32
    var eventMonitor: EventMonitor?
33
 
34
    /// Creates an instance from the given `FileManager`.
35
    ///
36
    /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files.
37
    ///                          `.default` by default.
38
    public init(fileManager: FileManager = .default) {
39
        self.fileManager = fileManager
40
    }
41
 
42
    /// Internal method to find and cast requests while maintaining some integrity checking.
43
    ///
44
    /// - Parameters:
45
    ///   - task: The `URLSessionTask` for which to find the associated `Request`.
46
    ///   - type: The `Request` subclass type to cast any `Request` associate with `task`.
47
    func request<R: Request>(for task: URLSessionTask, as type: R.Type) -> R? {
48
        guard let provider = stateProvider else {
49
            assertionFailure("StateProvider is nil.")
50
            return nil
51
        }
52
 
53
        return provider.request(for: task) as? R
54
    }
55
}
56
 
57
/// Type which provides various `Session` state values.
58
protocol SessionStateProvider: AnyObject {
59
    var serverTrustManager: ServerTrustManager? { get }
60
    var redirectHandler: RedirectHandler? { get }
61
    var cachedResponseHandler: CachedResponseHandler? { get }
62
 
63
    func request(for task: URLSessionTask) -> Request?
64
    func didGatherMetricsForTask(_ task: URLSessionTask)
65
    func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void)
66
    func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential?
67
    func cancelRequestsForSessionInvalidation(with error: Error?)
68
}
69
 
70
// MARK: URLSessionDelegate
71
 
72
extension SessionDelegate: URLSessionDelegate {
73
    open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
74
        eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
75
 
76
        stateProvider?.cancelRequestsForSessionInvalidation(with: error)
77
    }
78
}
79
 
80
// MARK: URLSessionTaskDelegate
81
 
82
extension SessionDelegate: URLSessionTaskDelegate {
83
    /// Result of a `URLAuthenticationChallenge` evaluation.
84
    typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?)
85
 
86
    open func urlSession(_ session: URLSession,
87
                         task: URLSessionTask,
88
                         didReceive challenge: URLAuthenticationChallenge,
89
                         completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
90
        eventMonitor?.urlSession(session, task: task, didReceive: challenge)
91
 
92
        let evaluation: ChallengeEvaluation
93
        switch challenge.protectionSpace.authenticationMethod {
94
        case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,
95
             NSURLAuthenticationMethodNegotiate:
96
            evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
97
        #if !(os(Linux) || os(Windows))
98
        case NSURLAuthenticationMethodServerTrust:
99
            evaluation = attemptServerTrustAuthentication(with: challenge)
100
        case NSURLAuthenticationMethodClientCertificate:
101
            evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
102
        #endif
103
        default:
104
            evaluation = (.performDefaultHandling, nil, nil)
105
        }
106
 
107
        if let error = evaluation.error {
108
            stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error)
109
        }
110
 
111
        completionHandler(evaluation.disposition, evaluation.credential)
112
    }
113
 
114
    #if !(os(Linux) || os(Windows))
115
    /// Evaluates the server trust `URLAuthenticationChallenge` received.
116
    ///
117
    /// - Parameter challenge: The `URLAuthenticationChallenge`.
118
    ///
119
    /// - Returns:             The `ChallengeEvaluation`.
120
    func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
121
        let host = challenge.protectionSpace.host
122
 
123
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
124
              let trust = challenge.protectionSpace.serverTrust
125
        else {
126
            return (.performDefaultHandling, nil, nil)
127
        }
128
 
129
        do {
130
            guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else {
131
                return (.performDefaultHandling, nil, nil)
132
            }
133
 
134
            try evaluator.evaluate(trust, forHost: host)
135
 
136
            return (.useCredential, URLCredential(trust: trust), nil)
137
        } catch {
138
            return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error))))
139
        }
140
    }
141
    #endif
142
 
143
    /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`.
144
    ///
145
    /// - Parameters:
146
    ///   - challenge: The `URLAuthenticationChallenge`.
147
    ///   - task:      The `URLSessionTask` which received the challenge.
148
    ///
149
    /// - Returns:     The `ChallengeEvaluation`.
150
    func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge,
151
                                         belongingTo task: URLSessionTask) -> ChallengeEvaluation {
152
        guard challenge.previousFailureCount == 0 else {
153
            return (.rejectProtectionSpace, nil, nil)
154
        }
155
 
156
        guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else {
157
            return (.performDefaultHandling, nil, nil)
158
        }
159
 
160
        return (.useCredential, credential, nil)
161
    }
162
 
163
    open func urlSession(_ session: URLSession,
164
                         task: URLSessionTask,
165
                         didSendBodyData bytesSent: Int64,
166
                         totalBytesSent: Int64,
167
                         totalBytesExpectedToSend: Int64) {
168
        eventMonitor?.urlSession(session,
169
                                 task: task,
170
                                 didSendBodyData: bytesSent,
171
                                 totalBytesSent: totalBytesSent,
172
                                 totalBytesExpectedToSend: totalBytesExpectedToSend)
173
 
174
        stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent,
175
                                                                totalBytesExpectedToSend: totalBytesExpectedToSend)
176
    }
177
 
178
    open func urlSession(_ session: URLSession,
179
                         task: URLSessionTask,
180
                         needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
181
        eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
182
 
183
        guard let request = request(for: task, as: UploadRequest.self) else {
184
            assertionFailure("needNewBodyStream did not find UploadRequest.")
185
            completionHandler(nil)
186
            return
187
        }
188
 
189
        completionHandler(request.inputStream())
190
    }
191
 
192
    open func urlSession(_ session: URLSession,
193
                         task: URLSessionTask,
194
                         willPerformHTTPRedirection response: HTTPURLResponse,
195
                         newRequest request: URLRequest,
196
                         completionHandler: @escaping (URLRequest?) -> Void) {
197
        eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
198
 
199
        if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler {
200
            redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler)
201
        } else {
202
            completionHandler(request)
203
        }
204
    }
205
 
206
    open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
207
        eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
208
 
209
        stateProvider?.request(for: task)?.didGatherMetrics(metrics)
210
 
211
        stateProvider?.didGatherMetricsForTask(task)
212
    }
213
 
214
    open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
215
        eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
216
 
217
        let request = stateProvider?.request(for: task)
218
 
219
        stateProvider?.didCompleteTask(task) {
220
            request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) })
221
        }
222
    }
223
 
224
    @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
225
    open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
226
        eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
227
    }
228
}
229
 
230
// MARK: URLSessionDataDelegate
231
 
232
extension SessionDelegate: URLSessionDataDelegate {
233
    open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
234
        eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
235
 
236
        if let request = request(for: dataTask, as: DataRequest.self) {
237
            request.didReceive(data: data)
238
        } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
239
            request.didReceive(data: data)
240
        } else {
241
            assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive")
242
            return
243
        }
244
    }
245
 
246
    open func urlSession(_ session: URLSession,
247
                         dataTask: URLSessionDataTask,
248
                         willCacheResponse proposedResponse: CachedURLResponse,
249
                         completionHandler: @escaping (CachedURLResponse?) -> Void) {
250
        eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
251
 
252
        if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
253
            handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
254
        } else {
255
            completionHandler(proposedResponse)
256
        }
257
    }
258
}
259
 
260
// MARK: URLSessionDownloadDelegate
261
 
262
extension SessionDelegate: URLSessionDownloadDelegate {
263
    open func urlSession(_ session: URLSession,
264
                         downloadTask: URLSessionDownloadTask,
265
                         didResumeAtOffset fileOffset: Int64,
266
                         expectedTotalBytes: Int64) {
267
        eventMonitor?.urlSession(session,
268
                                 downloadTask: downloadTask,
269
                                 didResumeAtOffset: fileOffset,
270
                                 expectedTotalBytes: expectedTotalBytes)
271
        guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
272
            assertionFailure("downloadTask did not find DownloadRequest.")
273
            return
274
        }
275
 
276
        downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
277
                                               totalBytesExpectedToWrite: expectedTotalBytes)
278
    }
279
 
280
    open func urlSession(_ session: URLSession,
281
                         downloadTask: URLSessionDownloadTask,
282
                         didWriteData bytesWritten: Int64,
283
                         totalBytesWritten: Int64,
284
                         totalBytesExpectedToWrite: Int64) {
285
        eventMonitor?.urlSession(session,
286
                                 downloadTask: downloadTask,
287
                                 didWriteData: bytesWritten,
288
                                 totalBytesWritten: totalBytesWritten,
289
                                 totalBytesExpectedToWrite: totalBytesExpectedToWrite)
290
        guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
291
            assertionFailure("downloadTask did not find DownloadRequest.")
292
            return
293
        }
294
 
295
        downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
296
                                               totalBytesExpectedToWrite: totalBytesExpectedToWrite)
297
    }
298
 
299
    open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
300
        eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
301
 
302
        guard let request = request(for: downloadTask, as: DownloadRequest.self) else {
303
            assertionFailure("downloadTask did not find DownloadRequest.")
304
            return
305
        }
306
 
307
        let (destination, options): (URL, DownloadRequest.Options)
308
        if let response = request.response {
309
            (destination, options) = request.destination(location, response)
310
        } else {
311
            // If there's no response this is likely a local file download, so generate the temporary URL directly.
312
            (destination, options) = (DownloadRequest.defaultDestinationURL(location), [])
313
        }
314
 
315
        eventMonitor?.request(request, didCreateDestinationURL: destination)
316
 
317
        do {
318
            if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) {
319
                try fileManager.removeItem(at: destination)
320
            }
321
 
322
            if options.contains(.createIntermediateDirectories) {
323
                let directory = destination.deletingLastPathComponent()
324
                try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
325
            }
326
 
327
            try fileManager.moveItem(at: location, to: destination)
328
 
329
            request.didFinishDownloading(using: downloadTask, with: .success(destination))
330
        } catch {
331
            request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error,
332
                                                                                                       source: location,
333
                                                                                                       destination: destination)))
334
        }
335
    }
336
}