diff --git a/Sources/Hummingbird/Application.swift b/Sources/Hummingbird/Application.swift index 338aac50c..0e2394f99 100644 --- a/Sources/Hummingbird/Application.swift +++ b/Sources/Hummingbird/Application.swift @@ -115,6 +115,7 @@ extension ApplicationProtocol { response = httpError.response(allocator: channel.allocator) default: // this error has not been recognised + context.logger.debug("Unrecognised Error", metadata: ["error": "\(error)"]) response = Response( status: .internalServerError, body: .init() diff --git a/Sources/Hummingbird/Deprecations.swift b/Sources/Hummingbird/Deprecations.swift index 1c882c4ed..3f100392c 100644 --- a/Sources/Hummingbird/Deprecations.swift +++ b/Sources/Hummingbird/Deprecations.swift @@ -97,3 +97,8 @@ public typealias HBMemoryPersistDriver = MemoryPersistDriver public typealias HBPersistDriver = PersistDriver @_documentation(visibility: internal) @available(*, deprecated, renamed: "PersistError") public typealias HBPersistError = PersistError + +@_documentation(visibility: internal) @available(*, deprecated, renamed: "HTTPError") +public typealias HBHTTPError = HTTPError +@_documentation(visibility: internal) @available(*, deprecated, renamed: "HTTPResponseError") +public typealias HBHTTPResponseError = HTTPResponseError diff --git a/Sources/HummingbirdCore/Error/HTTPError.swift b/Sources/Hummingbird/Error/HTTPError.swift similarity index 100% rename from Sources/HummingbirdCore/Error/HTTPError.swift rename to Sources/Hummingbird/Error/HTTPError.swift diff --git a/Sources/HummingbirdCore/Error/HTTPErrorResponse.swift b/Sources/Hummingbird/Error/HTTPErrorResponse.swift similarity index 100% rename from Sources/HummingbirdCore/Error/HTTPErrorResponse.swift rename to Sources/Hummingbird/Error/HTTPErrorResponse.swift diff --git a/Sources/HummingbirdCore/Error/NIOCore+HTTPResponseError.swift b/Sources/Hummingbird/Error/NIOCore+HTTPResponseError.swift similarity index 100% rename from Sources/HummingbirdCore/Error/NIOCore+HTTPResponseError.swift rename to Sources/Hummingbird/Error/NIOCore+HTTPResponseError.swift diff --git a/Sources/Hummingbird/Exports.swift b/Sources/Hummingbird/Exports.swift index 5398854c5..847189b05 100644 --- a/Sources/Hummingbird/Exports.swift +++ b/Sources/Hummingbird/Exports.swift @@ -13,8 +13,6 @@ //===----------------------------------------------------------------------===// @_exported import struct HummingbirdCore.BindAddress -@_exported import struct HummingbirdCore.HTTPError -@_exported import protocol HummingbirdCore.HTTPResponseError @_exported import struct HummingbirdCore.Request @_exported import struct HummingbirdCore.RequestBody @_exported import struct HummingbirdCore.Response @@ -32,8 +30,6 @@ @_exported @_documentation(visibility: internal) import struct HTTPTypes.HTTPResponse // Temporary exports or deprecated typealiases -@_exported import struct HummingbirdCore.HBHTTPError -@_exported import protocol HummingbirdCore.HBHTTPResponseError @_exported import struct HummingbirdCore.HBRequest @_exported import struct HummingbirdCore.HBRequestBody @_exported import struct HummingbirdCore.HBResponse diff --git a/Sources/HummingbirdCore/Deprecations.swift b/Sources/HummingbirdCore/Deprecations.swift index f0e74a1a0..ea566d5d2 100644 --- a/Sources/HummingbirdCore/Deprecations.swift +++ b/Sources/HummingbirdCore/Deprecations.swift @@ -28,10 +28,6 @@ public typealias HBResponse = Response public typealias HBResponseBody = ResponseBody @_documentation(visibility: internal) @available(*, deprecated, renamed: "ResponseBodyWriter") public typealias HBResponseBodyWriter = ResponseBodyWriter -@_documentation(visibility: internal) @available(*, deprecated, renamed: "HTTPError") -public typealias HBHTTPError = HTTPError -@_documentation(visibility: internal) @available(*, deprecated, renamed: "HTTPResponseError") -public typealias HBHTTPResponseError = HTTPResponseError @_documentation(visibility: internal) @available(*, deprecated, renamed: "Server") public typealias HBServer = Server @_documentation(visibility: internal) @available(*, deprecated, renamed: "ServerConfiguration") diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift index e802476b6..cce484e9a 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift @@ -27,7 +27,7 @@ public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { /// - responder: Function returning a HTTP response for a HTTP request /// - additionalChannelHandlers: Additional channel handlers to add to channel pipeline public init( - responder: @escaping @Sendable (Request, Channel) async throws -> Response, + responder: @escaping HTTPChannelHandler.Responder, additionalChannelHandlers: @escaping @Sendable () -> [any RemovableChannelHandler] = { [] } ) { self.additionalChannelHandlers = additionalChannelHandlers @@ -66,7 +66,7 @@ public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { await handleHTTP(asyncChannel: asyncChannel, logger: logger) } - public let responder: @Sendable (Request, Channel) async throws -> Response + public let responder: HTTPChannelHandler.Responder let additionalChannelHandlers: @Sendable () -> [any RemovableChannelHandler] } diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTPChannelHandler.swift b/Sources/HummingbirdCore/Server/HTTP/HTTPChannelHandler.swift index 13838e556..afdb5a3ba 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTPChannelHandler.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTPChannelHandler.swift @@ -21,7 +21,7 @@ import ServiceLifecycle /// Protocol for HTTP channels public protocol HTTPChannelHandler: ServerChildChannel { - typealias Responder = @Sendable (Request, Channel) async throws -> Response + typealias Responder = @Sendable (Request, Channel) async -> Response var responder: Responder { get } } @@ -60,12 +60,7 @@ extension HTTPChannelHandler { let bodyStream = NIOAsyncChannelRequestBody(iterator: iterator) let request = Request(head: head, body: .init(asyncSequence: bodyStream)) - let response: Response - do { - response = try await self.responder(request, asyncChannel.channel) - } catch { - response = self.getErrorResponse(from: error, allocator: asyncChannel.channel.allocator) - } + let response: Response = await self.responder(request, asyncChannel.channel) do { try await outbound.write(.head(response.head)) let tailHeaders = try await response.body.write(responseWriter) @@ -116,20 +111,6 @@ extension HTTPChannelHandler { logger.trace("Failed to read/write to Channel. Error: \(error)") } } - - func getErrorResponse(from error: Error, allocator: ByteBufferAllocator) -> Response { - switch error { - case let httpError as HTTPResponseError: - // this is a processed error so don't log as Error - return httpError.response(allocator: allocator) - default: - // this error has not been recognised - return Response( - status: .internalServerError, - body: .init() - ) - } - } } /// Writes ByteBuffers to AsyncChannel outbound writer diff --git a/Sources/HummingbirdHTTP2/HTTP2Channel.swift b/Sources/HummingbirdHTTP2/HTTP2Channel.swift index e9efb0c15..113671e73 100644 --- a/Sources/HummingbirdHTTP2/HTTP2Channel.swift +++ b/Sources/HummingbirdHTTP2/HTTP2Channel.swift @@ -33,7 +33,7 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { private let sslContext: NIOSSLContext private let http1: HTTP1Channel private let additionalChannelHandlers: @Sendable () -> [any RemovableChannelHandler] - public var responder: @Sendable (Request, Channel) async throws -> Response { http1.responder } + public var responder: HTTPChannelHandler.Responder { self.http1.responder } /// Initialize HTTP1Channel /// - Parameters: @@ -43,7 +43,7 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { public init( tlsConfiguration: TLSConfiguration, additionalChannelHandlers: @escaping @Sendable () -> [any RemovableChannelHandler] = { [] }, - responder: @escaping @Sendable (Request, Channel) async throws -> Response = { _, _ in throw HTTPError(.notFound) } + responder: @escaping HTTPChannelHandler.Responder ) throws { var tlsConfiguration = tlsConfiguration tlsConfiguration.applicationProtocols = NIOHTTP2SupportedALPNProtocols diff --git a/Sources/HummingbirdTLS/TLSChannel.swift b/Sources/HummingbirdTLS/TLSChannel.swift index 10ddc7204..3f55b23f6 100644 --- a/Sources/HummingbirdTLS/TLSChannel.swift +++ b/Sources/HummingbirdTLS/TLSChannel.swift @@ -58,8 +58,8 @@ public struct TLSChannel: ServerChildChannel { } extension TLSChannel: HTTPChannelHandler where BaseChannel: HTTPChannelHandler { - public var responder: @Sendable (Request, Channel) async throws -> Response { - baseChannel.responder + public var responder: HTTPChannelHandler.Responder { + self.baseChannel.responder } } diff --git a/Tests/HummingbirdCoreTests/CoreTests.swift b/Tests/HummingbirdCoreTests/CoreTests.swift index 3b217c0d5..5b515cb59 100644 --- a/Tests/HummingbirdCoreTests/CoreTests.swift +++ b/Tests/HummingbirdCoreTests/CoreTests.swift @@ -70,7 +70,7 @@ class HummingBirdCoreTests: XCTestCase { func testError() async throws { try await testServer( - responder: { _, _ in throw HTTPError(.unauthorized) }, + responder: { _, _ in .init(status: .unauthorized) }, httpChannelSetup: .http1(), configuration: .init(address: .hostname(port: 0)), eventLoopGroup: Self.eventLoopGroup, @@ -85,8 +85,12 @@ class HummingBirdCoreTests: XCTestCase { func testConsumeBody() async throws { try await testServer( responder: { request, _ in - let buffer = try await request.body.collect(upTo: .max) - return Response(status: .ok, body: .init(byteBuffer: buffer)) + do { + let buffer = try await request.body.collect(upTo: .max) + return Response(status: .ok, body: .init(byteBuffer: buffer)) + } catch { + return Response(status: .contentTooLarge) + } }, configuration: .init(address: .hostname(port: 0)), eventLoopGroup: Self.eventLoopGroup, @@ -190,20 +194,27 @@ class HummingBirdCoreTests: XCTestCase { } func testChannelHandlerErrorPropagation() async throws { + struct TestChannelHandlerError: Error {} class CreateErrorHandler: ChannelInboundHandler, RemovableChannelHandler { typealias InboundIn = HTTPRequestPart var seen: Bool = false func channelRead(context: ChannelHandlerContext, data: NIOAny) { if case .body = self.unwrapInboundIn(data) { - context.fireErrorCaught(HTTPError(.unavailableForLegalReasons)) + context.fireErrorCaught(TestChannelHandlerError()) } context.fireChannelRead(data) } } try await testServer( responder: { request, _ in - _ = try await request.body.collect(upTo: .max) + do { + _ = try await request.body.collect(upTo: .max) + } catch is TestChannelHandlerError { + return Response(status: .unavailableForLegalReasons) + } catch { + return Response(status: .contentTooLarge) + } return Response(status: .ok) }, httpChannelSetup: .http1(additionalChannelHandlers: [CreateErrorHandler()]), @@ -269,7 +280,11 @@ class HummingBirdCoreTests: XCTestCase { } try await testServer( responder: { request, _ in - _ = try await request.body.collect(upTo: .max) + do { + _ = try await request.body.collect(upTo: .max) + } catch { + return Response(status: .contentTooLarge) + } return .init(status: .ok) }, httpChannelSetup: .http1(additionalChannelHandlers: [HTTPServerIncompleteRequest(), IdleStateHandler(readTimeout: .seconds(1))]), @@ -292,7 +307,11 @@ class HummingBirdCoreTests: XCTestCase { func testWriteIdleTimeout() async throws { try await testServer( responder: { request, _ in - _ = try await request.body.collect(upTo: .max) + do { + _ = try await request.body.collect(upTo: .max) + } catch { + return Response(status: .contentTooLarge) + } return .init(status: .ok) }, httpChannelSetup: .http1(additionalChannelHandlers: [IdleStateHandler(writeTimeout: .seconds(1))]), @@ -320,7 +339,7 @@ class HummingBirdCoreTests: XCTestCase { logger: logger ) { request, _ in await handlerPromise.complete(()) - try await Task.sleep(for: .milliseconds(500)) + try? await Task.sleep(for: .milliseconds(500)) return Response(status: .ok, body: .init(asyncSequence: request.body.delayed())) } onServerRunning: { await portPromise.complete($0.localAddress!.port!)