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

Transition HBResponder to be async/await, removing HBAsyncResponder in the process #256

Merged
merged 8 commits into from
Oct 31, 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
8 changes: 4 additions & 4 deletions Sources/Hummingbird/AsyncAwaitSupport/AsyncMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
let responder: any HBResponder<Context>
let context: Context

Joannis marked this conversation as resolved.
Show resolved Hide resolved
func respond(to request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
func respond(to request: HBRequest, context: Context) async throws -> HBResponse {
if let serviceContext = ServiceContext.$current.get() {
return context.withServiceContext(serviceContext) { context in
self.responder.respond(to: request, context: context)
return try await context.withServiceContext(serviceContext) { context in
try await self.responder.respond(to: request, context: context)
}
} else {
return self.responder.respond(to: request, context: context)
return try await self.responder.respond(to: request, context: context)

Check warning on line 60 in Sources/Hummingbird/AsyncAwaitSupport/AsyncMiddleware.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/AsyncAwaitSupport/AsyncMiddleware.swift#L60

Added line #L60 was not covered by tests
}
}
}
44 changes: 0 additions & 44 deletions Sources/Hummingbird/AsyncAwaitSupport/AsyncResponder.swift

This file was deleted.

32 changes: 0 additions & 32 deletions Sources/Hummingbird/AsyncAwaitSupport/RouteHandler+async.swift

This file was deleted.

119 changes: 0 additions & 119 deletions Sources/Hummingbird/AsyncAwaitSupport/Router+async.swift

This file was deleted.

30 changes: 18 additions & 12 deletions Sources/Hummingbird/Middleware/CORSMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,16 @@

/// apply CORS middleware
public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) -> EventLoopFuture<HBResponse> {
Joannis marked this conversation as resolved.
Show resolved Hide resolved
context.eventLoop.makeFutureWithTask {
try await self.apply(to: request, context: context, next: next)
}
}

public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) async throws -> HBResponse {
// if no origin header then don't apply CORS
guard request.headers["origin"].first != nil else { return next.respond(to: request, context: context) }
guard request.headers["origin"].first != nil else {
return try await next.respond(to: request, context: context)

Check warning on line 95 in Sources/Hummingbird/Middleware/CORSMiddleware.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Middleware/CORSMiddleware.swift#L95

Added line #L95 was not covered by tests
}

if request.method == .OPTIONS {
// if request is OPTIONS then return CORS headers and skip the rest of the middleware chain
Expand All @@ -107,20 +115,18 @@
headers.add(name: "vary", value: "Origin")
}

return context.success(HBResponse(status: .noContent, headers: headers, body: .empty))
return HBResponse(status: .noContent, headers: headers, body: .empty)
} else {
// if not OPTIONS then run rest of middleware chain and add origin value at the end
return next.respond(to: request, context: context).map { response in
var response = response
response.headers.add(name: "access-control-allow-origin", value: self.allowOrigin.value(for: request) ?? "")
if self.allowCredentials {
response.headers.add(name: "access-control-allow-credentials", value: "true")
}
if case .originBased = self.allowOrigin {
response.headers.add(name: "vary", value: "Origin")
}
return response
var response = try await next.respond(to: request, context: context)
response.headers.add(name: "access-control-allow-origin", value: self.allowOrigin.value(for: request) ?? "")
if self.allowCredentials {
response.headers.add(name: "access-control-allow-credentials", value: "true")

Check warning on line 124 in Sources/Hummingbird/Middleware/CORSMiddleware.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Middleware/CORSMiddleware.swift#L124

Added line #L124 was not covered by tests
}
if case .originBased = self.allowOrigin {
response.headers.add(name: "vary", value: "Origin")
}
return response
}
}
}
67 changes: 35 additions & 32 deletions Sources/Hummingbird/Middleware/MetricsMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,47 @@ public struct HBMetricsMiddleware<Context: HBRequestContext>: HBMiddleware {
public init() {}

public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) -> EventLoopFuture<HBResponse> {
context.eventLoop.makeFutureWithTask {
try await apply(to: request, context: context, next: next)
}
}

