Add synchronous helpers for HTTP1 pipeline setup (#1762)

Motivation:

We recently added a synchronous view of the `ChannelPipline` so that
callers can avoid allocating futures when they know they're on the right
event loop. We also offer convenience APIs to configure the pipeline for
particular use cases, like an HTTP/1 server but we don't have
synchronous versions of these APIs yet. We should have parity
between as synchronous and asyncronous APIs where feasible.

Modifications:

- Add synchronous helpers to configure HTTP1 client and server pipelines

Result:

Callers to synchronously configure HTTP1 client and server pipelines.
This commit is contained in:
George Barnett 2021-03-04 12:04:26 +00:00 committed by GitHub
parent d22d89804c
commit e2b39de23b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 141 additions and 18 deletions

View File

@ -1018,6 +1018,11 @@ extension ChannelPipeline {
self._pipeline = pipeline
}
/// The `EventLoop` of the `Channel` this synchronous operations view corresponds to.
public var eventLoop: EventLoop {
return self._pipeline.eventLoop
}
/// Add a handler to the pipeline.
///
/// - Important: This *must* be called on the event loop.

View File

@ -34,6 +34,8 @@ extension ChannelPipeline {
///
/// - parameters:
/// - position: The position in the `ChannelPipeline` where to add the HTTP client handlers. Defaults to `.last`.
/// - leftOverBytesStrategy: The strategy to use when dealing with leftover bytes after removing the `HTTPDecoder`
/// from the pipeline.
/// - returns: An `EventLoopFuture` that will fire when the pipeline is configured.
public func addHTTPClientHandlers(position: Position = .last,
leftOverBytesStrategy: RemoveAfterUpgradeStrategy = .dropBytes) -> EventLoopFuture<Void> {
@ -41,10 +43,13 @@ extension ChannelPipeline {
leftOverBytesStrategy: leftOverBytesStrategy,
withClientUpgrade: nil)
}
/// Configure a `ChannelPipeline` for use as a HTTP client with a client upgrader configuration.
///
/// - parameters:
/// - position: The position in the `ChannelPipeline` where to add the HTTP client handlers. Defaults to `.last`.
/// - leftOverBytesStrategy: The strategy to use when dealing with leftover bytes after removing the `HTTPDecoder`
/// from the pipeline.
/// - upgrade: Add a `HTTPClientUpgradeHandler` to the pipeline, configured for
/// HTTP upgrade. Should be a tuple of an array of `HTTPClientProtocolUpgrader` and
/// the upgrade completion handler. See the documentation on `HTTPClientUpgradeHandler`
@ -53,20 +58,24 @@ extension ChannelPipeline {
public func addHTTPClientHandlers(position: Position = .last,
leftOverBytesStrategy: RemoveAfterUpgradeStrategy = .dropBytes,
withClientUpgrade upgrade: NIOHTTPClientUpgradeConfiguration?) -> EventLoopFuture<Void> {
let requestEncoder = HTTPRequestEncoder()
let responseDecoder = HTTPResponseDecoder(leftOverBytesStrategy: leftOverBytesStrategy)
var handlers: [RemovableChannelHandler] = [requestEncoder, ByteToMessageHandler(responseDecoder)]
if let (upgraders, completionHandler) = upgrade {
let upgrader = NIOHTTPClientUpgradeHandler(upgraders: upgraders,
httpHandlers: handlers,
upgradeCompletionHandler: completionHandler)
handlers.append(upgrader)
let future: EventLoopFuture<Void>
if self.eventLoop.inEventLoop {
let result = Result<Void, Error> {
try self.syncOperations.addHTTPClientHandlers(position: position,
leftOverBytesStrategy: leftOverBytesStrategy,
withClientUpgrade: upgrade)
}
future = self.eventLoop.makeCompletedFuture(result)
} else {
future = self.eventLoop.submit {
return try self.syncOperations.addHTTPClientHandlers(position: position,
leftOverBytesStrategy: leftOverBytesStrategy,
withClientUpgrade: upgrade)
}
}
return self.addHandlers(handlers, position: position)
return future
}
/// Configure a `ChannelPipeline` for use as a HTTP server.
@ -98,6 +107,115 @@ extension ChannelPipeline {
withPipeliningAssistance pipelining: Bool = true,
withServerUpgrade upgrade: NIOHTTPServerUpgradeConfiguration? = nil,
withErrorHandling errorHandling: Bool = true) -> EventLoopFuture<Void> {
let future: EventLoopFuture<Void>
if self.eventLoop.inEventLoop {
let result = Result<Void, Error> {
try self.syncOperations.configureHTTPServerPipeline(position: position,
withPipeliningAssistance: pipelining,
withServerUpgrade: upgrade,
withErrorHandling: errorHandling)
}
future = self.eventLoop.makeCompletedFuture(result)
} else {
future = self.eventLoop.submit {
try self.syncOperations.configureHTTPServerPipeline(position: position,
withPipeliningAssistance: pipelining,
withServerUpgrade: upgrade,
withErrorHandling: errorHandling)
}
}
return future
}
}
extension ChannelPipeline.SynchronousOperations {
/// Configure a `ChannelPipeline` for use as a HTTP client with a client upgrader configuration.
///
/// - important: This **must** be called on the Channel's event loop.
/// - parameters:
/// - position: The position in the `ChannelPipeline` where to add the HTTP client handlers. Defaults to `.last`.
/// - leftOverBytesStrategy: The strategy to use when dealing with leftover bytes after removing the `HTTPDecoder`
/// from the pipeline.
/// - upgrade: Add a `HTTPClientUpgradeHandler` to the pipeline, configured for
/// HTTP upgrade. Should be a tuple of an array of `HTTPClientProtocolUpgrader` and
/// the upgrade completion handler. See the documentation on `HTTPClientUpgradeHandler`
/// for more details.
/// - throws: If the pipeline could not be configured.
public func addHTTPClientHandlers(position: ChannelPipeline.Position = .last,
leftOverBytesStrategy: RemoveAfterUpgradeStrategy = .dropBytes,
withClientUpgrade upgrade: NIOHTTPClientUpgradeConfiguration? = nil) throws {
// Why two separate functions? When creating the array of handlers to add to the pipeline, when we don't have
// an upgrade handler -- i.e. just an array literal -- the compiler is able to promote the array to the stack
// which saves an allocation. That's not the case when the upgrade handler is present.
if let upgrade = upgrade {
try self._addHTTPClientHandlers(position: position,
leftOverBytesStrategy: leftOverBytesStrategy,
withClientUpgrade: upgrade)
} else {
try self._addHTTPClientHandlers(position: position,
leftOverBytesStrategy: leftOverBytesStrategy)
}
}
private func _addHTTPClientHandlers(position: ChannelPipeline.Position,
leftOverBytesStrategy: RemoveAfterUpgradeStrategy) throws {
self.eventLoop.assertInEventLoop()
let requestEncoder = HTTPRequestEncoder()
let responseDecoder = HTTPResponseDecoder(leftOverBytesStrategy: leftOverBytesStrategy)
let handlers: [ChannelHandler] = [requestEncoder, ByteToMessageHandler(responseDecoder)]
try self.addHandlers(handlers, position: position)
}
private func _addHTTPClientHandlers(position: ChannelPipeline.Position,
leftOverBytesStrategy: RemoveAfterUpgradeStrategy,
withClientUpgrade upgrade: NIOHTTPClientUpgradeConfiguration) throws {
self.eventLoop.assertInEventLoop()
let requestEncoder = HTTPRequestEncoder()
let responseDecoder = HTTPResponseDecoder(leftOverBytesStrategy: leftOverBytesStrategy)
var handlers: [RemovableChannelHandler] = [requestEncoder, ByteToMessageHandler(responseDecoder)]
let upgrader = NIOHTTPClientUpgradeHandler(upgraders: upgrade.upgraders,
httpHandlers: handlers,
upgradeCompletionHandler: upgrade.completionHandler)
handlers.append(upgrader)
try self.addHandlers(handlers, position: position)
}
/// Configure a `ChannelPipeline` for use as a HTTP server.
///
/// This function knows how to set up all first-party HTTP channel handlers appropriately
/// for server use. It supports the following features:
///
/// 1. Providing assistance handling clients that pipeline HTTP requests, using the
/// `HTTPServerPipelineHandler`.
/// 2. Supporting HTTP upgrade, using the `HTTPServerUpgradeHandler`.
///
/// This method will likely be extended in future with more support for other first-party
/// features.
///
/// - important: This **must** be called on the Channel's event loop.
/// - parameters:
/// - position: Where in the pipeline to add the HTTP server handlers, defaults to `.last`.
/// - pipelining: Whether to provide assistance handling HTTP clients that pipeline
/// their requests. Defaults to `true`. If `false`, users will need to handle
/// clients that pipeline themselves.
/// - upgrade: Whether to add a `HTTPServerUpgradeHandler` to the pipeline, configured for
/// HTTP upgrade. Defaults to `nil`, which will not add the handler to the pipeline. If
/// provided should be a tuple of an array of `HTTPServerProtocolUpgrader` and the upgrade
/// completion handler. See the documentation on `HTTPServerUpgradeHandler` for more
/// details.
/// - errorHandling: Whether to provide assistance handling protocol errors (e.g.
/// failure to parse the HTTP request) by sending 400 errors. Defaults to `true`.
/// - throws: If the pipeline could not be configured.
public func configureHTTPServerPipeline(position: ChannelPipeline.Position = .last,
withPipeliningAssistance pipelining: Bool = true,
withServerUpgrade upgrade: NIOHTTPServerUpgradeConfiguration? = nil,
withErrorHandling errorHandling: Bool = true) throws {
self.eventLoop.assertInEventLoop()
let responseEncoder = HTTPResponseEncoder()
let requestDecoder = HTTPRequestDecoder(leftOverBytesStrategy: upgrade == nil ? .dropBytes : .forwardBytes)
@ -119,6 +237,6 @@ extension ChannelPipeline {
handlers.append(upgrader)
}
return self.addHandlers(handlers, position: position)
try self.addHandlers(handlers, position: position)
}
}

View File

@ -30,7 +30,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=471050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=470050
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100 # 5.2 improvement 10000
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000

View File

@ -30,7 +30,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
- MAX_ALLOCS_ALLOWED_1000_udpconnections=101050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=466050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=465050
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000

View File

@ -30,7 +30,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=18050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
- MAX_ALLOCS_ALLOWED_1000_udpconnections=107050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=943050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=942050
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
- MAX_ALLOCS_ALLOWED_creating_10000_headers=10100
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000

View File

@ -30,7 +30,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=468050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=467050
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
- MAX_ALLOCS_ALLOWED_creating_10000_headers=10100
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000

View File

@ -30,7 +30,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050 # regression from 5.3 which was 101050
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=468050 # regression from 5.3 which was 466050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=468050 # regression from 5.3 which was 465050
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000