Skip to content

[3.1]Cherry picking URLProtocol implementation and refactoring URLSession #1027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions Foundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
5B1FD9DE1D6D16580080E83C /* TaskRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */; };
5B1FD9DF1D6D16580080E83C /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9D31D6D16580080E83C /* TransferState.swift */; };
5B1FD9E11D6D178E0080E83C /* libcurl.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B1FD9E01D6D178E0080E83C /* libcurl.3.dylib */; };
E429ED451E9638DA0031BC20 /* HTTPURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */; };
5B1FD9E31D6D17B80080E83C /* TestNSURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9E21D6D17B80080E83C /* TestNSURLSession.swift */; };
5B23AB871CE62D17000DB898 /* Boxing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23AB861CE62D17000DB898 /* Boxing.swift */; };
5B23AB891CE62D4D000DB898 /* ReferenceConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23AB881CE62D4D000DB898 /* ReferenceConvertible.swift */; };
Expand Down Expand Up @@ -495,6 +496,7 @@
5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskRegistry.swift; sourceTree = "<group>"; };
5B1FD9D31D6D16580080E83C /* TransferState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferState.swift; sourceTree = "<group>"; };
5B1FD9E01D6D178E0080E83C /* libcurl.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcurl.3.dylib; path = usr/lib/libcurl.3.dylib; sourceTree = SDKROOT; };
E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPURLProtocol.swift; path = HTTPURLProtocol.swift; sourceTree = "<group>"; };
5B1FD9E21D6D17B80080E83C /* TestNSURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLSession.swift; sourceTree = "<group>"; };
5B23AB861CE62D17000DB898 /* Boxing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Boxing.swift; sourceTree = "<group>"; };
5B23AB881CE62D4D000DB898 /* ReferenceConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceConvertible.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -928,23 +930,32 @@
5B1FD9C71D6D162D0080E83C /* Session */ = {
isa = PBXGroup;
children = (
E4F889331E9CF04D008A70EB /* http */,
5B1FD9C81D6D16580080E83C /* Configuration.swift */,
5B1FD9C91D6D16580080E83C /* EasyHandle.swift */,
5B1FD9CA1D6D16580080E83C /* HTTPBodySource.swift */,
5B1FD9CB1D6D16580080E83C /* HTTPMessage.swift */,
5B1FD9CC1D6D16580080E83C /* libcurlHelpers.swift */,
5B1FD9CD1D6D16580080E83C /* MultiHandle.swift */,
5B1FD9CE1D6D16580080E83C /* NSURLSession.swift */,
5B1FD9CF1D6D16580080E83C /* NSURLSessionConfiguration.swift */,
5B1FD9D01D6D16580080E83C /* NSURLSessionDelegate.swift */,
5B1FD9D11D6D16580080E83C /* NSURLSessionTask.swift */,
5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */,
5B1FD9D31D6D16580080E83C /* TransferState.swift */,
);
name = Session;
path = NSURLSession;
sourceTree = "<group>";
};
E4F889331E9CF04D008A70EB /* http */ = {
isa = PBXGroup;
children = (
E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */,
5B1FD9C91D6D16580080E83C /* EasyHandle.swift */,
5B1FD9CA1D6D16580080E83C /* HTTPBodySource.swift */,
5B1FD9CB1D6D16580080E83C /* HTTPMessage.swift */,
5B1FD9CC1D6D16580080E83C /* libcurlHelpers.swift */,
5B1FD9CD1D6D16580080E83C /* MultiHandle.swift */,
5B1FD9D31D6D16580080E83C /* TransferState.swift */,
);
name = http;
sourceTree = "<group>";
};
5B5D88531BBC938800234F36 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2040,6 +2051,7 @@
5B23AB871CE62D17000DB898 /* Boxing.swift in Sources */,
5BF7AEA41BCD51F9008F214A /* Bundle.swift in Sources */,
5B23AB891CE62D4D000DB898 /* ReferenceConvertible.swift in Sources */,
E429ED451E9638DA0031BC20 /* HTTPURLProtocol.swift in Sources */,
D3E8D6D11C367AB600295652 /* NSSpecialValue.swift in Sources */,
5B1FD9D51D6D16580080E83C /* EasyHandle.swift in Sources */,
EAB57B721BD1C7A5004AC5C5 /* NSPortMessage.swift in Sources */,
Expand Down
192 changes: 177 additions & 15 deletions Foundation/NSURLProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//

