Skip to content
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

Update HTTPServer API #424

Merged
merged 6 commits into from
May 17, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The Readium Swift toolkit now requires a minimum of iOS 13.
#### Shared

* The `Link` property key for archive-based publication assets (e.g. an EPUB/ZIP) is now `https://readium.org/webpub-manifest/properties#archive` instead of `archive`.
* The API of `HTTPServer` slightly changed to be more future-proof.

#### Navigator

Expand Down
29 changes: 8 additions & 21 deletions Sources/Adapters/GCDWebServer/GCDHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,14 @@ public enum GCDHTTPServerError: Error {

/// Implementation of `HTTPServer` using ReadiumGCDWebServer under the hood.
public class GCDHTTPServer: HTTPServer, Loggable {
private struct EndpointHandler {
let resourceHandler: (HTTPServerRequest) -> Resource
let failureHandler: HTTPServer.FailureHandler?
}

/// Shared instance of the HTTP server.
public static let shared = GCDHTTPServer()

/// The actual underlying HTTP server instance.
private let server = ReadiumGCDWebServer()

/// Mapping between endpoints and their handlers.
private var handlers: [HTTPURL: EndpointHandler] = [:]
private var handlers: [HTTPURL: HTTPRequestHandler] = [:]

/// Mapping between endpoints and resource transformers.
private var transformers: [HTTPURL: [ResourceTransformer]] = [:]
Expand Down Expand Up @@ -114,7 +109,7 @@ public class GCDHTTPServer: HTTPServer, Loggable {

private func responseResource(
for request: ReadiumGCDWebServerRequest,
completion: @escaping (HTTPServerRequest, Resource, FailureHandler?) -> Void
completion: @escaping (HTTPServerRequest, Resource, HTTPRequestHandler.OnFailure?) -> Void
) {
let completion = { request, resource, failureHandler in
// Escape the queue to avoid deadlocks if something is using the
Expand Down Expand Up @@ -145,11 +140,11 @@ public class GCDHTTPServer: HTTPServer, Loggable {
for (endpoint, handler) in handlers {
if endpoint == pathWithoutAnchor {
let request = HTTPServerRequest(url: url, href: nil)
let resource = handler.resourceHandler(request)
let resource = handler.onRequest(request)
completion(
request,
transform(resource: resource, at: endpoint),
handler.failureHandler
handler.onFailure
)
return

Expand All @@ -158,11 +153,11 @@ public class GCDHTTPServer: HTTPServer, Loggable {
url: url,
href: href
)
let resource = handler.resourceHandler(request)
let resource = handler.onRequest(request)
completion(
request,
transform(resource: resource, at: endpoint),
handler.failureHandler
handler.onFailure
)
return
}
Expand All @@ -182,25 +177,17 @@ public class GCDHTTPServer: HTTPServer, Loggable {

// MARK: HTTPServer

public func serve(at endpoint: HTTPServerEndpoint, handler: @escaping (HTTPServerRequest) -> Resource) throws -> HTTPURL {
try serve(at: endpoint, handler: handler, failureHandler: nil)
}

public func serve(
at endpoint: HTTPServerEndpoint,
handler: @escaping (HTTPServerRequest) -> Resource,
failureHandler: FailureHandler?
handler: HTTPRequestHandler
) throws -> HTTPURL {
try queue.sync(flags: .barrier) {
if case .stopped = state {
try start()
}

let url = try url(for: endpoint)
handlers[url] = EndpointHandler(
resourceHandler: handler,
failureHandler: failureHandler
)
handlers[url] = handler
return url
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Navigator/CBZ/CBZNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
publicationBaseURL = try httpServer.serve(
at: uuidEndpoint,
publication: publication,
failureHandler: { [weak self] request, error in
onFailure: { [weak self] request, error in
DispatchQueue.main.async { [weak self] in
guard let self = self, let href = request.href else {
return
Expand Down
7 changes: 3 additions & 4 deletions Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ final class EPUBNavigatorViewModel: Loggable {
assetsURL: httpServer.serve(
at: "readium",
contentsOf: Bundle.module.resourceURL!.fileURL!
.appendingPath("Assets/Static", isDirectory: true),
failureHandler: nil
.appendingPath("Assets/Static", isDirectory: true)
),
useLegacySettings: false
)
Expand All @@ -73,7 +72,7 @@ final class EPUBNavigatorViewModel: Loggable {
publicationBaseURL = try httpServer.serve(
at: uuidEndpoint, // serving the chapters endpoint
publication: publication,
failureHandler: { [weak self] request, error in
onFailure: { [weak self] request, error in
guard let self = self, let href = request.href else {
return
}
Expand Down Expand Up @@ -187,7 +186,7 @@ final class EPUBNavigatorViewModel: Loggable {
throw Error.noHTTPServer
}
let endpoint = baseEndpoint.addingSuffix("/") + file.lastPathSegment
let url = try httpServer.serve(at: endpoint, contentsOf: file, failureHandler: nil)
let url = try httpServer.serve(at: endpoint, contentsOf: file)
$servedFiles.write { $0[file] = url }
return url
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Navigator/PDF/PDFNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ open class PDFNavigatorViewController: UIViewController, VisualNavigator, Select
publicationBaseURL = try httpServer.serve(
at: uuidEndpoint,
publication: publication,
failureHandler: { request, error in
onFailure: { request, error in
DispatchQueue.main.async { [weak self] in
guard let self = self, let href = request.href else {
return
Expand Down
65 changes: 30 additions & 35 deletions Sources/Shared/Toolkit/HTTP/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,15 @@ import Foundation
/// This is required by some Navigators to access a local publication's
/// resources.
public protocol HTTPServer {
typealias FailureHandler = (_ request: HTTPServerRequest, _ error: ResourceError) -> Void

/// Serves resources at the given `endpoint`.
///
/// Subsequent calls with the same `endpoint` overwrite each other.
///
/// - Returns the base URL for this endpoint.
@discardableResult
func serve(
at endpoint: HTTPServerEndpoint,
handler: @escaping (HTTPServerRequest) -> Resource
) throws -> HTTPURL

/// Serves resources at the given `endpoint`.
///
/// Subsequent calls with the same `endpoint` overwrite each other.
///
/// If the resource cannot be served, the `failureHandler` is called.
///
/// - Returns the base URL for this endpoint.
@discardableResult
func serve(
at endpoint: HTTPServerEndpoint,
handler: @escaping (HTTPServerRequest) -> Resource,
failureHandler: FailureHandler?
handler: HTTPRequestHandler
) throws -> HTTPURL

/// Registers a `Resource` transformer that will be run on all responses
Expand All @@ -48,15 +32,6 @@ public protocol HTTPServer {
}

public extension HTTPServer {
@discardableResult
func serve(
at endpoint: HTTPServerEndpoint,
handler: @escaping (HTTPServerRequest) -> Resource,
failureHandler: FailureHandler?
) throws -> HTTPURL {
try serve(at: endpoint, handler: handler)
}

/// Serves the local file `url` at the given `endpoint`.
///
/// If the provided `url` is a directory, then all the files in the
Expand All @@ -70,9 +45,9 @@ public extension HTTPServer {
func serve(
at endpoint: HTTPServerEndpoint,
contentsOf url: FileURL,
failureHandler: FailureHandler? = nil
onFailure: HTTPRequestHandler.OnFailure? = nil
) throws -> HTTPURL {
func handler(request: HTTPServerRequest) -> Resource {
func onRequest(request: HTTPServerRequest) -> Resource {
let file = request.href.flatMap { url.resolve($0) }
?? url

Expand All @@ -87,8 +62,10 @@ public extension HTTPServer {

return try serve(
at: endpoint,
handler: handler(request:),
failureHandler: failureHandler
handler: HTTPRequestHandler(
onRequest: onRequest,
onFailure: onFailure
)
)
}

Expand All @@ -102,11 +79,11 @@ public extension HTTPServer {
func serve(
at endpoint: HTTPServerEndpoint,
publication: Publication,
failureHandler: FailureHandler? = nil
onFailure: HTTPRequestHandler.OnFailure? = nil
) throws -> HTTPURL {
func handler(request: HTTPServerRequest) -> Resource {
func onRequest(request: HTTPServerRequest) -> Resource {
guard let href = request.href else {
failureHandler?(request, .notFound(nil))
onFailure?(request, .notFound(nil))

return FailureResource(
link: Link(href: request.url.string),
Expand All @@ -119,8 +96,10 @@ public extension HTTPServer {

return try serve(
at: endpoint,
handler: handler(request:),
failureHandler: failureHandler
handler: HTTPRequestHandler(
onRequest: onRequest,
onFailure: onFailure
)
)
}
}
Expand All @@ -141,3 +120,19 @@ public struct HTTPServerRequest {
self.href = href
}
}

/// Callbacks handling a request.
///
/// If the resource cannot be served, the `onFailure` callback is called.
public struct HTTPRequestHandler {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: not that important but I was wondering why this struct and HTTPServerRequest are not in their own files, since they are public types.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like to keep tightly related types in the same file. If HTTPServer was a struct or class instead of a protocol, I would have used HTTPServer.Request.

public typealias OnRequest = (_ request: HTTPServerRequest) -> Resource
public typealias OnFailure = (_ request: HTTPServerRequest, _ error: ResourceError) -> Void

public let onRequest: OnRequest
public let onFailure: OnFailure?

public init(onRequest: @escaping OnRequest, onFailure: OnFailure? = nil) {
self.onRequest = onRequest
self.onFailure = onFailure
}
}
Loading