Skip to content

Make HTTP/2 flow control window size configurable for clients and servers #786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "82eb3fa0974b838358ee46bc6c5381e5ae5de6b9",
"version": "1.11.0"
"revision": "c8f952dbc37fe60def17eb15e2c90787ce6ee78a",
"version": "1.12.1"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let package = Package(
// Main SwiftNIO package
.package(url: "https://github.com/apple/swift-nio.git", from: "2.14.0"),
// HTTP2 via SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.8.0"),
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.12.1"),
// TLS via SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.6.2"),
// Support for Network.framework where possible.
Expand Down
11 changes: 9 additions & 2 deletions Sources/GRPC/ClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ extension ClientConnection {
/// The connection backoff configuration. If no connection retrying is required then this should
/// be `nil`.
public var connectionBackoff: ConnectionBackoff?

/// The HTTP/2 flow control target window size.
public var httpTargetWindowSize: Int

/// The HTTP protocol used for this connection.
public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
Expand All @@ -258,20 +261,23 @@ extension ClientConnection {
/// - Parameter tlsConfiguration: TLS configuration, defaulting to `nil`.
/// - Parameter connectionBackoff: The connection backoff configuration to use.
/// - Parameter messageEncoding: Message compression configuration, defaults to no compression.
/// - Parameter targetWindowSize: The HTTP/2 flow control target window size.
public init(
target: ConnectionTarget,
eventLoopGroup: EventLoopGroup,
errorDelegate: ClientErrorDelegate? = LoggingClientErrorDelegate(),
connectivityStateDelegate: ConnectivityStateDelegate? = nil,
tls: Configuration.TLS? = nil,
connectionBackoff: ConnectionBackoff? = ConnectionBackoff()
connectionBackoff: ConnectionBackoff? = ConnectionBackoff(),
httpTargetWindowSize: Int = 65535
) {
self.target = target
self.eventLoopGroup = eventLoopGroup
self.errorDelegate = errorDelegate
self.connectivityStateDelegate = connectivityStateDelegate
self.tls = tls
self.connectionBackoff = connectionBackoff
self.httpTargetWindowSize = httpTargetWindowSize
}
}
}
Expand Down Expand Up @@ -324,6 +330,7 @@ extension Channel {
}

func configureGRPCClient(
httpTargetWindowSize: Int,
tlsConfiguration: TLSConfiguration?,
tlsServerHostname: String?,
connectionManager: ConnectionManager,
Expand All @@ -335,7 +342,7 @@ extension Channel {
}

return (tlsConfigured ?? self.eventLoop.makeSucceededFuture(())).flatMap {
self.configureHTTP2Pipeline(mode: .client)
self.configureHTTP2Pipeline(mode: .client, targetWindowSize: httpTargetWindowSize)
}.flatMap { _ in
return self.pipeline.handler(type: NIOHTTP2Handler.self).flatMap { http2Handler in
self.pipeline.addHandler(
Expand Down
1 change: 1 addition & 0 deletions Sources/GRPC/ConnectionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ extension ConnectionManager {
.channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
.channelInitializer { channel in
channel.configureGRPCClient(
httpTargetWindowSize: configuration.httpTargetWindowSize,
tlsConfiguration: configuration.tls?.configuration,
tlsServerHostname: serverHostname,
connectionManager: self,
Expand Down
13 changes: 12 additions & 1 deletion Sources/GRPC/GRPCChannel/GRPCChannelBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extension ClientConnection {
private var connectionBackoffIsEnabled = true
private var errorDelegate: ClientErrorDelegate?
private var connectivityStateDelegate: ConnectivityStateDelegate?
private var httpTargetWindowSize: Int = 65535

fileprivate init(group: EventLoopGroup) {
self.group = group
Expand All @@ -48,7 +49,8 @@ extension ClientConnection {
errorDelegate: self.errorDelegate,
connectivityStateDelegate: self.connectivityStateDelegate,
tls: self.maybeTLS,
connectionBackoff: self.connectionBackoffIsEnabled ? self.connectionBackoff : nil
connectionBackoff: self.connectionBackoffIsEnabled ? self.connectionBackoff : nil,
httpTargetWindowSize: self.httpTargetWindowSize
)
return ClientConnection(configuration: configuration)
}
Expand Down Expand Up @@ -195,6 +197,15 @@ extension ClientConnection.Builder.Secure {
}
}

extension ClientConnection.Builder {
/// Sets the HTTP/2 flow control target window size. Defaults to 65,535 if not explicitly set.
@discardableResult
public func withHTTPTargetWindowSize(_ httpTargetWindowSize: Int) -> Self {
self.httpTargetWindowSize = httpTargetWindowSize
return self
}
}

fileprivate extension Double {
static func seconds(from amount: TimeAmount) -> Double {
return Double(amount.nanoseconds) / 1_000_000_000
Expand Down
13 changes: 11 additions & 2 deletions Sources/GRPC/HTTPProtocolSwitcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class HTTPProtocolSwitcher {
private let handlersInitializer: ((Channel) -> EventLoopFuture<Void>)
private let errorDelegate: ServerErrorDelegate?
private let logger = Logger(subsystem: .serverChannelCall)
private let httpTargetWindowSize: Int

// We could receive additional data after the initial data and before configuring
// the pipeline; buffer it and fire it down the pipeline once it is configured.
Expand All @@ -41,8 +42,13 @@ internal class HTTPProtocolSwitcher {
}
private var bufferedData: [NIOAny] = []

init(errorDelegate: ServerErrorDelegate?, handlersInitializer: (@escaping (Channel) -> EventLoopFuture<Void>)) {
init(
errorDelegate: ServerErrorDelegate?,
httpTargetWindowSize: Int = 65535,
handlersInitializer: (@escaping (Channel) -> EventLoopFuture<Void>)
) {
self.errorDelegate = errorDelegate
self.httpTargetWindowSize = httpTargetWindowSize
self.handlersInitializer = handlersInitializer
}
}
Expand Down Expand Up @@ -132,7 +138,10 @@ extension HTTPProtocolSwitcher: ChannelInboundHandler, RemovableChannelHandler {
.cascade(to: pipelineConfigured)

case .http2:
context.channel.configureHTTP2Pipeline(mode: .server) { (streamChannel, streamID) in
context.channel.configureHTTP2Pipeline(
mode: .server,
targetWindowSize: httpTargetWindowSize
) { (streamChannel, streamID) in
streamChannel.pipeline.addHandler(HTTP2ToHTTP1ServerCodec(streamID: streamID, normalizeHTTPHeaders: true))
.flatMap { self.handlersInitializer(streamChannel) }
}
Expand Down
13 changes: 10 additions & 3 deletions Sources/GRPC/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ public final class Server {
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
// Set the handlers that are applied to the accepted Channels
.childChannelInitializer { channel in
let protocolSwitcher = HTTPProtocolSwitcher(errorDelegate: configuration.errorDelegate) { channel -> EventLoopFuture<Void> in
let protocolSwitcher = HTTPProtocolSwitcher(
errorDelegate: configuration.errorDelegate,
httpTargetWindowSize: configuration.httpTargetWindowSize
) { channel -> EventLoopFuture<Void> in
let logger = Logger(subsystem: .serverChannelCall, metadata: [MetadataKey.requestID: "\(UUID())"])
let handler = GRPCServerRequestRoutingHandler(
servicesByName: configuration.serviceProvidersByName,
Expand Down Expand Up @@ -173,7 +176,6 @@ extension Server {
public struct Configuration {
/// The target to bind to.
public var target: BindTarget

/// The event loop group to run the connection on.
public var eventLoopGroup: EventLoopGroup

Expand All @@ -196,6 +198,9 @@ extension Server {
/// streaming and bidirectional streaming RPCs) by passing setting `compression` to `.disabled`
/// in `sendResponse(_:compression)`.
public var messageEncoding: ServerMessageEncoding

/// The HTTP/2 flow control target window size.
public var httpTargetWindowSize: Int

/// Create a `Configuration` with some pre-defined defaults.
///
Expand All @@ -212,14 +217,16 @@ extension Server {
serviceProviders: [CallHandlerProvider],
errorDelegate: ServerErrorDelegate? = LoggingServerErrorDelegate.shared,
tls: TLS? = nil,
messageEncoding: ServerMessageEncoding = .disabled
messageEncoding: ServerMessageEncoding = .disabled,
httpTargetWindowSize: Int = 65535
) {
self.target = target
self.eventLoopGroup = eventLoopGroup
self.serviceProviders = serviceProviders
self.errorDelegate = errorDelegate
self.tls = tls
self.messageEncoding = messageEncoding
self.httpTargetWindowSize = httpTargetWindowSize
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion Sources/GRPC/ServerBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extension Server {
private var providers: [CallHandlerProvider] = []
private var errorDelegate: ServerErrorDelegate?
private var messageEncoding: ServerMessageEncoding = .disabled
private var httpTargetWindowSize: Int = 65535

fileprivate init(group: EventLoopGroup) {
self.group = group
Expand Down Expand Up @@ -50,7 +51,8 @@ extension Server {
serviceProviders: self.providers,
errorDelegate: self.errorDelegate,
tls: self.maybeTLS,
messageEncoding: self.messageEncoding
messageEncoding: self.messageEncoding,
httpTargetWindowSize: self.httpTargetWindowSize
)
return Server.start(configuration: configuration)
}
Expand Down Expand Up @@ -102,6 +104,15 @@ extension Server.Builder.Secure {
}
}

extension Server.Builder {
/// Sets the HTTP/2 flow control target window size. Defaults to 65,535 if not explicitly set.
@discardableResult
public func withHTTPTargetWindowSize(_ httpTargetWindowSize: Int) -> Self {
self.httpTargetWindowSize = httpTargetWindowSize
return self
}
}

extension Server {
/// Returns an insecure `Server` builder which is *not configured with TLS*.
public static func insecure(group: EventLoopGroup) -> Builder {
Expand Down