import CoreFoundation
import Dispatch

/*!
@header NSURLProtocol.h

Expand Down Expand Up @@ -142,6 +145,96 @@ public protocol URLProtocolClient : NSObjectProtocol {
func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge)
}

internal class _ProtocolClient : NSObject, URLProtocolClient {

func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) {
`protocol`.task?.response = response
}

func urlProtocolDidFinishLoading(_ protocol: URLProtocol) {
guard let task = `protocol`.task else { fatalError() }
guard let session = task.session as? URLSession else { fatalError() }
switch session.behaviour(for: task) {
case .taskDelegate(let delegate):
guard let s = session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
delegate.urlSession(s, task: task, didCompleteWithError: nil)
task.state = .completed
}
case .noDelegate:
task.state = .completed
case .dataCompletionHandler(let completion):
let data = Data()
guard let client = `protocol`.client else { fatalError() }
client.urlProtocol(`protocol`, didLoad: data)
return
case .downloadCompletionHandler(let completion):
guard let s = session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
completion(task.currentRequest?.url, task.response, nil)
task.state = .completed
}
}
}

func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge) {
NSUnimplemented()
}

func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge) {
NSUnimplemented()
}

func urlProtocol(_ protocol: URLProtocol, didLoad data: Data) {
guard let task = `protocol`.task else { fatalError() }
guard let session = task.session as? URLSession else { fatalError() }
switch session.behaviour(for: task) {
case .dataCompletionHandler(let completion):
guard let s = task.session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
completion(data, task.response, nil)
task.state = .completed
}
default: return
}
}

func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) {
guard let task = `protocol`.task else { fatalError() }
guard let session = task.session as? URLSession else { fatalError() }
switch session.behaviour(for: task) {
case .taskDelegate(let delegate):
guard let s = session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
delegate.urlSession(s, task: task, didCompleteWithError: error as Error)
task.state = .completed
}
case .noDelegate:
task.state = .completed
case .dataCompletionHandler(let completion):
guard let s = session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
completion(nil, nil, error)
task.state = .completed
}
case .downloadCompletionHandler(let completion):
guard let s = session as? URLSession else { fatalError() }
s.delegateQueue.addOperation {
completion(nil, nil, error)
task.state = .completed
}
}
}

func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse) {
NSUnimplemented()
}

func urlProtocol(_ protocol: URLProtocol, wasRedirectedTo request: URLRequest, redirectResponse: URLResponse) {
NSUnimplemented()
}
}

/*!
@class NSURLProtocol

Expand All @@ -151,7 +244,9 @@ public protocol URLProtocolClient : NSObjectProtocol {
or more protocols or URL schemes.
*/
open class URLProtocol : NSObject {


private static var _registeredProtocolClasses = [AnyClass]()
private static var _classesLock = NSLock()
/*!
@method initWithRequest:cachedResponse:client:
@abstract Initializes an NSURLProtocol given request,
Expand All @@ -165,28 +260,43 @@ open class URLProtocol : NSObject {
interface the protocol implementation can use to report results back
to the URL loading system.
*/
public init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { NSUnimplemented() }

public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
self._request = request
self._cachedResponse = cachedResponse
self._client = client ?? _ProtocolClient()
}

private var _request : URLRequest
private var _cachedResponse : CachedURLResponse?
private var _client : URLProtocolClient?

/*!
@method client
@abstract Returns the NSURLProtocolClient of the receiver.
@result The NSURLProtocolClient of the receiver.
*/
open var client: URLProtocolClient? { NSUnimplemented() }
open var client: URLProtocolClient? {
set { self._client = newValue }
get { return self._client }
}

/*!
@method request
@abstract Returns the NSURLRequest of the receiver.
@result The NSURLRequest of the receiver.
*/
/*@NSCopying*/ open var request: URLRequest { NSUnimplemented() }
/*@NSCopying*/ open var request: URLRequest {
return _request
}

/*!
@method cachedResponse
@abstract Returns the NSCachedURLResponse of the receiver.
@result The NSCachedURLResponse of the receiver.
*/
/*@NSCopying*/ open var cachedResponse: CachedURLResponse? { NSUnimplemented() }
/*@NSCopying*/ open var cachedResponse: CachedURLResponse? {
return _cachedResponse
}

