Skip to content

Commit

Permalink
Generic request context (#248)
Browse files Browse the repository at this point in the history
* Use a generic context

* Added HBTracingRequestContext

* Combine router context parameters

Into HBRouterContext

* Set correct platform

* Make serviceContext public

* Context for all the parameters core library needs

* Use HBCoreRequestContext init with Channel

* HBApplicationBuilder(context -> HBApplicationBuilder(requestContext

* Remove HBChannelContextProtocol

* Make HBRequestContext.eventLoop only available in noasync

* make logger a var
  • Loading branch information
adam-fowler authored Oct 27, 2023
1 parent 494abf8 commit f0f5862
Show file tree
Hide file tree
Showing 42 changed files with 833 additions and 677 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription

let package = Package(
name: "hummingbird",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13)],
platforms: [.macOS(.v13), .iOS(.v16), .tvOS(.v16)],
products: [
.library(name: "Hummingbird", targets: ["Hummingbird"]),
.library(name: "HummingbirdFoundation", targets: ["HummingbirdFoundation"]),
Expand Down
58 changes: 28 additions & 30 deletions Sources/Hummingbird/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,44 @@ import NIOPosix
import NIOTransportServices
import ServiceLifecycle

public struct HBApplication: Sendable {
public struct Context: Sendable {
/// thread pool used by application
public let threadPool: NIOThreadPool
/// Configuration
public let configuration: Configuration
/// Logger. Required to be a var by hummingbird-lambda
public let logger: Logger
/// Encoder used by router
public let encoder: HBResponseEncoder
/// decoder used by router
public let decoder: HBRequestDecoder
public final class HBApplicationContext: Sendable {
/// thread pool used by application
public let threadPool: NIOThreadPool
/// Configuration
public let configuration: HBApplicationConfiguration
/// Logger. Required to be a var by hummingbird-lambda
public let logger: Logger
/// Encoder used by router
public let encoder: HBResponseEncoder
/// decoder used by router
public let decoder: HBRequestDecoder

public init(
threadPool: NIOThreadPool,
configuration: Configuration,
logger: Logger,
encoder: HBResponseEncoder,
decoder: HBRequestDecoder
) {
self.threadPool = threadPool
self.configuration = configuration
self.logger = logger
self.encoder = encoder
self.decoder = decoder
}
public init(
threadPool: NIOThreadPool,
configuration: HBApplicationConfiguration,
logger: Logger,
encoder: HBResponseEncoder,
decoder: HBRequestDecoder
) {
self.threadPool = threadPool
self.configuration = configuration
self.logger = logger
self.encoder = encoder
self.decoder = decoder
}
}

public struct HBApplication<RequestContext: HBRequestContext>: Sendable {
/// event loop group used by application
public let context: Context
public let context: HBApplicationContext
// eventLoopGroup
public let eventLoopGroup: EventLoopGroup
// server
public let server: HBHTTPServer
// date cache service
internal let dateCache: HBDateCache

init(builder: HBApplicationBuilder) {
init(builder: HBApplicationBuilder<RequestContext>) {
self.eventLoopGroup = builder.eventLoopGroup
self.context = .init(
threadPool: builder.threadPool,
Expand Down Expand Up @@ -90,8 +90,6 @@ public struct HBApplication: Sendable {
}

/// Conform to `Service` from `ServiceLifecycle`.
/// TODO: Temporarily I have added unchecked Sendable conformance to the class as Sendable
/// conformance is required by `Service`. I will need to revisit this.
extension HBApplication: Service {
public func run() async throws {
try await withGracefulShutdownHandler {
Expand All @@ -111,4 +109,4 @@ extension HBApplication: Service {

extension HBApplication: CustomStringConvertible {
public var description: String { "HBApplication" }
}
}
17 changes: 9 additions & 8 deletions Sources/Hummingbird/ApplicationBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ public enum EventLoopGroupProvider {
/// try await app.buildAndRun()
/// ```
/// Editing the application builder setup after calling `build` will produce undefined behaviour.
public final class HBApplicationBuilder {
public final class HBApplicationBuilder<RequestContext: HBRequestContext> {
// MARK: Member variables

/// event loop group used by application
public let eventLoopGroup: EventLoopGroup
/// thread pool used by application
public let threadPool: NIOThreadPool
/// routes requests to requestResponders based on URI
public let router: HBRouterBuilder
public let router: HBRouterBuilder<RequestContext>
/// Configuration
public var configuration: HBApplication.Configuration
public var configuration: HBApplicationConfiguration
/// Logger. Required to be a var by hummingbird-lambda
public var logger: Logger
/// Encoder used by router
Expand All @@ -69,14 +69,15 @@ public final class HBApplicationBuilder {

/// Initialize new Application
public init(
configuration: HBApplication.Configuration = HBApplication.Configuration(),
requestContext: RequestContext.Type = HBBasicRequestContext.self,
configuration: HBApplicationConfiguration = HBApplicationConfiguration(),
eventLoopGroupProvider: EventLoopGroupProvider = .singleton
) {
var logger = Logger(label: configuration.serverName ?? "HummingBird")
logger.logLevel = configuration.logLevel
self.logger = logger

self.router = HBRouterBuilder()
self.router = HBRouterBuilder(context: RequestContext.self)
self.configuration = configuration
self.encoder = NullEncoder()
self.decoder = NullDecoder()
Expand Down Expand Up @@ -111,7 +112,7 @@ public final class HBApplicationBuilder {

// MARK: Methods

public func build() -> HBApplication {
public func build() -> HBApplication<RequestContext> {
return .init(builder: self)
}

Expand All @@ -129,10 +130,10 @@ public final class HBApplicationBuilder {
}

/// middleware applied to requests
public var middleware: HBMiddlewareGroup { return self.router.middlewares }
public var middleware: HBMiddlewareGroup<RequestContext> { return self.router.middlewares }

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

Expand Down
37 changes: 20 additions & 17 deletions Sources/Hummingbird/AsyncAwaitSupport/AsyncMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,40 @@ import ServiceContextModule
/// Middleware using async/await
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol HBAsyncMiddleware: HBMiddleware {
func apply(to request: HBRequest, context: HBRequestContext, next: HBResponder) async throws -> HBResponse
func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) async throws -> HBResponse
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension HBAsyncMiddleware {
public func apply(to request: HBRequest, context: HBRequestContext, next: HBResponder) -> EventLoopFuture<HBResponse> {
public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) -> EventLoopFuture<HBResponse> {
let promise = context.eventLoop.makePromise(of: HBResponse.self)
return ServiceContext.$current.withValue(context.serviceContext) {
promise.completeWithTask {
promise.completeWithTask {
return try await self.apply(to: request, context: context, next: next)
}
return promise.futureResult
}
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension HBAsyncMiddleware where Context: HBTracingRequestContext {
public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) -> EventLoopFuture<HBResponse> {
let promise = context.eventLoop.makePromise(of: HBResponse.self)
promise.completeWithTask {
return try await ServiceContext.$current.withValue(context.serviceContext) {
return try await self.apply(to: request, context: context, next: HBPropagateServiceContextResponder(responder: next, context: context))
}
return promise.futureResult
}
return promise.futureResult
}
}

/// Propagate Task Local serviceContext back to HBRequest after running AsyncMiddleware
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
struct HBPropagateServiceContextResponder: HBResponder {
let responder: HBResponder
let context: HBRequestContext
struct HBPropagateServiceContextResponder<Context: HBTracingRequestContext>: HBResponder {
let responder: any HBResponder<Context>
let context: Context

func respond(to request: HBRequest, context: HBRequestContext) -> EventLoopFuture<HBResponse> {
func respond(to request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
if let serviceContext = ServiceContext.$current.get() {
return context.withServiceContext(serviceContext) { context in
self.responder.respond(to: request, context: context)
Expand All @@ -50,11 +61,3 @@ struct HBPropagateServiceContextResponder: HBResponder {
}
}
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension HBResponder {
/// extend HBResponder to provide async/await version of respond
public func respond(to request: HBRequest, context: HBRequestContext) async throws -> HBResponse {
return try await self.respond(to: request, context: context).get()
}
}
25 changes: 16 additions & 9 deletions Sources/Hummingbird/AsyncAwaitSupport/AsyncResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,30 @@
import NIO
import ServiceContextModule

extension HBResponder {
/// extend HBResponder to provide async/await version of respond
public func respond(to request: HBRequest, context: Context) async throws -> HBResponse {
return try await self.respond(to: request, context: context).get()
}
}

/// Responder that calls supplied closure
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct HBAsyncCallbackResponder: HBResponder {
let callback: @Sendable (HBRequest, HBRequestContext) async throws -> HBResponse
public struct HBAsyncCallbackResponder<Context: HBRequestContext>: HBResponder {
let callback: @Sendable (HBRequest, Context) async throws -> HBResponse

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

/// Return EventLoopFuture that will be fulfilled with response to the request supplied
public func respond(to request: HBRequest, context: HBRequestContext) -> EventLoopFuture<HBResponse> {
public extension HBAsyncCallbackResponder where Context: HBRequestContext {
func respond(to request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
let promise = context.eventLoop.makePromise(of: HBResponse.self)
return ServiceContext.$current.withValue(context.serviceContext) {
promise.completeWithTask {
promise.completeWithTask {
return try await ServiceContext.$current.withValue(context.serviceContext) {
try await self.callback(request, context)
}
return promise.futureResult
}
return promise.futureResult
}
}
20 changes: 10 additions & 10 deletions Sources/Hummingbird/AsyncAwaitSupport/Router+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension HBRouterMethods {
@discardableResult public func get<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .GET, options: options, use: handler)
}
Expand All @@ -29,7 +29,7 @@ extension HBRouterMethods {
@discardableResult public func put<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .PUT, options: options, use: handler)
}
Expand All @@ -38,7 +38,7 @@ extension HBRouterMethods {
@discardableResult public func delete<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .DELETE, options: options, use: handler)
}
Expand All @@ -47,7 +47,7 @@ extension HBRouterMethods {
@discardableResult public func head<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .HEAD, options: options, use: handler)
}
Expand All @@ -56,7 +56,7 @@ extension HBRouterMethods {
@discardableResult public func post<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .POST, options: options, use: handler)
}
Expand All @@ -65,15 +65,15 @@ extension HBRouterMethods {
@discardableResult public func patch<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest, HBRequestContext) async throws -> Output
use handler: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
return on(path, method: .PATCH, options: options, use: handler)
}

func constructResponder<Output: HBResponseGenerator>(
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest, HBRequestContext) async throws -> Output
) -> HBResponder {
use closure: @escaping (HBRequest, Context) async throws -> Output
) -> any HBResponder<Context> {
return HBAsyncCallbackResponder { request, context in
var request = request
if case .stream = request.body, !options.contains(.streamBody) {
Expand All @@ -94,7 +94,7 @@ extension HBRouterBuilder {
_ path: String,
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest, HBRequestContext) async throws -> Output
use closure: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
let responder = constructResponder(options: options, use: closure)
add(path, method: method, responder: responder)
Expand All @@ -109,7 +109,7 @@ extension HBRouterGroup {
_ path: String = "",
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest, HBRequestContext) async throws -> Output
use closure: @escaping (HBRequest, Context) async throws -> Output
) -> Self {
let responder = constructResponder(options: options, use: closure)
let path = self.combinePaths(self.path, path)
Expand Down
Loading

0 comments on commit f0f5862

Please sign in to comment.