-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Add support for HTTP/1.1 protocol upgrades #8849
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
base: master
Are you sure you want to change the base?
Conversation
// https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-client | ||
// docker run --rm -it --name attach alpine:edge top -b | ||
@Test | ||
fun upgradeConnection() { |
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'm not happy with this one, maybe you have better ideas how to test the connection upgrade?
This is now rebased on the current master to use the new okio.Socket. |
if (response.socketHandler != null) { | ||
response.socketHandler.handle( | ||
object : okio.Socket { | ||
override val source: BufferedSource |
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'm not clear who can access this as a BufferedSource for an anonymous object impl. Wouldn't clients still have to Buffer again to be sure?
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 guess I don't understand your question. Are you implying that this should be changed to a non-buffered Source?
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.
Yeah, are you relying on this being buffered here? Or also when used it's applied
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 don't rely on this being buffered, so I commited a cleanup 👍
@@ -82,7 +82,7 @@ class MockSocketHandler : SocketHandler { | |||
@JvmOverloads | |||
fun sendResponse( | |||
s: String, | |||
responseSent: CountDownLatch = CountDownLatch(0), | |||
responseSent: CountDownLatch = CountDownLatch(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.
Is this fixing an unrelated bug?
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.
According to the Javadoc, a CountDownLatch with a zero value doesn't have any effect. I didn't experience a bug.
This is unrelated to the current pull request, though. I referred to this one as one of the two commits which are both unrelated and can easily be moved to a dedicated pull request.
get() = socket.sink().buffer() | ||
|
||
override fun cancel() { | ||
socket.sink().flush() |
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.
Seems to contradict the API
Fail any in-flight and future operations.
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.
You're right, I didn't check the api thoroughly. I'm going to remove this one.
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 has been removed in RealConnection as well as in the MockWebServer.
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.
... is it correct and sufficient to cancel the Exchange?
/** | ||
* Non-null if this response is a successful upgrade ... | ||
*/ | ||
@get:JvmName("socket") val socket: Socket?, |
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'm a bit unclear why this is enough to consider upgrades handled. Is this only for TCP? or do you think that all protocols would be handled as a Socket?
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 must admit that I only considered TCP upgrades and ignored other options. I suppose that the socket
will only be necessary (and non-null) for upgrades which are not supported through another implementation (e.g. HTTP2). Returning the plain Socket might be considered as a generic way of supporting any protocol. That said: I'm unsure if this would make sense for OkHttp in general or if you prefer to keep supported protocols explicitly stated anywhere.
So, would it be enough to improve the Javadoc to be more specific?
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 think Jesse should weigh in. I don't think this is a simple feature to add especially given your TCP case is a poorly specced docker variant of a more general feature.
if (code == HTTP_SWITCHING_PROTOCOLS && | ||
"upgrade".equals(response.request.header("Connection"), ignoreCase = true) && | ||
"upgrade".equals(response.header("Connection"), ignoreCase = true) && | ||
"tcp".equals(response.request.header("Upgrade"), ignoreCase = true) && |
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.
The PR title feels like it's about all possible protocols, but in practice seems tied to solving TCP specifically for docker?
Is there a spec for this TCP upgrade?
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.
You're right, I wasn't aware of the more general possibilities for a connection upgrade, and the case for Websockets hasn't changed with my pull request.
My current aim is in fact to add support for TCP upgrades, because my use case is driven by the Docker engine api.
There's no spec for the TCP upgrade per se, at least not known to me.
Given the issue at #5874 I would suggest to start with support for tcp
and websocket
. Other protocols might be added in the future.
I'm struggling to find any implementations of the TCP upgrade in popular clients like Curl, Netty, Requests etc. Can you link to any? And wondering whether this really needs a more thorough implementation of the spec, which allows for negotation. Example from Netty https://github.com/netty/netty/blob/42d83b8081ab3d018f5d7611973e5d361cdd28d1/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientUpgradeHandler.java#L40 |
There is a use case with the Apache Http Client, so not exactly an example in a client: https://github.com/docker-java/docker-java/blob/20f08311d1613d8819c4548f34046b0557a43117/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/HijackingHttpRequestExecutor.java Looking at https://datatracker.ietf.org/doc/html/rfc9110#field.upgrade there's at least the As far as I understand the Netty implementation, it handles the connection upgrade more or less by "passing through" and only checking response headers for the requested protocol. I didn't dig deeper into how exactly it would allow the client to handle the upgraded connection. |
Thanks, that's useful. Yeah there are maybe three paths
|
Given the release of OkHttp 5, and this one probably changing the api, is there still a chance to have some kind of minimal implementation for the use case of OkHttp as client for a Docker Engine and the tcp upgrade? I guess there's no other chance of using OkHttp without changing its api? I'm not afraid of maintaining some kind of extension or module so that you wouldn't have to officially support such a feature, but I have no idea how I could implement such a module without maintaining a full OkHttp fork by myself. |
@gesellix I think it's worth supporting, but I'd like some other opinions on it also. I guess what is complicated here is the PR is a partial implementation that covers a non-standardised but valid case. While the standardized cases aren't supported. |
So, how much of a standard use case (which ones?) would be required to put some weight or clarity into it? Maybe this is not the right timing to push such a feature and I assume that you don't have other users asking for anything with a connection upgrade (on top of existing implementations for Websocket and HTTP/2). Do you have an indication which standard use cases make sense? I could try to prepare the required changes either with this pull request or in a separate one. I might only need some guidance on the interfaces and testing if that would work for you. |
If we have a clean mechanism, and this is currently the only supported protocol, but we can add more. That is probably ok. But if the public API of things like Response change in a way that has a bunch of assumptions that only hold for this TCP docker case, it would be a mistake. @swankjesse if you have opinions, feel free to weigh in. |
I’d like to ship this. I’d like to review this PR and the RFCs to figure out exactly what’s best for our next step and I intend to do so. |
Based on the discussion in #5874
See RFC 7230 section 6.7 and RFC 9110 section 15.2.2.
The first two commits are not related and can be submitted as separate pull request.
This is only a draft to continue the discussion started in #5874 and not ready for an actual merge, yet.