Add support for HTTP/1.1 protocol upgrades#8849
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.
I'm not happy with this one, maybe you have better ideas how to test the connection upgrade?
There was a problem hiding this comment.
I wonder if you can test any of this with container-tests?
There was a problem hiding this comment.
Thanks, I wasn't aware of those. I'll look into it!
|
This is now rebased on the current master to use the new okio.Socket. |
| /** | ||
| * Non-null if this response is a successful upgrade ... | ||
| */ | ||
| @get:JvmName("socket") val socket: Socket?, |
There was a problem hiding this comment.
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.
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.
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.
There was a problem hiding this comment.
My understanding is that on a protocol upgrade, OkHttp should hand off the okio.Socket to the application layer, and it can do whatever new protocol it wants on top. Our responsibility for the upgrade ends and handing off the socket.
There was a problem hiding this comment.
Websockets might be the only exception?
|
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. |
|
Thanks for looking into it. |
|
The branch has been rebased now (I had to make it twice to make spotless happy). |
|
I think running |
| /** | ||
| * Non-null if this response is a successful upgrade ... | ||
| */ | ||
| @get:JvmName("socket") val socket: Socket?, |
There was a problem hiding this comment.
My understanding is that on a protocol upgrade, OkHttp should hand off the okio.Socket to the application layer, and it can do whatever new protocol it wants on top. Our responsibility for the upgrade ends and handing off the socket.
|
|
||
| val responseCode = code | ||
| if ((responseCode < HTTP_CONTINUE || responseCode >= 200) && | ||
| if ((responseCode < HTTP_CONTINUE || responseCode == HTTP_SWITCHING_PROTOCOLS || responseCode >= 200) && |
There was a problem hiding this comment.
I don’t think I agree that a protocol change is a body? This is used to drive stuff like the gzip interceptor, which seems inappropriate here?
There was a problem hiding this comment.
The protocol change itself doesn't promise a body, but would depend on the actual protocl after a successful upgrade.
I think this change was some kind of workaround to run into the "newUnknownLengthSource(response.request.url)" case at https://github.com/gesellix/okhttp/blob/0984bc3fc5d41c4814b953194d10e5ee77361f7d/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http1/Http1ExchangeCodec.kt#L145
Maybe there's a better way?
|
The pull request will be continued at #8969 |
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.