public func apply(to request: HBRequest, context: Context, next: any HBResponder<Context>) async throws -> HBResponse {
let startTime = DispatchTime.now().uptimeNanoseconds

let responseFuture = next.respond(to: request, context: context)
responseFuture.whenComplete { result in
switch result {
case .success:
// need to create dimensions once request has been responded to ensure
// we have the correct endpoint path
let dimensions: [(String, String)] = [
("hb_uri", context.endpointPath ?? request.uri.path),
do {
let response = try await next.respond(to: request, context: context)
// need to create dimensions once request has been responded to ensure
// we have the correct endpoint path
let dimensions: [(String, String)] = [
("hb_uri", context.endpointPath ?? request.uri.path),
("hb_method", request.method.rawValue),
]
Counter(label: "hb_requests", dimensions: dimensions).increment()
Metrics.Timer(
label: "hb_request_duration",
dimensions: dimensions,
preferredDisplayUnit: .seconds
).recordNanoseconds(DispatchTime.now().uptimeNanoseconds - startTime)
return response
} catch {
// need to create dimensions once request has been responded to ensure
// we have the correct endpoint path
let dimensions: [(String, String)]
// Don't record uri in 404 errors, to avoid spamming of metrics
if let endpointPath = context.endpointPath {
dimensions = [
("hb_uri", endpointPath),
("hb_method", request.method.rawValue),
]
Counter(label: "hb_requests", dimensions: dimensions).increment()
Metrics.Timer(
label: "hb_request_duration",
dimensions: dimensions,
preferredDisplayUnit: .seconds
).recordNanoseconds(DispatchTime.now().uptimeNanoseconds - startTime)

case .failure:
// need to create dimensions once request has been responded to ensure
// we have the correct endpoint path
let dimensions: [(String, String)]
// Don't record uri in 404 errors, to avoid spamming of metrics
if let endpointPath = context.endpointPath {
dimensions = [
("hb_uri", endpointPath),
("hb_method", request.method.rawValue),
]
Counter(label: "hb_requests", dimensions: dimensions).increment()
} else {
dimensions = [
("hb_method", request.method.rawValue),
]
}
Counter(label: "hb_errors", dimensions: dimensions).increment()
} else {
dimensions = [
("hb_method", request.method.rawValue),
]
}
Counter(label: "hb_errors", dimensions: dimensions).increment()
throw error
}
return responseFuture
}
}
17 changes: 8 additions & 9 deletions Sources/Hummingbird/Middleware/Middleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,19 @@ import NIOCore
/// Middleware allows you to process a request before it reaches your request handler and then process the response
/// returned by that handler.
/// ```
/// func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> {
/// func apply(to request: HBRequest, next: HBResponder) async throws -> HBResponse {
/// let request = processRequest(request)
/// return next.respond(to: request).map { response in
/// return processResponse(response)
/// }
/// let response = try await next.respond(to: request)
/// return processResponse(response)
/// }
/// ```
/// Middleware also allows you to shortcut the whole process and not pass on the request to the handler
/// ```
/// func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> {
/// func apply(to request: HBRequest, next: HBResponder) async throws -> HBResponse {
/// if request.method == .OPTIONS {
/// return context.success(HBResponse(status: .noContent))
/// return HBResponse(status: .noContent)
/// } else {
/// return next.respond(to: request)
/// return try await next.respond(to: request)
/// }
/// }
/// ```
Expand All @@ -49,7 +48,7 @@ struct MiddlewareResponder<Context: HBRequestContext>: HBResponder {
let middleware: any HBMiddleware<Context>
let next: any HBResponder<Context>

func respond(to request: HBRequest, context: Context) -> EventLoopFuture<HBResponse> {
return self.middleware.apply(to: request, context: context, next: self.next)
func respond(to request: HBRequest, context: Context) async throws -> HBResponse {
return try await self.middleware.apply(to: request, context: context, next: self.next).get()
}
}
Loading