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

HBResponder conforming to Sendable #252

Merged
merged 4 commits into from
Oct 29, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:

jobs:
macOS:
runs-on: macOS-latest
runs-on: macOS-13
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/ApplicationBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public final class HBApplicationBuilder<RequestContext: HBRequestContext> {

/// Construct the RequestResponder from the middleware group and router
func constructResponder() -> any HBResponder<RequestContext> {
return self.router.buildRouter()
return self.router.buildResponder()
}

public func addChannelHandler(_ handler: @autoclosure @escaping @Sendable () -> any RemovableChannelHandler) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Middleware/CORSMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import NIOCore
/// "access-control-allow-origin" header
public struct HBCORSMiddleware<Context: HBRequestContext>: HBMiddleware {
/// Defines what origins are allowed
public enum AllowOrigin {
public enum AllowOrigin: Sendable {
case none
case all
case originBased
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Middleware/Middleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import NIOCore
/// }
/// }
/// ```
public protocol HBMiddleware<Context> {
public protocol HBMiddleware<Context>: Sendable {
associatedtype Context: HBRequestContext
func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) -> EventLoopFuture<HBResponse>
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hummingbird/Router/EndpointResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import NIOCore
import NIOHTTP1

/// Stores endpoint responders for each HTTP method
final class HBEndpointResponders<Context: HBRequestContext> {
struct HBEndpointResponders<Context: HBRequestContext>: Sendable {
init(path: String) {
self.path = path
self.methods = [:]
Expand All @@ -26,7 +26,7 @@ final class HBEndpointResponders<Context: HBRequestContext> {
return self.methods[method.rawValue]
}

func addResponder(for method: HTTPMethod, responder: any HBResponder<Context>) {
mutating func addResponder(for method: HTTPMethod, responder: any HBResponder<Context>) {
guard self.methods[method.rawValue] == nil else {
preconditionFailure("\(method.rawValue) already has a handler")
}
Expand Down
24 changes: 10 additions & 14 deletions Sources/Hummingbird/Router/RouterBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import NIOHTTP1
/// `head`, `post` and `patch`. The route handler closures all return objects conforming to
/// `HBResponseGenerator`. This allows us to support routes which return a multitude of types eg
/// ```
/// app.router.get("string") { _ -> String in
/// router.get("string") { _ -> String in
/// return "string"
/// }
/// app.router.post("status") { _ -> HTTPResponseStatus in
/// router.post("status") { _ -> HTTPResponseStatus in
/// return .ok
/// }
/// app.router.data("data") { request -> ByteBuffer in
/// router.data("data") { request -> ByteBuffer in
/// return context.allocator.buffer(string: "buffer")
/// }
/// ```
Expand All @@ -39,18 +39,18 @@ import NIOHTTP1
/// The default `Router` setup in `HBApplication` is the `TrieRouter` . This uses a
/// trie to partition all the routes for faster access. It also supports wildcards and parameter extraction
/// ```
/// app.router.get("user/*", use: anyUser)
/// app.router.get("user/:id", use: userWithId)
/// router.get("user/*", use: anyUser)
/// router.get("user/:id", use: userWithId)
/// ```
/// Both of these match routes which start with "/user" and the next path segment being anything.
/// The second version extracts the path segment out and adds it to `HBRequest.parameters` with the
/// key "id".
public final class HBRouterBuilder<Context: HBRequestContext>: HBRouterMethods {
var trie: RouterPathTrie<HBEndpointResponders<Context>>
var trie: RouterPathTrieBuilder<HBEndpointResponders<Context>>
public let middlewares: HBMiddlewareGroup<Context>

public init(context: Context.Type) {
self.trie = RouterPathTrie()
public init(context: Context.Type = HBBasicRequestContext.self) {
self.trie = RouterPathTrieBuilder()
self.middlewares = .init()
}

Expand All @@ -67,13 +67,9 @@ public final class HBRouterBuilder<Context: HBRequestContext>: HBRouterMethods {
}
}

func endpoint(_ path: String) -> HBEndpointResponders<Context>? {
self.trie.getValueAndParameters(path)?.value
}

/// build router
public func buildRouter() -> any HBResponder<Context> {
HBRouter(context: Context.self, trie: self.trie, notFoundResponder: self.middlewares.constructResponder(finalResponder: NotFoundResponder<Context>()))
public func buildResponder() -> some HBResponder<Context> {
HBRouter(context: Context.self, trie: self.trie.build(), notFoundResponder: self.middlewares.constructResponder(finalResponder: NotFoundResponder<Context>()))
}

/// Add path for closure returning type conforming to ResponseFutureEncodable
Expand Down
6 changes: 3 additions & 3 deletions Sources/Hummingbird/Router/RouterMethods.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
use closure: @escaping (HBRequest, Context) throws -> Output
) -> HBCallbackResponder<Context> {
// generate response from request. Moved repeated code into internal function
func _respond(request: HBRequest, context: Context) throws -> HBResponse {
@Sendable func _respond(request: HBRequest, context: Context) throws -> HBResponse {
return try closure(request, context).response(from: request, context: context)
}

Expand Down Expand Up @@ -216,7 +216,7 @@
use closure: @escaping (HBRequest, Context) -> EventLoopFuture<Output>
) -> HBCallbackResponder<Context> {
// generate response from request. Moved repeated code into internal function
func _respond(request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
@Sendable func _respond(request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
let responseFuture = closure(request, context).flatMapThrowing { try $0.response(from: request, context: context) }
return responseFuture.hop(to: context.eventLoop)
}
Expand All @@ -227,14 +227,14 @@
}
} else {
return HBCallbackResponder { request, context in
var request = request
if case .byteBuffer = request.body {
return _respond(request: request, context: context)
} else {
return request.body.consumeBody(
maxSize: context.applicationContext.configuration.maxUploadSize,
on: context.eventLoop
).flatMap { buffer in
var request = request

Check warning on line 237 in Sources/Hummingbird/Router/RouterMethods.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Router/RouterMethods.swift#L237

Added line #L237 was not covered by tests
request.body = .byteBuffer(buffer)
return _respond(request: request, context: context)
}
Expand Down
91 changes: 71 additions & 20 deletions Sources/Hummingbird/Router/TrieRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
//
//===----------------------------------------------------------------------===//

/// URI Path Trie
struct RouterPathTrie<Value> {
/// URI Path Trie Builder
struct RouterPathTrieBuilder<Value: Sendable> {
var root: Node

init() {
self.root = Node(key: .null, output: nil)
}

/// Add Entry to Trie
/// - Parameters:
/// - entry: Path for entry
/// - value: Value to add to this path if one does not exist already
/// - onAdd: How to edit the value at this path
func addEntry(_ entry: RouterPath, value: @autoclosure () -> Value, onAdd: (Node) -> Void = { _ in }) {
var node = self.root
for key in entry {
Expand All @@ -33,6 +38,61 @@
}
}

func build() -> RouterPathTrie<Value> {
.init(root: self.root.build())
}

/// Trie Node. Each node represents one component of a URI path
final class Node {
let key: RouterPath.Element
var children: [Node]
var value: Value?

init(key: RouterPath.Element, output: Value?) {
self.key = key
self.value = output
self.children = []
}

func addChild(key: RouterPath.Element, output: Value?) -> Node {
if let child = getChild(key) {
return child
}
let node = Node(key: key, output: output)
self.children.append(node)
return node
}

func getChild(_ key: RouterPath.Element) -> Node? {
return self.children.first { $0.key == key }
}

func getChild(_ key: Substring) -> Node? {
if let child = self.children.first(where: { $0.key == key }) {
return child
}
return self.children.first { $0.key ~= key }
}

Check warning on line 75 in Sources/Hummingbird/Router/TrieRouter.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Router/TrieRouter.swift#L70-L75

Added lines #L70 - L75 were not covered by tests

func build() -> RouterPathTrie<Value>.Node {
return .init(key: self.key, value: self.value, children: self.children.map { $0.build() })
}
}
}

/// Trie used by HBRouter responder
struct RouterPathTrie<Value: Sendable>: Sendable {
let root: Node

/// Initialise RouterPathTrie
/// - Parameter root: Root node of trie
init(root: Node) {
self.root = root
}

/// Get value from trie and any parameters from capture nodes
/// - Parameter path: Path to process
/// - Returns: value and parameters
func getValueAndParameters(_ path: String) -> (value: Value, parameters: HBParameters?)? {
let pathComponents = path.split(separator: "/", omittingEmptySubsequences: true)
var parameters: HBParameters?
Expand Down Expand Up @@ -63,25 +123,16 @@
return nil
}

/// Trie Node. Each node represents one component of a URI path
final class Node {
/// Internally used Node to describe static trie
struct Node: Sendable {
let key: RouterPath.Element
var children: [Node]
var value: Value?
let children: [Node]
let value: Value?

init(key: RouterPath.Element, output: Value?) {
init(key: RouterPath.Element, value: Value?, children: [Node]) {
self.key = key
self.value = output
self.children = []
}

func addChild(key: RouterPath.Element, output: Value?) -> Node {
if let child = getChild(key) {
return child
}
let node = Node(key: key, output: output)
self.children.append(node)
return node
self.value = value
self.children = children
}

func getChild(_ key: RouterPath.Element) -> Node? {
Expand All @@ -98,7 +149,7 @@
}

extension Optional where Wrapped == HBParameters {
mutating func set(_ s: Substring, value: Substring) {
fileprivate mutating func set(_ s: Substring, value: Substring) {
switch self {
case .some(var parameters):
parameters.set(s, value: value)
Expand All @@ -108,7 +159,7 @@
}
}

mutating func setCatchAll(_ value: Substring) {
fileprivate mutating func setCatchAll(_ value: Substring) {
switch self {
case .some(var parameters):
parameters.setCatchAll(value)
Expand Down
6 changes: 3 additions & 3 deletions Sources/Hummingbird/Server/Responder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ import NIOCore
/// Protocol for object that produces a response given a request
///
/// This is the core protocol for Hummingbird. It defines an object that can respond to a request.
public protocol HBResponder<Context> {
public protocol HBResponder<Context>: Sendable {
associatedtype Context: HBRequestContext
/// Return EventLoopFuture that will be fulfilled with response to the request supplied
func respond(to request: HBRequest, context: Context) -> EventLoopFuture<HBResponse>
}

/// Responder that calls supplied closure
public struct HBCallbackResponder<Context: HBRequestContext>: HBResponder {
let callback: (HBRequest, Context) -> EventLoopFuture<HBResponse>
let callback: @Sendable (HBRequest, Context) -> EventLoopFuture<HBResponse>

public init(callback: @escaping (HBRequest, Context) -> EventLoopFuture<HBResponse>) {
public init(callback: @escaping @Sendable (HBRequest, Context) -> EventLoopFuture<HBResponse>) {
self.callback = callback
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/HummingbirdFoundation/Files/CacheControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import Hummingbird

/// Associates cache control values with filename
public struct HBCacheControl {
public enum Value: CustomStringConvertible {
public struct HBCacheControl: Sendable {
public enum Value: CustomStringConvertible, Sendable {
case noStore
case noCache
case `private`
Expand Down Expand Up @@ -62,7 +62,7 @@ public struct HBCacheControl {
.joined(separator: ", ")
}

private struct Entry {
private struct Entry: Sendable {
let mediaType: HBMediaType
let cacheControl: [Value]
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/HummingbirdFoundation/Files/FileIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import NIOCore
import NIOPosix

/// Manages File reading and writing.
public struct HBFileIO {
public struct HBFileIO: Sendable {
let fileIO: NonBlockingFileIO
let chunkSize: Int

Expand Down
2 changes: 1 addition & 1 deletion Sources/HummingbirdXCT/HBXCTRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct HBXCTRouter<RequestContext: HBTestRouterContextProtocol>: HBXCTApplicatio
encoder: builder.encoder,
decoder: builder.decoder
)
self.responder = builder.router.buildRouter()
self.responder = builder.router.buildResponder()
}

func shutdown() async throws {
Expand Down
Loading