-
Notifications
You must be signed in to change notification settings - Fork 147
Bubble up errors while loading Resources #400
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
Bubble up errors while loading Resources #400
Conversation
ugh the build fails because of that GCDWebServer PR. Let me know what you think of it, and if needed I can just nuke the 2nd commit in this PR. |
2223bc0
to
7fe268a
Compare
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.
Thanks @ettore!
The changes to HTTPServer
are breaking the semantic versioning. We could either switch the base branch to v3
, or maintain the old HTTPServer
APIs with fallbacks. What do you prefer?
I plan to release at least one other minor version from develop
before v3
is released.
@@ -11,13 +11,17 @@ import Foundation | |||
/// This is required by some Navigators to access a local publication's | |||
/// resources. | |||
public protocol HTTPServer { | |||
typealias FailureHandler = (_ href: String?, _ url: URL?, _ error: ResourceError) -> Void |
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.
Now that we have several handlers with multiple parameters, I'm wondering if adding a HTTPServerEndpointDelegate
protocol would be cleaner, with serve(at: endpoint, using delegate: HTTPServerEndpointDelegate)
. What do you think?
public protocol HTTPServerEndpointDelegate {
func endpoint(_ endpoint: HTTPServerEndpoint, handle request: HTTPServerRequest) throws -> Resource
func endpoint(_ endpoint: HTTPServerEndpoint, requestAt url: URL, href: String, didFailWithError error: Error)
}
Also I'm not sure url
and href
should be optional here?
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.
the delegate is more scalable but on the other hand I kind of like how the server is handling things more directly. Also there are already a lot of moving parts in all these PRs, so for now I'd prefer to keep it as is for now. But it could be an iterative change before we get to v3?
Regarding url
and href
being optional, it's because there are 2 cases where one of them can be nil:
- handling case where server doesn't start, where
server.serverURL
may be nil. - handling case where
request.href
may be nil
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.
Also there are already a lot of moving parts in all these PRs, so for now I'd prefer to keep it as is for now.
I'm fine with keeping things as they are if you prefer.
- handling case where server doesn't start, where server.serverURL may be nil.
I think the FailureHandler
should only notify errors for a particular request (the fact that you had to use ResourceError.other(GCDHTTPServerError.serverNotStarted)
is a good indication). So for this case, IMHO the error thrown by serve()
is enough.
- handling case where request.href may be nil
That's true. Actually that makes me think the FailureHandler
could just be (_ request: HTTPServerRequest, _ error: ResourceError)
. The request has the url
and optional href
, and if we ever add more information (e.g. headers), that could be interesting as well.
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.
- 👍 I'll remove the use of
ResourceError.other(GCDHTTPServerError.serverNotStarted)
. - I started doing the 2nd change but in GCDHTTPServer::handle(request:completion:) I do not have a reference to the
HTTPServerRequest
. I only haveGCDWebServerRequest
and I don't think I can generate the former from the latter? Or maybe you meant to just create a newHTTPServerRequest
from theResource
like so:
let httpServerRequest = HTTPServerRequest(url: request.url, href: resource.link.href) //??
failureHandler?(httpServerRequest, error)
but does this correspond to the actual request that was performed? 🤔
Otherwise I could pass the HTTPServerRequest from the responseResource(for:completion:)
method but it gets gnarly:
for (endpoint, handler) in handlers {
if endpoint == pathWithoutAnchor {
let request = HTTPServerRequest(url: request.url, href: nil)
let resource = handler.resourceHandler(request)
completion(request,
transform(resource: resource, at: endpoint),
handler.failureHandler)
return
} else if path.hasPrefix(endpoint.addingSuffix("/")) {
let request = HTTPServerRequest(
url: request.url,
href: path.removingPrefix(endpoint.removingSuffix("/"))
)
let resource = handler.resourceHandler(request)
completion(request,
transform(resource: resource, at: endpoint),
handler.failureHandler)
return
}
}
// plus this seems pretty wrong since this is not an actual request that was attempted
completion(HTTPServerRequest(url: request.url, href: request.path), FailureResource(link: Link(href: request.url.absoluteString), error: .notFound(nil)), nil)
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.
I think passing the request
computed in responseResource()
to its completion block is best.
// plus this seems pretty wrong since this is not an actual request that was attempted completion(HTTPServerRequest(url: request.url, href: request.path), FailureResource(link: Link(href: >request.url.absoluteString), error: .notFound(nil)), nil)
It is a real HTTP request, but without an href
as we couldn't identify any supporting endpoint. So it would be just HTTPServerRequest(url: request.url, href: nil)
.
🤔 I have not looked into For the other option, do you mean having HTTPServer include both versions of the api, meaning:
where basically the old one simply calls the new one with a
👍 In this case the 2nd option with fallbacks might be better. It would be preferable for us to be able to use this new stuff sooner rather than later. (And I'd prefer to avoid targeting |
That's right, keeping the old APIs and just forwarding the calls to the new ones could be enough here. 👌 |
Errors are bubbled up to the app via a new NavigatorDelegate method: ``` navigator(_:didFailToLoadResourceAt:url:withError) ``` No breaking changes to the Navigators client-facing APIs.
1afaa0d
to
fa9985f
Compare
7878c5a
to
45b6333
Compare
I was able to build the TestApp and fix all the regressions 😅 I also fixed a couple easy warnings. I ended up adding |
45b6333
to
4d3c66a
Compare
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.
I wonder if the build logs could be improved, perhaps with xcpretty? we've been using it and it makes the build logs much clearer because it highlights the errors and it is more succinct for the commands that succeed. IIRC it is provided by GitHub out of the box, so I could try adding it to the xcodebuild commands.
That's great, thank you for the tip!
I made a couple of changes in the PR:
- I restored
HTTPServer.serve(at:handler:)
as the default API to implement for the protocol, to avoid breaking implementers updating Readium. In v3, I will remove it. - I changed the
NavigatorDelegate
API to remove theURL
(a Navigator doesn't necessarily use an HTTP server) but require a non optionalhref
, as it identifies a particular resource. This is to be consistent with the same Kotlin API.
Let me know if you are fine with these changes, and I will merge the PR. Thanks again!
I tested my code with this change and the errors I was observing are still being reported, so this should be ok, I think. But I do worry though that the |
These are outside the scope of broken publication resources, so they should not be reported with Although I would be open to add a global fallback failure handler directly in the |
I was even just thinking something like another "catch all" delegate method to do something like this
I did not propose it because it's still unclear to me when the case of a Thanks for merging the PR! 🎉 |
Errors are bubbled up to the app via a new NavigatorDelegate method:
No breaking changes to the Navigators client-facing APIs.
This solves issue #398.
The 2nd commit depends on PR readium/GCDWebServer#11. It is not strictly required but it is nice to have, because it attaches the underlying error to the
GCDWebServerErrorResponse
.