Skip to content

Add HTTP2Connection #401

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 12 commits into from
Jul 22, 2021
Merged

Conversation

fabianfett
Copy link
Member

This pr adds an HTTP2Connection that we want to use in our connection pool going forward.

It has no tests at all. I would like to get some early feedback before adding those.

@fabianfett fabianfett added the 🔨 semver/patch No public API change. label Jul 8, 2021
@fabianfett fabianfett added this to the HTTP/2 support milestone Jul 8, 2021
@fabianfett fabianfett requested review from Lukasa and glbrntt July 8, 2021 15:00
@fabianfett fabianfett force-pushed the ff-h2-connection branch 2 times, most recently from d5ba480 to f507cdd Compare July 8, 2021 16:27

private(set) var channelContext: ChannelHandlerContext?
private(set) var state: HTTPRequestStateMachine = .init(isChannelWritable: false)
private(set) var request: HTTPExecutingRequest?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for these not to be fully private?


try sync.addHandler(http2Handler, position: .last)
try sync.addHandler(idleHandler, position: .last)
try sync.addHandler(self.multiplexer, position: .last)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we building the HTTP/2 handlers ourselves?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need to add the IdleHandler in the middle. This trick was imported from grpc based on @glbrntt's suggestion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment to that effect please?


let maxStream = settings.first(where: { $0.parameter == .maxConcurrentStreams })?.value ?? 100

self.state = .active(openStreams: 0, maxStreams: maxStream)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A SETTINGS frame MUST be sent by both endpoints at the start of a connection and MAY be sent at any other time by either endpoint over the lifetime of the connection.

We need to handle this otherwise we'll blow away our open stream count. Moreover we can't always assume 100 as the default when no setting is present (since a mid-connection settings update may change another setting and we don't want to inadvertently overwrite the previous value of max concurrent streams).

self.state = .goAwayReceived(openStreams: openStreams, maxStreams: maxStreams)
return .notifyConnectionGoAwayReceived
case .goAwayReceived:
preconditionFailure("Invalid state")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it's valid for a server to send more than one GOAWAY frame.

@fabianfett fabianfett force-pushed the ff-h2-connection branch 3 times, most recently from 5c7899a to 5376b92 Compare July 10, 2021 12:16
func http2ConnectionClosed(_: HTTP2Connection)
}

class HTTP2Connection {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this shouldn't be final?


try sync.addHandler(http2Handler, position: .last)
try sync.addHandler(idleHandler, position: .last)
try sync.addHandler(self.multiplexer, position: .last)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment to that effect please?

self.state = .starting(readyToAcceptConnectionsPromise)
self.readyToAcceptConnectionsFuture = readyToAcceptConnectionsPromise.futureResult

// 1. Modify channel pipeline and add http2 handlers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me that we should do this on init. I tend to be a bit nervous about inits that do a lot of work. Would it be better to move this to a, say, start method?

self.multiplexer = HTTP2StreamMultiplexer(
mode: .client,
channel: channel,
targetWindowSize: 65535,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should almost certainly default this to a substantially larger number. 65535 is the protocol default and it's very conservative.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd normally suggest something closer to 8MB.

@fabianfett fabianfett requested a review from glbrntt July 19, 2021 08:38
import NIOHTTP1
import NIOHTTP2

class HTTP2ClientRequestHandler: ChannelDuplexHandler {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

if let newRequest = self.request {
if let idleReadTimeout = newRequest.idleReadTimeout {
self.idleReadTimeoutStateMachine = .init(timeAmount: idleReadTimeout)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we doing with the idleReadTimeoutStateMachine if newRequest.idleReadTimeout == nil? Should the two if-lets be a single line?

// MARK: Private HTTPRequestExecutor

private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest) {
guard self.request === request, let context = self.channelContext else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: we repeat this a handful of times, might be worth extracting it.

@fabianfett fabianfett requested a review from glbrntt July 20, 2021 16:13
@fabianfett fabianfett requested a review from Lukasa July 21, 2021 14:25
Copy link
Collaborator

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get a chance to look at the tests yet but leaving these comments for the time being.

Comment on lines 242 to 243
case .closing(var openStreams, let maxStreams):
openStreams += 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what situations can we open a stream when we're closing?


openStreams -= 1
assert(openStreams >= 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we drop the assertion? It seems relevant at least in the active case.

@fabianfett fabianfett requested a review from glbrntt July 22, 2021 14:18
private let eventLoop: EventLoop

private var state: HTTPRequestStateMachine = .init(isChannelWritable: false) {
didSet {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be willSet, or this won't fire early enough.

}

static func == (lhs: Self, rhs: Self) -> Bool {
lhs.channel === rhs.channel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the equatable/hashable implementations the same.

@fabianfett fabianfett requested a review from Lukasa July 22, 2021 15:49
@fabianfett fabianfett merged commit 388b8dc into swift-server:main Jul 22, 2021
@fabianfett fabianfett deleted the ff-h2-connection branch July 22, 2021 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants