Skip to content
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

Introduce new typed HTTPServerUpgrader and WebSocketServerUpgrader #2517

Merged
merged 3 commits into from
Oct 2, 2023

Conversation

FranzBusch
Copy link
Member

Motivation

With our new NIOAsyncChannel and typed bootstrap APIs we want to be able to let users spell out their pipeline in a typed way. Pipelines can contain handlers that have to make a forking decision such as HTTP upgrading. Our current HTTPServerUpgradeHandler is one of those handlers but it lacks strict typing. To interact nicely with our new typed APIs we need to have a new variant of the HTTPServerUpgradeHandler that can carry type information.

Modification

This PR adds a few things:

  1. A new NIOTypedHTTPServerUpgradeHandler + NIOTypedHTTPServeProtocolUpgrader. I also moved the state handling logic to a separate state machine. I thought about unifying the state machines of the old handler and the new one but they differ in behaviour which makes the state machine more complicated.
  2. A new NIOTypedWebSocketServerUpgrader that conforms to NIOTypedHTTPServerProtocolUpgrader
  3. An overhauled WebSocket server example that fully uses Concurrency.

Result

We now have a way to fully type the server side of HTTP protocol upgrading.

@FranzBusch
Copy link
Member Author

@swift-server-bot test this please

Copy link
Contributor

@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'm only part way through reviewing this but dropping my comments so far.

Could you punt the example client and server into a follow-up PR to make this one a bit smaller?

Comment on lines 411 to 415
withPipeliningAssistance pipelining: Bool = true,
withServerUpgrade upgrade: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult>,
withErrorHandling errorHandling: Bool = true,
withOutboundHeaderValidation headerValidation: Bool = true,
withEncoderConfiguration encoderConfiguration: HTTPResponseEncoder.Configuration = .init()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should avoid all the withs here as they aren't closures enforcing a lifetime

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, but the current APIs use this spelling. Do we want to diverge?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should. While there is value in consistency I think diverging from it to make APIs more idiomatic is justified.

Comment on lines 430 to 434
withPipeliningAssistance pipelining: Bool = true,
withServerUpgrade upgrade: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult>,
withErrorHandling errorHandling: Bool = true,
withOutboundHeaderValidation headerValidation: Bool = true,
withEncoderConfiguration encoderConfiguration: HTTPResponseEncoder.Configuration = .init()
Copy link
Contributor

Choose a reason for hiding this comment

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

same here re: with

Sources/NIOHTTP1/HTTPPipelineSetup.swift Outdated Show resolved Hide resolved
Sources/NIOHTTP1/NIOTypedHTTPServerUpgradeHandler.swift Outdated Show resolved Hide resolved
Sources/NIOHTTP1/NIOTypedHTTPServerUpgradeHandler.swift Outdated Show resolved Hide resolved

case finished

case modifying
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't need this state: have you validated that we do?

Copy link
Member Author

Choose a reason for hiding this comment

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

So I was believing that this isn't necessary anymore but from @glbrntt and my recent benchmarking we saw that the compiler can still miss correctly avoiding those CoWs even if everything is inlined. So I am being defensive again until we get switch inout.

case failUpgradePromise
}

@inlinable
Copy link
Contributor

Choose a reason for hiding this comment

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

We're very inconsistent with the @inlinables here, despite all the actions being @usableFromInline. Why is that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Just me forgetting it in some places. Added it to everything in the state machine now.

@FranzBusch
Copy link
Member Author

5.6 CI failure is due to primary associated type usage. Since we are dropping 5.6 support soon, I will just merge this PR after we dropped it.

Comment on lines 411 to 415
withPipeliningAssistance pipelining: Bool = true,
withServerUpgrade upgrade: NIOTypedHTTPServerUpgradeConfiguration<UpgradeResult>,
withErrorHandling errorHandling: Bool = true,
withOutboundHeaderValidation headerValidation: Bool = true,
withEncoderConfiguration encoderConfiguration: HTTPResponseEncoder.Configuration = .init()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should. While there is value in consistency I think diverging from it to make APIs more idiomatic is justified.

Comment on lines 405 to 407
isPipeliningEnabled: Bool = true,
isErrorHandlingEnabled: Bool = true,
isResponseHeaderValidationEnabled: Bool = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think is isn't right here, because it reads like a question. I think enablePipelining, enableErrorHandling, and enableResponseHeaderValidation are more appropriate

@adam-fowler
Copy link
Contributor

I've only had a quick look at this. The websocket upgrade code seems to require that every packet be tested for whether it is an http packet, or an upgraded packet. Can we not do the test on the channel once instead, ie is this channel producing http or websocket packets.

I was hoping the websocket server code could be separate from the http server code but this implementation means the two are intertwined and hard to separate into two modules.

@FranzBusch
Copy link
Member Author

I've only had a quick look at this. The websocket upgrade code seems to require that every packet be tested for whether it is an http packet, or an upgraded packet. Can we not do the test on the channel once instead, ie is this channel producing http or websocket packets.

I was hoping the websocket server code could be separate from the http server code but this implementation means the two are intertwined and hard to separate into two modules.

