Skip to content

Commit 46d1a29

Browse files
authored
Update to SwiftNIO HTTP/2 1.13.0 (#922)
Motivation: The multiplexer from SwiftNIO HTTP/2 required that the first write on each stream matched the order in which the streams were created. Violating this led to a connection error; all in-flight and subsequent RPCs on that connection would fail. Some of our users noticed this (#912). SwiftNIO HTTP/2 recently reworked the API around how streams are created: HTTP/2 stream channels are no longer created with a stream ID and now deliver the frame payload to the channel pipeline rather than the entire HTTP/2 frame. This allows for a stream ID to be assigned to a stream when it attempts to flush its first write, rather than when the stream is created. Modifications: - Increase the minimum HTTP/2 version to 1.13.0 - Move away from deprecated APIs: this required changing the inbound-in and outbound-out types for `_GRPCClientChannelHandler` as well as a few smaller changes elsewhere. Result: - RPCs can be created concurrently without fear of violating any stream ID ordering rules - Resolves #912
1 parent 2006e52 commit 46d1a29

8 files changed

+40
-48
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
2525
"state": {
2626
"branch": null,
27-
"revision": "c5d10f4165128c3d0cc0e3c0f0a8ef55947a73a6",
28-
"version": "1.12.2"
27+
"revision": "e9627350bdb85bde7e0dc69a29799e40961ced72",
28+
"version": "1.13.0"
2929
}
3030
},
3131
{

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ let package = Package(
2929
// Main SwiftNIO package
3030
.package(url: "https://github.com/apple/swift-nio.git", from: "2.19.0"),
3131
// HTTP2 via SwiftNIO
32-
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.12.1"),
32+
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.13.0"),
3333
// TLS via SwiftNIO
3434
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),
3535
// Support for Network.framework where possible.

Sources/GRPC/ClientCalls/ClientCallTransport.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,11 @@ internal class ChannelTransport<Request, Response> {
168168
multiplexer.whenComplete { result in
169169
switch result {
170170
case .success(let mux):
171-
mux.createStreamChannel(promise: streamPromise) { stream, streamID in
172-
var logger = logger
173-
logger[metadataKey: MetadataKey.streamID] = "\(streamID)"
171+
mux.createStreamChannel(promise: streamPromise) { stream in
174172
logger.trace("created http/2 stream")
175173

176174
return stream.pipeline.addHandlers([
177-
_GRPCClientChannelHandler(streamID: streamID, callType: callType, logger: logger),
175+
_GRPCClientChannelHandler(callType: callType, logger: logger),
178176
GRPCClientCodecHandler(serializer: serializer, deserializer: deserializer),
179177
GRPCClientCallHandler(call: call)
180178
])

Sources/GRPC/ClientConnection.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ extension ClientConnection {
509509
public var httpTargetWindowSize: Int
510510

511511
/// The HTTP protocol used for this connection.
512-
public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
512+
public var httpProtocol: HTTP2FramePayloadToHTTP1ClientCodec.HTTPProtocol {
513513
return self.tls == nil ? .http : .https
514514
}
515515

@@ -642,7 +642,7 @@ extension Channel {
642642
}
643643

644644
let configuration: EventLoopFuture<Void> = (tlsConfigured ?? self.eventLoop.makeSucceededFuture(())).flatMap {
645-
self.configureHTTP2Pipeline(mode: .client, targetWindowSize: httpTargetWindowSize)
645+
self.configureHTTP2Pipeline(mode: .client, targetWindowSize: httpTargetWindowSize, inboundStreamInitializer: nil)
646646
}.flatMap { _ in
647647
return self.pipeline.handler(type: NIOHTTP2Handler.self).flatMap { http2Handler in
648648
self.pipeline.addHandlers([
@@ -677,7 +677,7 @@ extension Channel {
677677
errorDelegate: ClientErrorDelegate?,
678678
logger: Logger
679679
) -> EventLoopFuture<Void> {
680-
return self.configureHTTP2Pipeline(mode: .client).flatMap { _ in
680+
return self.configureHTTP2Pipeline(mode: .client, inboundStreamInitializer: nil).flatMap { _ in
681681
self.pipeline.addHandler(DelegatingErrorHandler(logger: logger, delegate: errorDelegate))
682682
}
683683
}

Sources/GRPC/HTTPProtocolSwitcher.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,22 @@ extension HTTPProtocolSwitcher: ChannelInboundHandler, RemovableChannelHandler {
145145
context.channel.configureHTTP2Pipeline(
146146
mode: .server,
147147
targetWindowSize: httpTargetWindowSize
148-
) { (streamChannel, streamID) in
148+
) { streamChannel in
149149
var logger = self.logger
150-
logger[metadataKey: MetadataKey.streamID] = "\(streamID)"
151-
return streamChannel.pipeline.addHandler(HTTP2ToHTTP1ServerCodec(streamID: streamID, normalizeHTTPHeaders: true)).flatMap {
152-
self.handlersInitializer(streamChannel, logger)
150+
151+
// Grab the streamID from the channel.
152+
return streamChannel.getOption(HTTP2StreamChannelOptions.streamID).map { streamID in
153+
logger[metadataKey: MetadataKey.streamID] = "\(streamID)"
154+
return logger
155+
}.recover { _ in
156+
logger[metadataKey: MetadataKey.streamID] = "<unknown>"
157+
return logger
158+
}.flatMap { logger in
159+
return streamChannel.pipeline.addHandler(HTTP2FramePayloadToHTTP1ServerCodec()).flatMap {
160+
self.handlersInitializer(streamChannel, logger)
161+
}
153162
}
154-
}.flatMap { multiplexer in
163+
}.flatMap { multiplexer -> EventLoopFuture<Void> in
155164
// Add a keepalive and idle handlers between the two HTTP2 handlers.
156165
let keepaliveHandler = GRPCServerKeepaliveHandler(configuration: self.keepAlive)
157166
let idleHandler = GRPCIdleHandler(mode: .server, idleTimeout: self.idleTimeout)

Sources/GRPC/_EmbeddedThroughput.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extension EmbeddedChannel {
2828
responseType: Response.Type = Response.self
2929
) -> EventLoopFuture<Void> {
3030
return self.pipeline.addHandlers([
31-
_GRPCClientChannelHandler(streamID: 1, callType: callType, logger: logger),
31+
_GRPCClientChannelHandler(callType: callType, logger: logger),
3232
GRPCClientCodecHandler(
3333
serializer: ProtobufSerializer<Request>(),
3434
deserializer: ProtobufDeserializer<Response>()

Sources/GRPC/_GRPCClientChannelHandler.swift

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -268,18 +268,14 @@ public enum GRPCCallType {
268268
/// `public` because it is used within performance tests.
269269
public final class _GRPCClientChannelHandler {
270270
private let logger: Logger
271-
private let streamID: HTTP2StreamID
272271
private var stateMachine: GRPCClientStateMachine
273272

274273
/// Creates a new gRPC channel handler for clients to translate HTTP/2 frames to gRPC messages.
275274
///
276275
/// - Parameters:
277-
/// - streamID: The ID of the HTTP/2 stream that this handler will read and write HTTP/2
278-
/// frames on.
279276
/// - callType: Type of RPC call being made.
280277
/// - logger: Logger.
281-
public init(streamID: HTTP2StreamID, callType: GRPCCallType, logger: Logger) {
282-
self.streamID = streamID
278+
public init(callType: GRPCCallType, logger: Logger) {
283279
self.logger = logger
284280
switch callType {
285281
case .unary:
@@ -296,12 +292,12 @@ public final class _GRPCClientChannelHandler {
296292

297293
// MARK: - GRPCClientChannelHandler: Inbound
298294
extension _GRPCClientChannelHandler: ChannelInboundHandler {
299-
public typealias InboundIn = HTTP2Frame
295+
public typealias InboundIn = HTTP2Frame.FramePayload
300296
public typealias InboundOut = _RawGRPCClientResponsePart
301297

302298
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
303-
let frame = self.unwrapInboundIn(data)
304-
switch frame.payload {
299+
let payload = self.unwrapInboundIn(data)
300+
switch payload {
305301
case .headers(let content):
306302
self.readHeaders(content: content, context: context)
307303

@@ -437,7 +433,7 @@ extension _GRPCClientChannelHandler: ChannelInboundHandler {
437433
// MARK: - GRPCClientChannelHandler: Outbound
438434
extension _GRPCClientChannelHandler: ChannelOutboundHandler {
439435
public typealias OutboundIn = _RawGRPCClientRequestPart
440-
public typealias OutboundOut = HTTP2Frame
436+
public typealias OutboundOut = HTTP2Frame.FramePayload
441437

442438
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
443439
switch self.unwrapOutboundIn(data) {
@@ -446,8 +442,8 @@ extension _GRPCClientChannelHandler: ChannelOutboundHandler {
446442
switch self.stateMachine.sendRequestHeaders(requestHead: requestHead) {
447443
case .success(let headers):
448444
// We're clear to write some headers. Create an appropriate frame and write it.
449-
let frame = HTTP2Frame(streamID: self.streamID, payload: .headers(.init(headers: headers)))
450-
context.write(self.wrapOutboundOut(frame), promise: promise)
445+
let framePayload = HTTP2Frame.FramePayload.headers(.init(headers: headers))
446+
context.write(self.wrapOutboundOut(framePayload), promise: promise)
451447

452448
case .failure(let sendRequestHeadersError):
453449
switch sendRequestHeadersError {
@@ -464,11 +460,8 @@ extension _GRPCClientChannelHandler: ChannelOutboundHandler {
464460
switch result {
465461
case .success(let buffer):
466462
// We're clear to send a message; wrap it up in an HTTP/2 frame.
467-
let frame = HTTP2Frame(
468-
streamID: self.streamID,
469-
payload: .data(.init(data: .byteBuffer(buffer)))
470-
)
471-
context.write(self.wrapOutboundOut(frame), promise: promise)
463+
let framePayload = HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))
464+
context.write(self.wrapOutboundOut(framePayload), promise: promise)
472465

473466
case .failure(let writeError):
474467
switch writeError {
@@ -493,11 +486,8 @@ extension _GRPCClientChannelHandler: ChannelOutboundHandler {
493486
case .success:
494487
// We can. Send an empty DATA frame with end-stream set.
495488
let empty = context.channel.allocator.buffer(capacity: 0)
496-
let frame = HTTP2Frame(
497-
streamID: self.streamID,
498-
payload: .data(.init(data: .byteBuffer(empty), endStream: true))
499-
)
500-
context.write(self.wrapOutboundOut(frame), promise: promise)
489+
let framePayload = HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(empty), endStream: true))
490+
context.write(self.wrapOutboundOut(framePayload), promise: promise)
501491

502492
case .failure(let error):
503493
// Why can't we close the request stream?

Tests/GRPCTests/GRPCStatusCodeTests.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,13 @@ class GRPCStatusCodeTests: GRPCTestCase {
2929
override func setUp() {
3030
super.setUp()
3131

32-
let handler = _GRPCClientChannelHandler(
33-
streamID: .init(1),
34-
callType: .unary,
35-
logger: self.logger
36-
)
37-
32+
let handler = _GRPCClientChannelHandler(callType: .unary, logger: self.logger)
3833
self.channel = EmbeddedChannel(handler: handler)
3934
}
4035

41-
func headersFrame(status: HTTPResponseStatus) -> HTTP2Frame {
36+
func headersFramePayload(status: HTTPResponseStatus) -> HTTP2Frame.FramePayload {
4237
let headers: HPACKHeaders = [":status": "\(status.code)"]
43-
return .init(streamID: .init(1), payload: .headers(.init(headers: headers)))
38+
return .headers(.init(headers: headers))
4439
}
4540

4641
func sendRequestHead() {
@@ -60,7 +55,7 @@ class GRPCStatusCodeTests: GRPCTestCase {
6055
func doTestResponseStatus(_ status: HTTPResponseStatus, expected: GRPCStatus.Code) throws {
6156
// Send the request head so we're in a valid state to receive headers.
6257
self.sendRequestHead()
63-
XCTAssertThrowsError(try self.channel.writeInbound(self.headersFrame(status: status))) { error in
58+
XCTAssertThrowsError(try self.channel.writeInbound(self.headersFramePayload(status: status))) { error in
6459
guard let withContext = error as? GRPCError.WithContext,
6560
let invalidHTTPStatus = withContext.error as? GRPCError.InvalidHTTPStatus else {
6661
XCTFail("Unexpected error: \(error)")
@@ -113,8 +108,8 @@ class GRPCStatusCodeTests: GRPCTestCase {
113108
]
114109

115110
self.sendRequestHead()
116-
let headerFrame = HTTP2Frame(streamID: .init(1), payload: .headers(.init(headers: headers)))
117-
XCTAssertThrowsError(try self.channel.writeInbound(headerFrame)) { error in
111+
let headerFramePayload = HTTP2Frame.FramePayload.headers(.init(headers: headers))
112+
XCTAssertThrowsError(try self.channel.writeInbound(headerFramePayload)) { error in
118113
guard let withContext = error as? GRPCError.WithContext,
119114
let invalidHTTPStatus = withContext.error as? GRPCError.InvalidHTTPStatusWithGRPCStatus else {
120115
XCTFail("Unexpected error: \(error)")

0 commit comments

Comments
 (0)