-
Notifications
You must be signed in to change notification settings - Fork 264
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5fc9079
commit b9e5537
Showing
23 changed files
with
1,336 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-parent</artifactId> | ||
<version>2.12.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>logbook-ktor-client</artifactId> | ||
|
||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-api</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-ktor-common</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jetbrains.kotlin</groupId> | ||
<artifactId>kotlin-stdlib</artifactId> | ||
<version>1.5.21</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.ktor</groupId> | ||
<artifactId>ktor-client-core-jvm</artifactId> | ||
<version>1.6.2</version> | ||
</dependency> | ||
<!-- testing --> | ||
<dependency> | ||
<groupId>io.ktor</groupId> | ||
<artifactId>ktor-client-cio-jvm</artifactId> | ||
<version>1.6.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.ktor</groupId> | ||
<artifactId>ktor-server-cio</artifactId> | ||
<version>1.6.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-nop</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> | ||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.jetbrains.kotlin</groupId> | ||
<artifactId>kotlin-maven-plugin</artifactId> | ||
<version>1.5.21</version> | ||
<configuration> | ||
<jvmTarget>${java.version}</jvmTarget> | ||
<args> | ||
<arg>-Xopt-in=kotlin.RequiresOptIn</arg> | ||
</args> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<id>compile</id> | ||
<phase>compile</phase> | ||
<goals> | ||
<goal>compile</goal> | ||
</goals> | ||
</execution> | ||
<execution> | ||
<id>test-compile</id> | ||
<phase>test-compile</phase> | ||
<goals> | ||
<goal>test-compile</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
43 changes: 43 additions & 0 deletions
43
logbook-ktor-client/src/main/kotlin/org/zalando/logbook/client/ClientRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
@file:Suppress( | ||
"SimpleRedundantLet" // jacoco workaround | ||
) | ||
|
||
package org.zalando.logbook.client | ||
|
||
import io.ktor.client.request.* | ||
import io.ktor.http.* | ||
import io.ktor.http.HttpProtocolVersion.Companion.HTTP_1_1 | ||
import io.ktor.util.* | ||
import org.zalando.logbook.HttpHeaders | ||
import org.zalando.logbook.HttpRequest | ||
import org.zalando.logbook.Origin | ||
import org.zalando.logbook.common.State | ||
import java.nio.charset.Charset | ||
import java.util.* | ||
import java.util.concurrent.atomic.AtomicReference | ||
import kotlin.text.Charsets.UTF_8 | ||
|
||
|
||
internal class ClientRequest( | ||
private val request: HttpRequestBuilder | ||
) : HttpRequest { | ||
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered) | ||
|
||
override fun getProtocolVersion(): String = HTTP_1_1.toString() | ||
override fun getOrigin(): Origin = Origin.LOCAL | ||
override fun getHeaders(): HttpHeaders = HttpHeaders.of(request.headers.build().toMap()) | ||
override fun getContentType(): String? = request.contentType()?.let { it.toString().substringBefore(";") } | ||
override fun getCharset(): Charset = request.charset() ?: UTF_8 | ||
override fun getRemote(): String = "localhost" | ||
override fun getMethod(): String = request.method.value | ||
override fun getScheme(): String = request.url.protocol.name | ||
override fun getHost(): String = request.host | ||
override fun getPort(): Optional<Int> = Optional.of(request.port) | ||
override fun getPath(): String = request.url.encodedPath | ||
override fun getQuery(): String = request.url.buildString().substringAfter("?", "") | ||
override fun withBody(): HttpRequest = apply { state.updateAndGet { it.with() } } | ||
override fun withoutBody(): HttpRequest = apply { state.updateAndGet { it.without() } } | ||
override fun getBody(): ByteArray = state.get().body | ||
internal fun buffer(bytes: ByteArray): State = state.updateAndGet { it.buffer(bytes) } | ||
internal fun shouldBuffer(): Boolean = state.get() is State.Offering | ||
} |
35 changes: 35 additions & 0 deletions
35
logbook-ktor-client/src/main/kotlin/org/zalando/logbook/client/ClientResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
@file:Suppress( | ||
"SimpleRedundantLet" // jacoco workaround | ||
) | ||
|
||
package org.zalando.logbook.client | ||
|
||
import io.ktor.http.* | ||
import io.ktor.util.* | ||
import org.zalando.logbook.HttpHeaders | ||
import org.zalando.logbook.HttpResponse | ||
import org.zalando.logbook.Origin | ||
import org.zalando.logbook.common.State | ||
import java.nio.charset.Charset | ||
import java.util.concurrent.atomic.AtomicReference | ||
import kotlin.text.Charsets.UTF_8 | ||
import io.ktor.client.statement.HttpResponse as KtorResponse | ||
|
||
|
||
internal class ClientResponse( | ||
private val response: KtorResponse | ||
) : HttpResponse { | ||
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered) | ||
|
||
override fun getProtocolVersion(): String = response.version.toString() | ||
override fun getOrigin(): Origin = Origin.REMOTE | ||
override fun getHeaders(): HttpHeaders = HttpHeaders.of(response.headers.toMap()) | ||
override fun getContentType(): String? = response.contentType()?.let { it.toString().substringBefore(";") } | ||
override fun getCharset(): Charset = response.charset() ?: UTF_8 | ||
override fun getStatus(): Int = response.status.value | ||
override fun withBody(): HttpResponse = apply { state.updateAndGet { it.with() } } | ||
override fun withoutBody(): HttpResponse = apply { state.updateAndGet { it.without() } } | ||
override fun getBody(): ByteArray = state.get().body | ||
internal fun buffer(bytes: ByteArray) = state.updateAndGet { it.buffer(bytes) } | ||
internal fun shouldBuffer(): Boolean = state.get() is State.Offering | ||
} |
67 changes: 67 additions & 0 deletions
67
logbook-ktor-client/src/main/kotlin/org/zalando/logbook/client/LogbookClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
@file:Suppress( | ||
"BlockingMethodInNonBlockingContext" | ||
) | ||
|
||
package org.zalando.logbook.client | ||
|
||
import io.ktor.client.* | ||
import io.ktor.client.features.* | ||
import io.ktor.client.features.observer.* | ||
import io.ktor.client.request.* | ||
import io.ktor.client.statement.* | ||
import io.ktor.http.content.* | ||
import io.ktor.util.* | ||
import org.apiguardian.api.API | ||
import org.apiguardian.api.API.Status.EXPERIMENTAL | ||
import org.zalando.logbook.Logbook | ||
import org.zalando.logbook.Logbook.ResponseProcessingStage | ||
import org.zalando.logbook.common.ExperimentalLogbookKtorApi | ||
import org.zalando.logbook.common.readBytes | ||
|
||
|
||
@API(status = EXPERIMENTAL) | ||
@ExperimentalLogbookKtorApi | ||
class LogbookClient( | ||
val logbook: Logbook | ||
) { | ||
|
||
class Config { | ||
var logbook: Logbook = Logbook.create() | ||
} | ||
|
||
companion object : HttpClientFeature<Config, LogbookClient> { | ||
private val responseProcessingStageKey: AttributeKey<ResponseProcessingStage> = AttributeKey("Logbook.ResponseProcessingStage") | ||
override val key: AttributeKey<LogbookClient> = AttributeKey("LogbookFeature") | ||
override fun prepare(block: Config.() -> Unit): LogbookClient = LogbookClient(Config().apply(block).logbook) | ||
|
||
override fun install(feature: LogbookClient, scope: HttpClient) { | ||
scope.sendPipeline.intercept(HttpSendPipeline.Monitoring) { | ||
val request = ClientRequest(context) | ||
val requestWritingStage = feature.logbook.process(request) | ||
if (request.shouldBuffer()) { | ||
val content = (context.body as OutgoingContent).readBytes(scope) | ||
request.buffer(content) | ||
} | ||
val responseStage = requestWritingStage.write() | ||
context.attributes.put(responseProcessingStageKey, responseStage) | ||
proceed() | ||
} | ||
|
||
scope.receivePipeline.intercept(HttpReceivePipeline.After) { | ||
val (loggingContent, responseContent) = it.content.split(it) | ||
|
||
val responseProcessingStage = it.call.attributes[responseProcessingStageKey] | ||
val response = ClientResponse(it) | ||
val responseWritingStage = responseProcessingStage.process(response) | ||
if (response.shouldBuffer() && !loggingContent.isClosedForRead) { | ||
val content = loggingContent.readBytes() | ||
response.buffer(content) | ||
} | ||
responseWritingStage.write() | ||
|
||
val newClientCall = context.wrapWithContent(responseContent) | ||
proceedWith(newClientCall.response) | ||
} | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
logbook-ktor-client/src/test/kotlin/org/zalando/logbook/client/ClientRequestUnitTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.zalando.logbook.client | ||
|
||
import io.ktor.client.request.* | ||
import io.ktor.http.* | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import kotlin.text.Charsets.US_ASCII | ||
|
||
|
||
internal class ClientRequestUnitTest { | ||
|
||
@Test | ||
fun `ClientRequest unit test`() { | ||
val req = HttpRequestBuilder().apply { | ||
headers.append(HttpHeaders.ContentType, "application/json; charset=us-ascii") | ||
} | ||
val request = ClientRequest(req) | ||
assertThat(request.contentType).isEqualTo("application/json") | ||
assertThat(request.charset).isEqualTo(US_ASCII) | ||
} | ||
} |
Oops, something went wrong.