Skip to content

Commit add6a96

Browse files
committed
Use upgraded connections for interactive requests
# Conflicts: # gradle/libs.versions.toml
1 parent 34f8444 commit add6a96

File tree

6 files changed

+83
-11
lines changed

6 files changed

+83
-11
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ jobs:
6161
go run .github/check.go
6262
- name: clean build
6363
run: ./gradlew clean build --info --stacktrace
64+
env:
65+
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
66+
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
6467
- name: Upload Test Results
6568
# see publish-test-results.yml for workflow that publishes test results without security issues for forks
6669
# 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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ open class ApiClient(
7777

7878
protected inline fun <reified T> requestBody(content: T, mediaType: String = JsonMediaType): RequestBody =
7979
when {
80+
content is RequestBody -> content
8081
content is File -> content.asRequestBody(
8182
mediaType.toMediaTypeOrNull()
8283
)
@@ -319,6 +320,11 @@ open class ApiClient(
319320
response.code,
320321
response.headers.toMultimap()
321322
)
323+
response.code == 101 && request.isTcpUpgrade() && response.isTcpUpgrade() -> return SuccessStream(
324+
response.socket.consumeFrames(mediaType),
325+
response.code,
326+
response.headers.toMultimap()
327+
)
322328
response.isInformational -> return Informational(
323329
response.message,
324330
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+
// this@consumeFrames.cancel()
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: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,19 @@ 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")
43+
// listOf<String>(
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+
//// }
5056
// }
5157
// }
5258
mavenCentral()
@@ -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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
3131
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
3232
moshiKotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
3333
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
34-
okhttpMockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
34+
okhttpMockwebserverJunit5 = { module = "com.squareup.okhttp3:mockwebserver3-junit5", version.ref = "okhttp" }
3535
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
3636
okioJvm = { module = "com.squareup.okio:okio-jvm", version.ref = "okio" }
3737
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
3838

3939
[bundles]
4040
kotlin = ["kotlin", "kotlinCommon", "kotlinJdk7", "kotlinJdk8", "kotlinReflect", "kotlinScriptingJvm", "kotlinStdlib", "kotlinTest"]
4141
moshi = ["moshi", "moshiKotlin"]
42-
okhttp = ["okhttp", "okhttpMockwebserver"]
42+
okhttp = ["okhttp", "okhttpMockwebserverJunit5"]
4343
okio = ["okio", "okioJvm"]
4444

4545
[plugins]

0 commit comments

Comments
 (0)