-
Notifications
You must be signed in to change notification settings - Fork 653
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Revert "Back out new typed HTTP protocol upgrader (#2579)" # Motivation We have reverted the typed HTTP protocol upgrader pieces since adopters were running into a compiler bug (swiftlang/swift#69459) that caused the compiler to emit strong references to `swift_getExtendedExistentialTypeMetadata`. The problem is that `swift_getExtendedExistentialTypeMetadata` is not available on older runtimes before constrained existentials have been introduced. This caused adopters to run into runtime crashes when loading any library compiled with this NIO code. # Modifications This PR reverts the revert and guard all new code in a compiler guard that checks that we are either on non-Darwin platforms or on a new enough Swift compiler that contains the fix. # Result We can offer the typed HTTP upgrade code to our adopters again. * Add compiler guards
- Loading branch information
1 parent
1040927
commit c461993
Showing
13 changed files
with
3,124 additions
and
365 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
#if !canImport(Darwin) || (canImport(Darwin) && swift(>=5.10)) | ||
import NIOCore | ||
|
||
// MARK: - Server pipeline configuration | ||
|
||
/// Configuration for an upgradable HTTP pipeline. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public struct NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult: Sendable> { | ||
/// 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. | ||
public var enablePipelining = true | ||
|
||
/// Whether to provide assistance handling protocol errors (e.g. failure to parse the HTTP | ||
/// request) by sending 400 errors. Defaults to `true`. | ||
public var enableErrorHandling = true | ||
|
||
/// Whether to validate outbound response headers to confirm that they are | ||
/// spec compliant. Defaults to `true`. | ||
public var enableResponseHeaderValidation = true | ||
|
||
/// The configuration for the ``HTTPResponseEncoder``. | ||
public var encoderConfiguration = HTTPResponseEncoder.Configuration() | ||
|
||
/// The configuration for the ``NIOTypedHTTPServerUpgradeHandler``. | ||
public var upgradeConfiguration: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult> | ||
|
||
/// Initializes a new ``NIOUpgradableHTTPServerPipelineConfiguration`` with default values. | ||
/// | ||
/// The current defaults provide the following features: | ||
/// 1. Assistance handling clients that pipeline HTTP requests. | ||
/// 2. Assistance handling protocol errors. | ||
/// 3. Outbound header fields validation to protect against response splitting attacks. | ||
public init( | ||
upgradeConfiguration: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult> | ||
) { | ||
self.upgradeConfiguration = upgradeConfiguration | ||
} | ||
} | ||
|
||
extension ChannelPipeline { | ||
/// Configure a `ChannelPipeline` for use as an HTTP server. | ||
/// | ||
/// - Parameters: | ||
/// - configuration: The HTTP pipeline's configuration. | ||
/// - Returns: An `EventLoopFuture` that will fire when the pipeline is configured. The future contains an `EventLoopFuture` | ||
/// that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public func configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult> | ||
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> { | ||
self._configureUpgradableHTTPServerPipeline( | ||
configuration: configuration | ||
) | ||
} | ||
|
||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
private func _configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult> | ||
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> { | ||
let future: EventLoopFuture<EventLoopFuture<UpgradeResult>> | ||
|
||
if self.eventLoop.inEventLoop { | ||
let result = Result<EventLoopFuture<UpgradeResult>, Error> { | ||
try self.syncOperations.configureUpgradableHTTPServerPipeline( | ||
configuration: configuration | ||
) | ||
} | ||
future = self.eventLoop.makeCompletedFuture(result) | ||
} else { | ||
future = self.eventLoop.submit { | ||
try self.syncOperations.configureUpgradableHTTPServerPipeline( | ||
configuration: configuration | ||
) | ||
} | ||
} | ||
|
||
return future | ||
} | ||
} | ||
|
||
extension ChannelPipeline.SynchronousOperations { | ||
/// Configure a `ChannelPipeline` for use as an HTTP server. | ||
/// | ||
/// - Parameters: | ||
/// - configuration: The HTTP pipeline's configuration. | ||
/// - Returns: An `EventLoopFuture` that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public func configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult> | ||
) throws -> EventLoopFuture<UpgradeResult> { | ||
self.eventLoop.assertInEventLoop() | ||
|
||
let responseEncoder = HTTPResponseEncoder(configuration: configuration.encoderConfiguration) | ||
let requestDecoder = ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes)) | ||
|
||
var extraHTTPHandlers = [RemovableChannelHandler]() | ||
extraHTTPHandlers.reserveCapacity(4) | ||
extraHTTPHandlers.append(requestDecoder) | ||
|
||
try self.addHandler(responseEncoder) | ||
try self.addHandler(requestDecoder) | ||
|
||
if configuration.enablePipelining { | ||
let pipeliningHandler = HTTPServerPipelineHandler() | ||
try self.addHandler(pipeliningHandler) | ||
extraHTTPHandlers.append(pipeliningHandler) | ||
} | ||
|
||
if configuration.enableResponseHeaderValidation { | ||
let headerValidationHandler = NIOHTTPResponseHeadersValidator() | ||
try self.addHandler(headerValidationHandler) | ||
extraHTTPHandlers.append(headerValidationHandler) | ||
} | ||
|
||
if configuration.enableErrorHandling { | ||
let errorHandler = HTTPServerProtocolErrorHandler() | ||
try self.addHandler(errorHandler) | ||
extraHTTPHandlers.append(errorHandler) | ||
} | ||
|
||
let upgrader = NIOTypedHTTPServerUpgradeHandler( | ||
httpEncoder: responseEncoder, | ||
extraHTTPHandlers: extraHTTPHandlers, | ||
upgradeConfiguration: configuration.upgradeConfiguration | ||
) | ||
try self.addHandler(upgrader) | ||
|
||
return upgrader.upgradeResultFuture | ||
} | ||
} | ||
|
||
// MARK: - Client pipeline configuration | ||
|
||
/// Configuration for an upgradable HTTP pipeline. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public struct NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult: Sendable> { | ||
/// The strategy to use when dealing with leftover bytes after removing the ``HTTPDecoder`` from the pipeline. | ||
public var leftOverBytesStrategy = RemoveAfterUpgradeStrategy.dropBytes | ||
|
||
/// Whether to validate outbound response headers to confirm that they are | ||
/// spec compliant. Defaults to `true`. | ||
public var enableOutboundHeaderValidation = true | ||
|
||
/// The configuration for the ``HTTPRequestEncoder``. | ||
public var encoderConfiguration = HTTPRequestEncoder.Configuration() | ||
|
||
/// The configuration for the ``NIOTypedHTTPClientUpgradeHandler``. | ||
public var upgradeConfiguration: NIOTypedHTTPClientUpgradeConfiguration<UpgradeResult> | ||
|
||
/// Initializes a new ``NIOUpgradableHTTPClientPipelineConfiguration`` with default values. | ||
/// | ||
/// The current defaults provide the following features: | ||
/// 1. Outbound header fields validation to protect against response splitting attacks. | ||
public init( | ||
upgradeConfiguration: NIOTypedHTTPClientUpgradeConfiguration<UpgradeResult> | ||
) { | ||
self.upgradeConfiguration = upgradeConfiguration | ||
} | ||
} | ||
|
||
extension ChannelPipeline { | ||
/// Configure a `ChannelPipeline` for use as an HTTP client. | ||
/// | ||
/// - Parameters: | ||
/// - configuration: The HTTP pipeline's configuration. | ||
/// - Returns: An `EventLoopFuture` that will fire when the pipeline is configured. The future contains an `EventLoopFuture` | ||
/// that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public func configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult> | ||
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> { | ||
self._configureUpgradableHTTPClientPipeline(configuration: configuration) | ||
} | ||
|
||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
private func _configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult> | ||
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> { | ||
let future: EventLoopFuture<EventLoopFuture<UpgradeResult>> | ||
|
||
if self.eventLoop.inEventLoop { | ||
let result = Result<EventLoopFuture<UpgradeResult>, Error> { | ||
try self.syncOperations.configureUpgradableHTTPClientPipeline( | ||
configuration: configuration | ||
) | ||
} | ||
future = self.eventLoop.makeCompletedFuture(result) | ||
} else { | ||
future = self.eventLoop.submit { | ||
try self.syncOperations.configureUpgradableHTTPClientPipeline( | ||
configuration: configuration | ||
) | ||
} | ||
} | ||
|
||
return future | ||
} | ||
} | ||
|
||
extension ChannelPipeline.SynchronousOperations { | ||
/// Configure a `ChannelPipeline` for use as an HTTP client. | ||
/// | ||
/// - Parameters: | ||
/// - configuration: The HTTP pipeline's configuration. | ||
/// - Returns: An `EventLoopFuture` that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`. | ||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public func configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>( | ||
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult> | ||
) throws -> EventLoopFuture<UpgradeResult> { | ||
self.eventLoop.assertInEventLoop() | ||
|
||
let requestEncoder = HTTPRequestEncoder(configuration: configuration.encoderConfiguration) | ||
let responseDecoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: configuration.leftOverBytesStrategy)) | ||
var httpHandlers = [RemovableChannelHandler]() | ||
httpHandlers.reserveCapacity(3) | ||
httpHandlers.append(requestEncoder) | ||
httpHandlers.append(responseDecoder) | ||
|
||
try self.addHandler(requestEncoder) | ||
try self.addHandler(responseDecoder) | ||
|
||
if configuration.enableOutboundHeaderValidation { | ||
let headerValidationHandler = NIOHTTPRequestHeadersValidator() | ||
try self.addHandler(headerValidationHandler) | ||
httpHandlers.append(headerValidationHandler) | ||
} | ||
|
||
let upgrader = NIOTypedHTTPClientUpgradeHandler( | ||
httpHandlers: httpHandlers, | ||
upgradeConfiguration: configuration.upgradeConfiguration | ||
) | ||
try self.addHandler(upgrader) | ||
|
||
return upgrader.upgradeResultFuture | ||
} | ||
} | ||
#endif |
Oops, something went wrong.