Skip to content

Commit cd3b6d9

Browse files
authored
Merge pull request #2293 from millenomi/urlsessionuploadtask-urlsessiondownloadtask
Parity: Networking: URLUploadTask and URLDownloadTask
2 parents 8a7cebc + 151cc91 commit cd3b6d9

File tree

6 files changed

+280
-154
lines changed

6 files changed

+280
-154
lines changed

Foundation/URLSession/NativeProtocol.swift

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,9 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
333333
}
334334
}
335335

336-
func createTransferState(url: URL, workQueue: DispatchQueue) -> _TransferState {
336+
func createTransferState(url: URL, body: _Body, workQueue: DispatchQueue) -> _TransferState {
337337
let drain = createTransferBodyDataDrain()
338-
guard let t = task else {
339-
fatalError("Cannot create transfer state")
340-
}
341-
switch t.body {
338+
switch body {
342339
case .none:
343340
return _TransferState(url: url, bodyDataDrain: drain)
344341
case .data(let data):
@@ -358,22 +355,19 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
358355

359356
/// Start a new transfer
360357
func startNewTransfer(with request: URLRequest) {
361-
guard let t = task else {
362-
fatalError()
363-
}
364-
t.currentRequest = request
358+
let task = self.task!
359+
task.currentRequest = request
365360
guard let url = request.url else {
366361
fatalError("No URL in request.")
367362
}
368363

369-
self.internalState = .transferReady(createTransferState(url: url, workQueue: t.workQueue))
370-
if let authRequest = task?.authRequest {
371-
configureEasyHandle(for: authRequest)
372-
} else {
373-
configureEasyHandle(for: request)
374-
}
375-
if (t.suspendCount) < 1 {
376-
resume()
364+
task.getBody { (body) in
365+
self.internalState = .transferReady(self.createTransferState(url: url, body: body, workQueue: task.workQueue))
366+
let request = task.authRequest ?? request
367+
self.configureEasyHandle(for: request, body: body)
368+
if (task.suspendCount) < 1 {
369+
self.resume()
370+
}
377371
}
378372
}
379373

@@ -427,7 +421,7 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
427421
}
428422
}
429423

430-
func configureEasyHandle(for: URLRequest) {
424+
func configureEasyHandle(for request: URLRequest, body: _Body) {
431425
NSRequiresConcreteImplementation()
432426
}
433427
}
@@ -624,37 +618,7 @@ extension _NativeProtocol._ResponseHeaderLines {
624618
}
625619

626620
internal extension _NativeProtocol {
627-
enum _Body {
628-
case none
629-
case data(DispatchData)
630-
/// Body data is read from the given file URL
631-
case file(URL)
632-
case stream(InputStream)
633-
}
634-
}
635-
636-
fileprivate extension _NativeProtocol._Body {
637-
enum _Error : Error {
638-
case fileForBodyDataNotFound
639-
}
640-
641-
/// - Returns: The body length, or `nil` for no body (e.g. `GET` request).
642-
func getBodyLength() throws -> UInt64? {
643-
switch self {
644-
case .none:
645-
return 0
646-
case .data(let d):
647-
return UInt64(d.count)
648-
/// Body data is read from the given file URL
649-
case .file(let fileURL):
650-
guard let s = try FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as? NSNumber else {
651-
throw _Error.fileForBodyDataNotFound
652-
}
653-
return s.uint64Value
654-
case .stream:
655-
return nil
656-
}
657-
}
621+
typealias _Body = URLSessionTask._Body
658622
}
659623

660624
extension _NativeProtocol {

Foundation/URLSession/URLSession.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,10 @@ open class URLSession : NSObject {
433433
}
434434

435435
/* Creates an upload task with the given request. The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */
436-
open func uploadTask(withStreamedRequest request: URLRequest) -> URLSessionUploadTask { NSUnimplemented() }
436+
open func uploadTask(withStreamedRequest request: URLRequest) -> URLSessionUploadTask {
437+
let r = URLSession._Request(request)
438+
return uploadTask(with: r, body: nil, behaviour: .callDelegate)
439+
}
437440

438441
/* Creates a download task with the given request. */
439442
open func downloadTask(with request: URLRequest) -> URLSessionDownloadTask {
@@ -447,7 +450,9 @@ open class URLSession : NSObject {
447450
}
448451

449452
/* Creates a download task with the resume data. If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */
450-
open func downloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask { NSUnimplemented() }
453+
open func downloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask {
454+
return invalidDownloadTask(behavior: .callDelegate)
455+
}
451456

452457
/* Creates a bidirectional stream task to a given host and port.
453458
*/
@@ -511,7 +516,7 @@ fileprivate extension URLSession {
511516
/// Create an upload task.
512517
///
513518
/// All public methods funnel into this one.
514-
func uploadTask(with request: _Request, body: URLSessionTask._Body, behaviour: _TaskRegistry._Behaviour) -> URLSessionUploadTask {
519+
func uploadTask(with request: _Request, body: URLSessionTask._Body?, behaviour: _TaskRegistry._Behaviour) -> URLSessionUploadTask {
515520
guard !self.invalidated else { fatalError("Session invalidated") }
516521
let r = createConfiguredRequest(from: request)
517522
let i = createNextTaskIdentifier()
@@ -533,6 +538,21 @@ fileprivate extension URLSession {
533538
}
534539
return task
535540
}
541+
542+
/// Create a download task that is marked invalid.
543+
func invalidDownloadTask(behavior: _TaskRegistry._Behaviour) -> URLSessionDownloadTask {
544+
/* We do not support resume data in swift-corelibs-foundation, so whatever we are passed, we should just behave as Darwin does in the presence of invalid data. */
545+
546+
guard !self.invalidated else { fatalError("Session invalidated") }
547+
let task = URLSessionDownloadTask()
548+
task.createdFromInvalidResumeData = true
549+
task.taskIdentifier = createNextTaskIdentifier()
550+
task.session = self
551+
workQueue.async {
552+
self.taskRegistry.add(task, behaviour: behavior)
553+
}
554+
return task
555+
}
536556
}
537557

538558

@@ -588,7 +608,9 @@ extension URLSession {
588608
return downloadTask(with: _Request(url), behavior: .downloadCompletionHandler(completionHandler))
589609
}
590610

591-
open func downloadTask(withResumeData resumeData: Data, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { NSUnimplemented() }
611+
open func downloadTask(withResumeData resumeData: Data, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask {
612+
return invalidDownloadTask(behavior: .downloadCompletionHandler(completionHandler))
613+
}
592614
}
593615

594616
internal extension URLSession {

Foundation/URLSession/URLSessionTask.swift

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ open class URLSessionTask : NSObject, NSCopying {
5454

5555
default:
5656
let toBeSent: Int64?
57-
if let bodyLength = try? self.body.getBodyLength() {
57+
if let bodyLength = try? self.knownBody?.getBodyLength() {
5858
toBeSent = Int64(clamping: bodyLength)
5959
} else if self.countOfBytesExpectedToSend > 0 {
6060
toBeSent = Int64(clamping: self.countOfBytesExpectedToSend)
@@ -95,9 +95,10 @@ open class URLSessionTask : NSObject, NSCopying {
9595

9696
/// How many times the task has been suspended, 0 indicating a running task.
9797
internal var suspendCount = 1
98-
internal var session: URLSessionProtocol! //change to nil when task completes
99-
internal let body: _Body
10098

99+
internal var actualSession: URLSession? { return session as? URLSession }
100+
internal var session: URLSessionProtocol! //change to nil when task completes
101+
101102
fileprivate enum ProtocolState {
102103
case toBeCreated
103104
case awaitingCacheReply(Bag<(URLProtocol?) -> Void>)
@@ -193,6 +194,27 @@ open class URLSessionTask : NSObject, NSCopying {
193194
}
194195
}
195196

197+
198+
internal let knownBody: _Body?
199+
func getBody(completion: @escaping (_Body) -> Void) {
200+
if let body = knownBody {
201+
completion(body)
202+
return
203+
}
204+
205+
if let session = actualSession, let delegate = session.delegate as? URLSessionTaskDelegate {
206+
delegate.urlSession(session, task: self) { (stream) in
207+
if let stream = stream {
208+
completion(.stream(stream))
209+
} else {
210+
completion(.none)
211+
}
212+
}
213+
} else {
214+
completion(.none)
215+
}
216+
}
217+
196218
private let syncQ = DispatchQueue(label: "org.swift.URLSessionTask.SyncQ")
197219
private var hasTriggeredResume: Bool = false
198220
internal var isSuspendedAfterResume: Bool {
@@ -212,7 +234,7 @@ open class URLSessionTask : NSObject, NSCopying {
212234
session = _MissingURLSession()
213235
taskIdentifier = 0
214236
originalRequest = nil
215-
body = .none
237+
knownBody = URLSessionTask._Body.none
216238
workQueue = DispatchQueue(label: "URLSessionTask.notused.0")
217239
super.init()
218240
}
@@ -226,13 +248,13 @@ open class URLSessionTask : NSObject, NSCopying {
226248
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: .none)
227249
}
228250
}
229-
internal init(session: URLSession, request: URLRequest, taskIdentifier: Int, body: _Body) {
251+
internal init(session: URLSession, request: URLRequest, taskIdentifier: Int, body: _Body?) {
230252
self.session = session
231253
/* make sure we're actually having a serial queue as it's used for synchronization */
232254
self.workQueue = DispatchQueue.init(label: "org.swift.URLSessionTask.WorkQueue", target: session.workQueue)
233255
self.taskIdentifier = taskIdentifier
234256
self.originalRequest = request
235-
self.body = body
257+
self.knownBody = body
236258
super.init()
237259
self.currentRequest = request
238260
self.progress.cancellationHandler = { [weak self] in
@@ -252,7 +274,7 @@ open class URLSessionTask : NSObject, NSCopying {
252274
}
253275

254276
/// An identifier for this task, assigned by and unique to the owning session
255-
open private(set) var taskIdentifier: Int
277+
open internal(set) var taskIdentifier: Int
256278

257279
/// May be nil if this is a stream task
258280

@@ -589,6 +611,17 @@ open class URLSessionUploadTask : URLSessionDataTask {
589611
*/
590612
open class URLSessionDownloadTask : URLSessionTask {
591613

614+
var createdFromInvalidResumeData = false
615+
616+
// If a task is created from invalid resume data, prevent attempting creation of the protocol object.
617+
override func _getProtocol(_ callback: @escaping (URLProtocol?) -> Void) {
618+
if createdFromInvalidResumeData {
619+
callback(nil)
620+
} else {
621+
super._getProtocol(callback)
622+
}
623+
}
624+
592625
internal var fileLength = -1.0
593626

594627
/* Cancel the download (and calls the superclass -cancel). If

Foundation/URLSession/ftp/FTPURLProtocol.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ internal class _FTPURLProtocol: _NativeProtocol {
5151
}
5252
}
5353

54-
override func configureEasyHandle(for request: URLRequest) {
54+
override func configureEasyHandle(for request: URLRequest, body: _Body) {
5555
easyHandle.set(verboseModeOn: enableLibcurlDebugOutput)
5656
easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: task!)
5757
easyHandle.set(skipAllSignalHandling: true)
5858
guard let url = request.url else { fatalError("No URL in request.") }
5959
easyHandle.set(url: url)
6060
easyHandle.set(preferredReceiveBufferSize: Int.max)
6161
do {
62-
switch (task?.body, try task?.body.getBodyLength()) {
63-
case (.some(URLSessionTask._Body.none), _):
62+
switch (body, try body.getBodyLength()) {
63+
case (.none, _):
6464
set(requestBodyLength: .noBody)
6565
case (_, .some(let length)):
6666
set(requestBodyLength: .length(length))

Foundation/URLSession/http/HTTPURLProtocol.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ internal class _HTTPURLProtocol: _NativeProtocol {
260260
/// Set options on the easy handle to match the given request.
261261
///
262262
/// This performs a series of `curl_easy_setopt()` calls.
263-
override func configureEasyHandle(for request: URLRequest) {
263+
override func configureEasyHandle(for request: URLRequest, body: _Body) {
264264
// At this point we will call the equivalent of curl_easy_setopt()
265265
// to configure everything on the handle. Since we might be re-using
266266
// a handle, we must be sure to set everything and not rely on default
@@ -294,8 +294,8 @@ internal class _HTTPURLProtocol: _NativeProtocol {
294294
easyHandle.setAllowedProtocolsToHTTPAndHTTPS()
295295
easyHandle.set(preferredReceiveBufferSize: Int.max)
296296
do {
297-
switch (task?.body, try task?.body.getBodyLength()) {
298-
case (nil, _):
297+
switch (body, try body.getBodyLength()) {
298+
case (.none, _):
299299
set(requestBodyLength: .noBody)
300300
case (_, let length?):
301301
set(requestBodyLength: .length(length))
@@ -509,11 +509,15 @@ fileprivate extension _HTTPURLProtocol {
509509
/// Any header values that should be removed from the ones set by libcurl
510510
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
511511
var curlHeadersToRemove: [String] {
512-
if task?.body == nil {
513-
return []
514-
} else {
515-
return ["Expect"]
512+
if let task = task {
513+
if task.knownBody == nil {
514+
return []
515+
} else if case .some(.none) = task.knownBody {
516+
return []
517+
}
516518
}
519+
520+
return ["Expect"]
517521
}
518522
}
519523

0 commit comments

Comments
 (0)