/*======================================================================
Begin responsibilities for protocol implementors
Expand All @@ -207,7 +317,9 @@ open class URLProtocol : NSObject {
@param request A request to inspect.
@result YES if the protocol can handle the given request, NO if not.
*/
open class func canInit(with request: URLRequest) -> Bool { NSUnimplemented() }
open class func canInit(with request: URLRequest) -> Bool {
NSRequiresConcreteImplementation()
}

/*!
@method canonicalRequestForRequest:
Expand Down Expand Up @@ -246,7 +358,9 @@ open class URLProtocol : NSObject {
@discussion When this method is called, the protocol implementation
should start loading a request.
*/
open func startLoading() { NSUnimplemented() }
open func startLoading() {
NSRequiresConcreteImplementation()
}

/*!
@method stopLoading
Expand All @@ -256,7 +370,9 @@ open class URLProtocol : NSObject {
to a cancel operation, so protocol implementations must be able to
handle this call while a load is in progress.
*/
open func stopLoading() { NSUnimplemented() }
open func stopLoading() {
NSRequiresConcreteImplementation()
}

/*======================================================================
End responsibilities for protocol implementors
Expand Down Expand Up @@ -323,19 +439,65 @@ open class URLProtocol : NSObject {
The only way that failure can occur is if the given class is not a
subclass of NSURLProtocol.
*/
open class func registerClass(_ protocolClass: AnyClass) -> Bool { NSUnimplemented() }

open class func registerClass(_ protocolClass: AnyClass) -> Bool {
if protocolClass is URLProtocol.Type {
_classesLock.lock()
guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else {
_classesLock.unlock()
return true
}
_registeredProtocolClasses.append(protocolClass)
_classesLock.unlock()
return true
}
return false
}

internal class func getProtocolClass(protocols: [AnyClass], request: URLRequest) -> AnyClass? {
// Registered protocols are consulted in reverse order.
// This behaviour makes the latest registered protocol to be consulted first
_classesLock.lock()
let protocolClasses = protocols
for protocolClass in protocolClasses {
let urlProtocolClass: AnyClass = protocolClass
guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
if urlProtocol.canInit(with: request) {
_classesLock.unlock()
return urlProtocol
}
}
_classesLock.unlock()
return nil
}

internal class func getProtocols() -> [AnyClass]? {
return _registeredProtocolClasses
}
/*!
@method unregisterClass:
@abstract This method unregisters a protocol.
@discussion After unregistration, a protocol class is no longer
consulted in calls to NSURLProtocol class methods.
@param protocolClass The class to unregister.
*/
open class func unregisterClass(_ protocolClass: AnyClass) { NSUnimplemented() }
open class func unregisterClass(_ protocolClass: AnyClass) {
_classesLock.lock()
if let idx = _registeredProtocolClasses.index(where: { $0 === protocolClass }) {
_registeredProtocolClasses.remove(at: idx)
}
_classesLock.unlock()
}

open class func canInit(with task: URLSessionTask) -> Bool { NSUnimplemented() }
public convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { NSUnimplemented() }
/*@NSCopying*/ open var task: URLSessionTask? { NSUnimplemented() }
}
public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
let urlRequest = task.originalRequest
self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client)
self.task = task
}
/*@NSCopying*/ open var task: URLSessionTask? {
set { self._task = newValue }
get { return self._task }
}

private var _task : URLSessionTask? = nil
}
4 changes: 4 additions & 0 deletions Foundation/NSURLSession/NSURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ open class URLSession : NSObject {
let c = URLSession._Configuration(URLSessionConfiguration: configuration)
self._configuration = c
self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue)
// registering all the protocol classes with URLProtocol
let _ = URLProtocol.registerClass(_HTTPURLProtocol.self)
}

/*
Expand All @@ -246,6 +248,8 @@ open class URLSession : NSObject {
let c = URLSession._Configuration(URLSessionConfiguration: configuration)
self._configuration = c
self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue)
// registering all the protocol classes with URLProtocol
let _ = URLProtocol.registerClass(_HTTPURLProtocol.self)
}

open let delegateQueue: OperationQueue
Expand Down
Loading