Skip to content

Commit 6f1490e

Browse files
committed
Revert "Back out new typed HTTP protocol upgrader (apple#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.
1 parent 1040927 commit 6f1490e

13 files changed

+3089
-363
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import NIOCore
15+
16+
// MARK: - Server pipeline configuration
17+
18+
/// Configuration for an upgradable HTTP pipeline.
19+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
20+
public struct NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult: Sendable> {
21+
/// Whether to provide assistance handling HTTP clients that pipeline
22+
/// their requests. Defaults to `true`. If `false`, users will need to handle clients that pipeline themselves.
23+
public var enablePipelining = true
24+
25+
/// Whether to provide assistance handling protocol errors (e.g. failure to parse the HTTP
26+
/// request) by sending 400 errors. Defaults to `true`.
27+
public var enableErrorHandling = true
28+
29+
/// Whether to validate outbound response headers to confirm that they are
30+
/// spec compliant. Defaults to `true`.
31+
public var enableResponseHeaderValidation = true
32+
33+
/// The configuration for the ``HTTPResponseEncoder``.
34+
public var encoderConfiguration = HTTPResponseEncoder.Configuration()
35+
36+
/// The configuration for the ``NIOTypedHTTPServerUpgradeHandler``.
37+
public var upgradeConfiguration: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult>
38+
39+
/// Initializes a new ``NIOUpgradableHTTPServerPipelineConfiguration`` with default values.
40+
///
41+
/// The current defaults provide the following features:
42+
/// 1. Assistance handling clients that pipeline HTTP requests.
43+
/// 2. Assistance handling protocol errors.
44+
/// 3. Outbound header fields validation to protect against response splitting attacks.
45+
public init(
46+
upgradeConfiguration: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult>
47+
) {
48+
self.upgradeConfiguration = upgradeConfiguration
49+
}
50+
}
51+
52+
extension ChannelPipeline {
53+
/// Configure a `ChannelPipeline` for use as an HTTP server.
54+
///
55+
/// - Parameters:
56+
/// - configuration: The HTTP pipeline's configuration.
57+
/// - Returns: An `EventLoopFuture` that will fire when the pipeline is configured. The future contains an `EventLoopFuture`
58+
/// that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`.
59+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
60+
public func configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>(
61+
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult>
62+
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> {
63+
self._configureUpgradableHTTPServerPipeline(
64+
configuration: configuration
65+
)
66+
}
67+
68+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
69+
private func _configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>(
70+
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult>
71+
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> {
72+
let future: EventLoopFuture<EventLoopFuture<UpgradeResult>>
73+
74+
if self.eventLoop.inEventLoop {
75+
let result = Result<EventLoopFuture<UpgradeResult>, Error> {
76+
try self.syncOperations.configureUpgradableHTTPServerPipeline(
77+
configuration: configuration
78+
)
79+
}
80+
future = self.eventLoop.makeCompletedFuture(result)
81+
} else {
82+
future = self.eventLoop.submit {
83+
try self.syncOperations.configureUpgradableHTTPServerPipeline(
84+
configuration: configuration
85+
)
86+
}
87+
}
88+
89+
return future
90+
}
91+
}
92+
93+
extension ChannelPipeline.SynchronousOperations {
94+
/// Configure a `ChannelPipeline` for use as an HTTP server.
95+
///
96+
/// - Parameters:
97+
/// - configuration: The HTTP pipeline's configuration.
98+
/// - Returns: An `EventLoopFuture` that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`.
99+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
100+
public func configureUpgradableHTTPServerPipeline<UpgradeResult: Sendable>(
101+
configuration: NIOUpgradableHTTPServerPipelineConfiguration<UpgradeResult>
102+
) throws -> EventLoopFuture<UpgradeResult> {
103+
self.eventLoop.assertInEventLoop()
104+
105+
let responseEncoder = HTTPResponseEncoder(configuration: configuration.encoderConfiguration)
106+
let requestDecoder = ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes))
107+
108+
var extraHTTPHandlers = [RemovableChannelHandler]()
109+
extraHTTPHandlers.reserveCapacity(4)
110+
extraHTTPHandlers.append(requestDecoder)
111+
112+
try self.addHandler(responseEncoder)
113+
try self.addHandler(requestDecoder)
114+
115+
if configuration.enablePipelining {
116+
let pipeliningHandler = HTTPServerPipelineHandler()
117+
try self.addHandler(pipeliningHandler)
118+
extraHTTPHandlers.append(pipeliningHandler)
119+
}
120+
121+
if configuration.enableResponseHeaderValidation {
122+
let headerValidationHandler = NIOHTTPResponseHeadersValidator()
123+
try self.addHandler(headerValidationHandler)
124+
extraHTTPHandlers.append(headerValidationHandler)
125+
}
126+
127+
if configuration.enableErrorHandling {
128+
let errorHandler = HTTPServerProtocolErrorHandler()
129+
try self.addHandler(errorHandler)
130+
extraHTTPHandlers.append(errorHandler)
131+
}
132+
133+
let upgrader = NIOTypedHTTPServerUpgradeHandler(
134+
httpEncoder: responseEncoder,
135+
extraHTTPHandlers: extraHTTPHandlers,
136+
upgradeConfiguration: configuration.upgradeConfiguration
137+
)
138+
try self.addHandler(upgrader)
139+
140+
return upgrader.upgradeResultFuture
141+
}
142+
}
143+
144+
// MARK: - Client pipeline configuration
145+
146+
/// Configuration for an upgradable HTTP pipeline.
147+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
148+
public struct NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult: Sendable> {
149+
/// The strategy to use when dealing with leftover bytes after removing the ``HTTPDecoder`` from the pipeline.
150+
public var leftOverBytesStrategy = RemoveAfterUpgradeStrategy.dropBytes
151+
152+
/// Whether to validate outbound response headers to confirm that they are
153+
/// spec compliant. Defaults to `true`.
154+
public var enableOutboundHeaderValidation = true
155+
156+
/// The configuration for the ``HTTPRequestEncoder``.
157+
public var encoderConfiguration = HTTPRequestEncoder.Configuration()
158+
159+
/// The configuration for the ``NIOTypedHTTPClientUpgradeHandler``.
160+
public var upgradeConfiguration: NIOTypedHTTPClientUpgradeConfiguration<UpgradeResult>
161+
162+
/// Initializes a new ``NIOUpgradableHTTPClientPipelineConfiguration`` with default values.
163+
///
164+
/// The current defaults provide the following features:
165+
/// 1. Outbound header fields validation to protect against response splitting attacks.
166+
public init(
167+
upgradeConfiguration: NIOTypedHTTPClientUpgradeConfiguration<UpgradeResult>
168+
) {
169+
self.upgradeConfiguration = upgradeConfiguration
170+
}
171+
}
172+
173+
extension ChannelPipeline {
174+
/// Configure a `ChannelPipeline` for use as an HTTP client.
175+
///
176+
/// - Parameters:
177+
/// - configuration: The HTTP pipeline's configuration.
178+
/// - Returns: An `EventLoopFuture` that will fire when the pipeline is configured. The future contains an `EventLoopFuture`
179+
/// that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`.
180+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
181+
public func configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>(
182+
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult>
183+
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> {
184+
self._configureUpgradableHTTPClientPipeline(configuration: configuration)
185+
}
186+
187+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
188+
private func _configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>(
189+
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult>
190+
) -> EventLoopFuture<EventLoopFuture<UpgradeResult>> {
191+
let future: EventLoopFuture<EventLoopFuture<UpgradeResult>>
192+
193+
if self.eventLoop.inEventLoop {
194+
let result = Result<EventLoopFuture<UpgradeResult>, Error> {
195+
try self.syncOperations.configureUpgradableHTTPClientPipeline(
196+
configuration: configuration
197+
)
198+
}
199+
future = self.eventLoop.makeCompletedFuture(result)
200+
} else {
201+
future = self.eventLoop.submit {
202+
try self.syncOperations.configureUpgradableHTTPClientPipeline(
203+
configuration: configuration
204+
)
205+
}
206+
}
207+
208+
return future
209+
}
210+
}
211+
212+
extension ChannelPipeline.SynchronousOperations {
213+
/// Configure a `ChannelPipeline` for use as an HTTP client.
214+
///
215+
/// - Parameters:
216+
/// - configuration: The HTTP pipeline's configuration.
217+
/// - Returns: An `EventLoopFuture` that is fired once the pipeline has been upgraded or not and contains the `UpgradeResult`.
218+
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
219+
public func configureUpgradableHTTPClientPipeline<UpgradeResult: Sendable>(
220+
configuration: NIOUpgradableHTTPClientPipelineConfiguration<UpgradeResult>
221+
) throws -> EventLoopFuture<UpgradeResult> {
222+
self.eventLoop.assertInEventLoop()
223+
224+
let requestEncoder = HTTPRequestEncoder(configuration: configuration.encoderConfiguration)
225+
let responseDecoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: configuration.leftOverBytesStrategy))
226+
var httpHandlers = [RemovableChannelHandler]()
227+
httpHandlers.reserveCapacity(3)
228+
httpHandlers.append(requestEncoder)
229+
httpHandlers.append(responseDecoder)
230+
231+
try self.addHandler(requestEncoder)
232+
try self.addHandler(responseDecoder)
233+
234+
if configuration.enableOutboundHeaderValidation {
235+
let headerValidationHandler = NIOHTTPRequestHeadersValidator()
236+
try self.addHandler(headerValidationHandler)
237+
httpHandlers.append(headerValidationHandler)
238+
}
239+
240+
let upgrader = NIOTypedHTTPClientUpgradeHandler(
241+
httpHandlers: httpHandlers,
242+
upgradeConfiguration: configuration.upgradeConfiguration
243+
)
244+
try self.addHandler(upgrader)
245+
246+
return upgrader.upgradeResultFuture
247+
}
248+
}

0 commit comments

Comments
 (0)