The two are not really intertwined. The NIOTypedHTTPServerUpgrader is only present in the pipeline until the first request comes in. Once the first request is received, we check if it was an upgrade request and if we can handle it. Once, it is either upgraded or not the NIOTypedHTTPServerUpgrader removes itself from the pipeline. Hence, we only really check if the incoming reads are HTTP parts until we have received a complete HTTP request from the peer.

@adam-fowler
Copy link
Contributor

I wasn't really talking about the NIOTypedHTTPServerUpgrader. But I misread the sample code and thought you were switching over the packet type (ie websocket packet or http packet) but you are switching over the UpgradeResult enum which holds AsyncChannels of each type. Ignore me.

# Motivation
With our new `NIOAsyncChannel` and typed bootstrap APIs we want to be able to let users spell out their pipeline in a typed way. Pipelines can contain handlers that have to make a forking decision such as HTTP upgrading. Our current `HTTPServerUpgradeHandler` is one of those handlers but it lacks strict typing. To interact nicely with our new typed APIs we need to have a new variant of the `HTTPServerUpgradeHandler` that can carry type information.

# Modification
This PR adds a few things:
1. A new `NIOTypedHTTPServerUpgradeHandler` + `NIOTypedHTTPServeProtocolUpgrader`. I also moved the state handling logic to a separate state machine. I thought about unifying the state machines of the _old_ handler and the new one but they differ in behaviour which makes the state machine more complicated.
2. A new `NIOTypedWebSocketServerUpgrader` that conforms to `NIOTypedHTTPServerProtocolUpgrader`
3. An overhauled WebSocket server example that fully uses Concurrency.

# Result
We now have a way to fully type the server side of HTTP protocol upgrading.

Code review

Update parameter names for new API and fix example
@FranzBusch
Copy link
Member Author

@Lukasa @glbrntt This PR is now ready to merge. In my last commit, I renamed the methods to configureUpgradableHTTPServerPipeline which aligns closer to what we are doing here. Furthermore, I added a new NIOUpgradableHTTPServerPipelineConfiguration which allows us to extend this configuration method in the future with new defaults without requiring new APIs.

Copy link
Contributor

@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.

Couple of nits, looks good otherwise.

Sources/NIOHTTP1/HTTPTypedPipelineSetup.swift Outdated Show resolved Hide resolved
Sources/NIOHTTP1/HTTPTypedPipelineSetup.swift Outdated Show resolved Hide resolved
Sources/NIOHTTP1/HTTPTypedPipelineSetup.swift Outdated Show resolved Hide resolved
Sources/NIOHTTP1/HTTPTypedPipelineSetup.swift Show resolved Hide resolved
@FranzBusch FranzBusch enabled auto-merge (squash) October 2, 2023 13:47
let requestDecoder = ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes))

var extraHTTPHandlers: [RemovableChannelHandler] = [requestDecoder]
extraHTTPHandlers.reserveCapacity(3)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you'll need to reserve then add the decoder

public var enableResponseHeaderValidation = true

/// The configuration for the ``HTTPResponseEncoder``.
public var httpResponseEncoderConfiguration = HTTPResponseEncoder.Configuration()
Copy link
Contributor

Choose a reason for hiding this comment

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

You missed this one, we can drop http here too

Copy link
Member Author

Choose a reason for hiding this comment

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

Will fix both in my follow up client PR. Thanks!

@FranzBusch FranzBusch merged commit de07e57 into apple:main Oct 2, 2023
6 of 8 checks passed
@FranzBusch FranzBusch deleted the fb-websocket-server-upgrade branch October 2, 2023 14:26
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Oct 2, 2023
# Motivation
In my previous PR apple#2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Oct 2, 2023
# Motivation
In my previous PR apple#2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Oct 3, 2023
# Motivation
In my previous PR apple#2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Oct 4, 2023
# Motivation
In my previous PR apple#2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Oct 5, 2023
# Motivation
In my previous PR apple#2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.
Lukasa pushed a commit that referenced this pull request Oct 6, 2023
#2526)

* Introduce new typed `HTTPClientUpgrader` and `WebSocketClientUpgrader`

# Motivation
In my previous PR #2517, I added a new typed `HTTPServerUpgrader` and corresponding implementation for the `WebSocketServerUpgrader`. The goal of those is to carry type information across HTTP upgrades which allows us to build fully typed pipelines.

# Modification
This PR adds a few things:
1. A new `NIOTypedHttpClientUpgradeHandler` + `NIOTypedHttpClientProtocolUpgrader`. I also moved the state handling to a separate state machine. Similar to the server PR I did not unify the state machine between the newly typed and untyped upgrade handlers since they differ in logic.
2. A new `NIOTypedWebSocketClientUpgrader`
3. An overhauled WebSocket client example.

# Result
This is the last missing piece of dynamic pipeline changing where we did not carry around the type information. After this PR lands, we can finalize the `AsyncChannel` and async typed NIO pieces.

* Remove availability on the protocols
@FranzBusch FranzBusch added the semver/minor Adds new public API. label Oct 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants