Skip to content

Commit 704c813

Browse files
committed
Use upgraded connections for interactive requests
1 parent 169f38f commit 704c813

File tree

6 files changed

+88
-14
lines changed

6 files changed

+88
-14
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ jobs:
6262
go run .github/check.go
6363
- name: clean build
6464
run: ./gradlew clean build --info --stacktrace
65+
env:
66+
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
67+
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
6568
- name: Upload Test Results
6669
# see publish-test-results.yml for workflow that publishes test results without security issues for forks
6770
# https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches

api-client/src/main/kotlin/de/gesellix/docker/remote/api/client/ContainerApi.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ class ContainerApi(dockerClientConfig: DockerClientConfig = defaultClientConfig,
214214
)
215215

216216
when (localVarResponse.responseType) {
217-
ResponseType.Success -> {
217+
ResponseType.Success,
218+
ResponseType.Informational -> {
218219
runBlocking {
219220
launch {
220221
withTimeout(timeoutMillis) {
@@ -225,7 +226,6 @@ class ContainerApi(dockerClientConfig: DockerClientConfig = defaultClientConfig,
225226
}
226227
}
227228
}
228-
ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.")
229229
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
230230
ResponseType.ClientError -> {
231231
val localVarError = localVarResponse as ClientError<*>
@@ -282,6 +282,12 @@ class ContainerApi(dockerClientConfig: DockerClientConfig = defaultClientConfig,
282282
}
283283
}
284284
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
285+
val requiresConnectionUpgrade = stdin != null
286+
if (requiresConnectionUpgrade)
287+
localVariableHeaders.apply {
288+
put("Upgrade", "tcp")
289+
put("Connection", "Upgrade")
290+
}
285291

286292
return RequestConfig(
287293
method = POST,

api-client/src/main/kotlin/de/gesellix/docker/remote/api/core/ApiClient.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ open class ApiClient(
319319
response.code,
320320
response.headers.toMultimap()
321321
)
322+
response.code == 101 && request.isTcpUpgrade() && response.isTcpUpgrade() -> return SuccessStream(
323+
response.socket.consumeFrames(mediaType),
324+
response.code,
325+
response.headers.toMultimap()
326+
)
322327
response.isInformational -> return Informational(
323328
response.message,
324329
response.code,

api-client/src/main/kotlin/de/gesellix/docker/remote/api/core/ResponseConsumer.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,34 @@ import de.gesellix.docker.response.JsonChunksReader
66
import kotlinx.coroutines.flow.Flow
77
import kotlinx.coroutines.flow.emptyFlow
88
import kotlinx.coroutines.flow.flow
9+
import okhttp3.Request
10+
import okhttp3.Response
911
import okhttp3.ResponseBody
1012
import okio.Closeable
13+
import okio.Socket
1114
import okio.appendingSink
1215
import okio.buffer
1316
import java.io.File
1417
import java.io.InputStream
1518
import java.lang.reflect.Type
1619
import java.nio.file.Files
1720

21+
fun Request?.isTcpUpgrade(): Boolean {
22+
if (this == null) {
23+
return false
24+
}
25+
return this.header("Connection")?.contains("Upgrade", ignoreCase = true) ?: false &&
26+
this.header("Upgrade")?.contains("tcp", ignoreCase = true) ?: false
27+
}
28+
29+
fun Response?.isTcpUpgrade(): Boolean {
30+
if (this == null) {
31+
return false
32+
}
33+
return this.header("Connection")?.contains("Upgrade", ignoreCase = true) ?: false &&
34+
this.header("Upgrade")?.contains("tcp", ignoreCase = true) ?: false
35+
}
36+
1837
fun ResponseBody?.consumeFile(): File? {
1938
if (this == null) {
2039
return null
@@ -57,6 +76,34 @@ inline fun <reified T : Any?> ResponseBody?.consumeStream(mediaType: String?): F
5776
}
5877
}
5978

79+
fun Socket?.consumeFrames(mediaType: String?): Flow<Frame> {
80+
if (this == null) {
81+
return emptyFlow()
82+
}
83+
when (mediaType) {
84+
// Requires api v1.42
85+
// multiplexed-stream: without attached Tty
86+
ApiClient.Companion.DockerMultiplexedStreamMediaType,
87+
// Requires api v1.42
88+
// raw-stream: with attached Tty
89+
ApiClient.Companion.DockerRawStreamMediaType -> {
90+
val reader = FrameReader(source, mediaType)
91+
val events = flow {
92+
while (reader.hasNext()) {
93+
val next = reader.readNext(Frame::class.java)
94+
emit(next)
95+
}
96+
source.closeQuietly()
97+
98+
}
99+
return events
100+
}
101+
else -> {
102+
throw UnsupportedOperationException("Can't handle media type $mediaType")
103+
}
104+
}
105+
}
106+
60107
fun ResponseBody?.consumeFrames(mediaType: String?): Flow<Frame> {
61108
if (this == null) {
62109
return emptyFlow()

build.gradle.kts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,21 @@ val dependencyVersionsByGroup = mapOf(
4040
subprojects {
4141
repositories {
4242
// mavenLocal()
43-
// fun findProperty(s: String) = project.findProperty(s) as String?
44-
// maven {
45-
// name = "github"
46-
// setUrl("https://maven.pkg.github.com/docker-client/*")
47-
// credentials {
48-
// username = System.getenv("PACKAGE_REGISTRY_USER") ?: findProperty("github.package-registry.username")
49-
// password = System.getenv("PACKAGE_REGISTRY_TOKEN") ?: findProperty("github.package-registry.password")
50-
// }
51-
// }
43+
listOf(
44+
"gesellix/okhttp",
45+
// "docker-client/*",
46+
).forEach { slug ->
47+
// fun findProperty(s: String) = project.findProperty(s) as String?
48+
maven {
49+
name = "githubPackages"
50+
url = uri("https://maven.pkg.github.com/${slug}")
51+
credentials(PasswordCredentials::class)
52+
// credentials {
53+
// username = System.getenv("PACKAGE_REGISTRY_USER") ?: findProperty("github.package-registry.username")
54+
// password = System.getenv("PACKAGE_REGISTRY_TOKEN") ?: findProperty("github.package-registry.password")
55+
// }
56+
}
57+
}
5258
mavenCentral()
5359
}
5460
}
@@ -64,6 +70,10 @@ allprojects {
6470
useVersion(forcedVersion)
6571
}
6672
}
73+
dependencySubstitution {
74+
substitute(module("com.squareup.okhttp3:okhttp-jvm"))
75+
.using(module("de.gesellix.okhttp3-forked:okhttp-jvm:${libs.versions.okhttp.get()}"))
76+
}
6777
}
6878
}
6979
}

gradle/libs.versions.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ logback = "1.3.15"
77
logbackVersionrange = "[1.2,2)"
88
moshi = "1.15.2"
99
moshiVersionrange = "[1.12.0,2)"
10-
okhttp = "5.1.0"
10+
okhttp = "5.2.0-2025-08-02T07-52-48"
11+
#okhttp = "5.1.0"
1112
okhttpVersionrange = "[4,6)"
1213
okio = "3.16.0"
1314
okioVersionrange = "[3,4)"
@@ -30,8 +31,10 @@ kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotli
3031
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
3132
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
3233
moshiKotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
33-
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
34-
okhttpMockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
34+
okhttp = { module = "de.gesellix.okhttp3-forked:okhttp", version.ref = "okhttp" }
35+
#okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
36+
okhttpMockwebserver = { module = "de.gesellix.okhttp3-forked:mockwebserver3-junit4", version.ref = "okhttp" }
37+
#okhttpMockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
3538
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
3639
okioJvm = { module = "com.squareup.okio:okio-jvm", version.ref = "okio" }
3740
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }

0 commit comments

Comments
 (0)