-
Notifications
You must be signed in to change notification settings - Fork 44
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
Implement NSURLProtocol.stopLoading() for delayed requests #96
Changes from 8 commits
57e42fa
2106533
9c98c60
a6d40f0
2415924
c10b110
2d0203d
f5d61a9
ea5a349
c65872f
69338ca
b91867f
60417ca
cd83ba7
1e6d40b
1672e55
e6ea7c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,10 @@ | |
import Foundation | ||
|
||
/** | ||
A server that conforms to NSURLProtocol in order to intercept outgoing network communication. | ||
You shouldn't use this class directly but register a `Router` instead. Since frameworks like **AFNetworking** and **Alamofire** require manual registration of the `NSURLProtocol` classes you will need to register this class when needed. | ||
A server that conforms to `NSURLProtocol` in order to intercept outgoing network communication. | ||
You shouldn't use this class directly but register a `Router` instead. | ||
Since frameworks like **AFNetworking** and **Alamofire** require manual registration of the `NSURLProtocol` classes | ||
you will need to register this class when needed. | ||
|
||
### Examples | ||
|
||
|
@@ -37,15 +39,20 @@ import Foundation | |
``` | ||
*/ | ||
public final class KakapoServer: NSURLProtocol { | ||
|
||
|
||
/** | ||
The overall list of `Router` objects, which is know to all `KakapoServer` instances. | ||
Visibility is on the `KakapoServer` class level, because `NSURLProtocol` just allows us to register | ||
class elements (no instances are possible). | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to add documentation here since it's a private var -> implementation detail on how the Router and Server communicate and Server keeps storage of routers, nothing that the client needs to be aware of. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to keep this comment. How shall I mark it that its not visible to the public? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing special needed, private prop/methods/types are not accessible anyway. However we try to limit comments , only when really needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is not clear/correct IMHO. Not sure if it's even needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh important: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed comment in 60417ca |
||
private static var routers: [Router] = [] | ||
|
||
/** | ||
Register and return a new Router in the Server | ||
|
||
- parameter baseURL: The base URL that this Router will use | ||
|
||
- returns: An new initializcaRouter objects can hold the same baseURL. | ||
- returns: A newly initialized Router object, which is configured to use the `baseURL`. | ||
*/ | ||
class func register(baseURL: String) -> Router { | ||
NSURLProtocol.registerClass(self) | ||
|
@@ -74,14 +81,19 @@ public final class KakapoServer: NSURLProtocol { | |
} | ||
|
||
/** | ||
KakapoServer checks if the given request matches any of the registered routes and determines if the request should be intercepted | ||
`KakapoServer` checks if the given request matches any of the registered routes | ||
and determines if the request should be intercepted. | ||
|
||
Note: If this method returns `true`, then the OS will create a new `KakapoServer` instance for the `request` | ||
via the `init(request:cachedResponse:client:)` initializer. So, this `request` is the same, which | ||
we'll have access to later on in the `startLoading()` and `stopLoading()` methods. | ||
|
||
- parameter request: A request | ||
|
||
- returns: true if any of the registered route match the request URL | ||
*/ | ||
override public class func canInitWithRequest(request: NSURLRequest) -> Bool { | ||
return routers.filter { $0.canInitWithRequest(request) }.first != nil | ||
return routers.indexOf({ $0.canInitWithRequest(request) }) != nil | ||
} | ||
|
||
/// Just returns the given request without changes | ||
|
@@ -91,11 +103,15 @@ public final class KakapoServer: NSURLProtocol { | |
|
||
/// Start loading the matched requested, the route handler will be called and the returned object will be serialized. | ||
override public func startLoading() { | ||
KakapoServer.routers.filter { $0.canInitWithRequest(request) }.first!.startLoading(self) | ||
if let routerIndex = KakapoServer.routers.indexOf({ $0.canInitWithRequest(request) }) { | ||
KakapoServer.routers[routerIndex].startLoading(self) | ||
} | ||
} | ||
|
||
/// Not implemented yet, does nothing ATM. https://github.com/devlucky/Kakapo/issues/88 | ||
/// Stops the loading of the matched request. | ||
override public func stopLoading() { | ||
/* TODO: implement stopLoading for delayed requests https://github.com/devlucky/Kakapo/issues/88 */ | ||
if let routerIndex = KakapoServer.routers.indexOf({ $0.canInitWithRequest(request) }) { | ||
KakapoServer.routers[routerIndex].stopLoading(self) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, if our assumption is right that Foundation is creating a new KakapoServer instance per request since we return That sounds like the most reasonable and simple approach for now, though we can discuss if it's the best one. But it will, almost 100% sure, prevent to modify the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a test case in ea5a349 for the case when the URLRequest gets cancelled directly. However, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned, can you try to just keep a private boolean flag that, when set, will prevent the Server to forward the call to the routers in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This way, we can just leave the Router as it was before. No changes would be needed as far as I know. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I will implement it like this. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,8 @@ public struct Request { | |
|
||
/** | ||
A protocol to adopt when a `Serializable object needs to also provide response status code and/or headerFields | ||
For example you may use `Response` to wrap your `Serializable` object to just achieve the result or directly implement the protocol. For examply `JSONAPISerializer` implement the protocol in order to be able to provide custom status code in the response. | ||
For example you may use `Response` to wrap your `Serializable` object to just achieve the result or directly implement the protocol. | ||
For example `JSONAPISerializer` implement the protocol in order to be able to provide custom status code in the response. | ||
*/ | ||
public protocol ResponseFieldsProvider: CustomSerializable { | ||
/// The response status code | ||
|
@@ -59,7 +60,7 @@ extension ResponseFieldsProvider { | |
/** | ||
A ResponseFieldsProvider implementation which can be used in `RouteHandlers` to provide valid responses that can return different status code than the default (200) or headerFields. | ||
|
||
The struct provides, appart from a Serializable `body` object, a status code and header fields. | ||
The struct provides, apart from a Serializable `body` object, a status code and header fields. | ||
*/ | ||
public struct Response: ResponseFieldsProvider { | ||
/// The response status code | ||
|
@@ -72,11 +73,11 @@ public struct Response: ResponseFieldsProvider { | |
public let headerFields: [String : String]? | ||
|
||
/** | ||
Initialize `Response` object that wraps another `Serializable` object for the serialization but, implemententing `ResponseFieldsProvider` can affect some parameters of the HTTP response | ||
Initialize `Response` object that wraps another `Serializable` object for the serialization but, implementing `ResponseFieldsProvider` can affect some parameters of the HTTP response | ||
|
||
- parameter statusCode: the status code that the response should provide to the HTTP repsonse | ||
- parameter statusCode: the status code that the response should provide to the HTTP response | ||
- parameter body: the body that will be serialized | ||
- parameter headerFields: the headerFields that the response should provide to the HTTP repsonse | ||
- parameter headerFields: the headerFields that the response should provide to the HTTP response | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for fixing all those typos! 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ty! I'm really bad with typos I will implement Danger that apparently has typo checks! |
||
|
||
- returns: A wrapper `Serializable` object that affect http requests. | ||
*/ | ||
|
@@ -101,17 +102,19 @@ public final class Router { | |
} | ||
|
||
private var routes: [String : Route] = [:] | ||
|
||
private var canceledRequests: [NSURL] = [] | ||
|
||
/// The `baseURL` of the Router | ||
public let baseURL: String | ||
|
||
/// The desired latency to delay the mocked responses. Default value is 0. | ||
/// The desired latency (in seconds) to delay the mocked responses. Default value is 0. | ||
public var latency: NSTimeInterval = 0 | ||
|
||
|
||
/** | ||
Register a new Router in the KakapoServer. | ||
The baseURL can contain a scheme, and the requestURL must match the scheme; if it doesn't contain a scheme then the baseURL is a wildcard and will be matched by any subdomain or any scheme: | ||
Register a new Router in the `KakapoServer`. | ||
The `baseURL` can contain a scheme, and the requestURL must match the scheme; if it doesn't contain a scheme then | ||
the `baseURL` is a wildcard and will be matched by any subdomain or any scheme: | ||
|
||
- base: `http://kakapo.com`, path: "any", requestURL: "http://kakapo.com/any" ✅ | ||
- base: `http://kakapo.com`, path: "any", requestURL: "https://kakapo.com/any" ❌ because it's **https** | ||
|
@@ -210,14 +213,28 @@ public final class Router { | |
|
||
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(latency * Double(NSEC_PER_SEC))) | ||
dispatch_after(delayTime, dispatch_get_main_queue()) { | ||
didFinishLoading(server) | ||
[weak self] in | ||
// before reporting "finished", check if request has been canceled in the meantime | ||
if self?.cancelRequest(requestURL) == false { | ||
didFinishLoading(server) | ||
} | ||
} | ||
} | ||
|
||
|
||
func stopLoading(server: NSURLProtocol) { | ||
guard let requestURL = server.request.URL else { | ||
return | ||
} | ||
// if request URL not in the list of "to be canceled" requests -> enqueue it | ||
if canceledRequests.contains(requestURL) == false { | ||
canceledRequests.append(requestURL) | ||
} | ||
} | ||
|
||
/** | ||
Registers a GET request with the given path. | ||
|
||
The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: | ||
The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: | ||
|
||
- "/users/:userid" and "/users/1234" will produce [userid: 1234] | ||
|
||
|
@@ -239,7 +256,7 @@ public final class Router { | |
/** | ||
Registers a POST request with the given path | ||
|
||
The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: | ||
The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: | ||
|
||
- "/users/:userid" and "/users/1234" will produce [userid: 1234] | ||
|
||
|
@@ -261,7 +278,7 @@ public final class Router { | |
/** | ||
Registers a DEL request with the given path | ||
|
||
The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: | ||
The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: | ||
|
||
- "/users/:userid" and "/users/1234" will produce [userid: 1234] | ||
|
||
|
@@ -283,7 +300,7 @@ public final class Router { | |
/** | ||
Registers a PUT request with the given path | ||
|
||
The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: | ||
The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: | ||
|
||
- "/users/:userid" and "/users/1234" will produce [userid: 1234] | ||
|
||
|
@@ -301,5 +318,22 @@ public final class Router { | |
public func put(path: String, handler: RouteHandler) { | ||
routes[path] = (.PUT, handler) | ||
} | ||
|
||
/** | ||
Determines, if the `requestURL` has been marked as "should be canceled" or not. | ||
|
||
- return: `true` if the `requestURL` should be canceled, otherwise `false` | ||
*/ | ||
private func cancelRequest(requestURL: NSURL) -> Bool { | ||
var requestUrlCanceled = false | ||
|
||
if let canceledRequestIndex = canceledRequests.indexOf(requestURL) { | ||
// remove request URL from the list of "canceled requests" and DO NOT send notification(s) | ||
canceledRequests.removeAtIndex(canceledRequestIndex) | ||
requestUrlCanceled = true | ||
} | ||
|
||
return requestUrlCanceled | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for? Some auto generated files from AppCode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these are meta files generated by AppCode (don't want to have them in the repo).