-
Notifications
You must be signed in to change notification settings - Fork 123
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
Add HTTP2Connection #401
Conversation
d5ba480
to
f507cdd
Compare
|
||
private(set) var channelContext: ChannelHandlerContext? | ||
private(set) var state: HTTPRequestStateMachine = .init(isChannelWritable: false) | ||
private(set) var request: HTTPExecutingRequest? |
There was a problem hiding this comment.
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
?
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Show resolved
Hide resolved
|
||
try sync.addHandler(http2Handler, position: .last) | ||
try sync.addHandler(idleHandler, position: .last) | ||
try sync.addHandler(self.multiplexer, position: .last) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift
Outdated
Show resolved
Hide resolved
|
||
let maxStream = settings.first(where: { $0.parameter == .maxConcurrentStreams })?.value ?? 100 | ||
|
||
self.state = .active(openStreams: 0, maxStreams: maxStream) |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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.
5c7899a
to
5376b92
Compare
func http2ConnectionClosed(_: HTTP2Connection) | ||
} | ||
|
||
class HTTP2Connection { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 init
s 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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
5376b92
to
deaf5b4
Compare
deaf5b4
to
0bac419
Compare
import NIOHTTP1 | ||
import NIOHTTP2 | ||
|
||
class HTTP2ClientRequestHandler: ChannelDuplexHandler { |
There was a problem hiding this comment.
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) | ||
} |
There was a problem hiding this comment.
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?
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Outdated
Show resolved
Hide resolved
// MARK: Private HTTPRequestExecutor | ||
|
||
private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest) { | ||
guard self.request === request, let context = self.channelContext else { |
There was a problem hiding this comment.
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.
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift
Outdated
Show resolved
Hide resolved
975c8a0
to
4af1b1a
Compare
There was a problem hiding this 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.
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift
Outdated
Show resolved
Hide resolved
case .closing(var openStreams, let maxStreams): | ||
openStreams += 1 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift
Outdated
Show resolved
Hide resolved
Co-authored-by: George Barnett <[email protected]>
69f95a5
to
7d6c089
Compare
private let eventLoop: EventLoop | ||
|
||
private var state: HTTPRequestStateMachine = .init(isChannelWritable: false) { | ||
didSet { |
There was a problem hiding this comment.
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.
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift
Show resolved
Hide resolved
} | ||
|
||
static func == (lhs: Self, rhs: Self) -> Bool { | ||
lhs.channel === rhs.channel |
There was a problem hiding this comment.
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.
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.