Skip to content

Implementation of URLProtocol and refactoring URLSession #968

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 1 commit into from
May 31, 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 @@ -500,6 +501,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 @@ -938,23 +940,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 @@ -2057,6 +2068,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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Will it be possible to factor out some code this function shares with 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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use

defer {
    _classesLock.unlock()
}

here and in the following cases, otherwise it looks quite easy that a further refactoring forgets to unlock...

Copy link
Member

@pushkarnk pushkarnk May 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remembered what @parkera had to say about this :)
#902 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very surprising but fair enough, thanks for pointing that out 👍

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 @@ -220,6 +220,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we want to register the native protocol classes every time a new URLSession is created. This can be a one-time activity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR: #1030

}

/*
Expand All @@ -245,6 +247,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we want to register the native protocol classes every time a new URLSession is created. This can be a one-time activity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR: #1030

}

open let delegateQueue: OperationQueue
Expand Down
Loading