Skip to content
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

feat: additional callback methods for Interceptor interface #5025

Merged
merged 1 commit into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package io.fabric8.kubernetes.client.http;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public interface Interceptor {

public interface RequestTags {
interface RequestTags {

<T> T getTag(Class<T> type);

Expand All @@ -35,6 +37,31 @@ public interface RequestTags {
default void before(BasicBuilder builder, HttpRequest request, RequestTags tags) {
}

/**
* Called after a non-WebSocket HTTP response is received. The body might or might not be already consumed.
* <p>
* Should be used to analyze response codes and headers, original response shouldn't be altered.
*
* @param response the response received from the server.
*/
default void after(HttpResponse<?> response) {

}

// In case we want to encapsulate to spy the responses from the server

/**
* Called before a request to allow the encapsulation of the provided consumer.
* <p>
*
* @param consumer the original consumer.
* @param request the HTTP request.
* @return the consumer to use.
*/
default AsyncBody.Consumer<List<ByteBuffer>> consumer(AsyncBody.Consumer<List<ByteBuffer>> consumer, HttpRequest request) {
return consumer;
}

/**
* Called after a websocket failure or by default from a normal request
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,22 @@ private CompletableFuture<HttpResponse<AsyncBody>> consumeBytesOnce(HttpRequest
for (Interceptor interceptor : builder.getInterceptors().values()) {
interceptor.before(copy, standardHttpRequest, this);
standardHttpRequest = copy.build();
consumer = interceptor.consumer(consumer, standardHttpRequest);
}
final Consumer<List<ByteBuffer>> effectiveConsumer = consumer;

CompletableFuture<HttpResponse<AsyncBody>> cf = consumeBytesDirect(standardHttpRequest, consumer);
CompletableFuture<HttpResponse<AsyncBody>> cf = consumeBytesDirect(standardHttpRequest, effectiveConsumer);

for (Interceptor interceptor : builder.getInterceptors().values()) {
cf = cf.thenCompose(response -> {
interceptor.after(response);
if (!HttpResponse.isSuccessful(response.code())) {
return interceptor.afterFailure(copy, response, this)
.thenCompose(b -> {
if (Boolean.TRUE.equals(b)) {
// before starting another request, make sure the old one is cancelled / closed
response.body().cancel();
return consumeBytesDirect(copy.build(), consumer);
return consumeBytesDirect(copy.build(), effectiveConsumer);
}
return CompletableFuture.completedFuture(response);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -261,6 +263,89 @@ public CompletableFuture<Boolean> afterFailure(BasicBuilder builder, HttpRespons
assertThat(server.getLastRequest().getPath()).isEqualTo("/valid-url");
}

@Test
@DisplayName("after, called after HTTP successful response")
public void afterHttpSuccess() throws Exception {
// Given
server.expect().withPath("/success").andReturn(200, "This works").once();
final CompletableFuture<HttpResponse<?>> responseFuture = new CompletableFuture<>();
final HttpClient.Builder builder = getHttpClientFactory().newBuilder()
.addOrReplaceInterceptor("after", new Interceptor() {
@Override
public void after(HttpResponse<?> response) {
responseFuture.complete(response);
}
});
// When
try (HttpClient client = builder.build()) {
client.consumeBytes(
client.newHttpRequestBuilder().uri(server.url("/success")).build(),
(s, ab) -> ab.consume())
.get(10, TimeUnit.SECONDS);
}
// Then
assertThat(responseFuture)
.succeedsWithin(1, TimeUnit.SECONDS)
.extracting(HttpResponse::code)
.isEqualTo(200);
}

@Test
@DisplayName("after, called after HTTP error response")
public void afterHttpError() throws Exception {
// Given
server.expect().withPath("/client-error").andReturn(400, "Client problems").once();
final CompletableFuture<HttpResponse<?>> responseFuture = new CompletableFuture<>();
final HttpClient.Builder builder = getHttpClientFactory().newBuilder()
.addOrReplaceInterceptor("after", new Interceptor() {
@Override
public void after(HttpResponse<?> response) {
responseFuture.complete(response);
}
});
// When
try (HttpClient client = builder.build()) {
client.consumeBytes(
client.newHttpRequestBuilder().uri(server.url("/client-error")).build(),
(s, ab) -> ab.consume())
.get(10, TimeUnit.SECONDS);
}
// Then
assertThat(responseFuture)
.succeedsWithin(1, TimeUnit.SECONDS)
.extracting(HttpResponse::code)
.isEqualTo(400);
}

@Test
@DisplayName("consumer, can encapsulate the original consumer to log response body")
public void consumerForLogging() throws Exception {
// Given
server.expect().withPath("/look-at-my-body").andReturn(200, "I\nHave\r\nNice Body\r").once();
final StringBuilder sb = new StringBuilder();
final HttpClient.Builder builder = getHttpClientFactory().newBuilder()
.addOrReplaceInterceptor("consumer", new Interceptor() {
@Override
public AsyncBody.Consumer<List<ByteBuffer>> consumer(AsyncBody.Consumer<List<ByteBuffer>> consumer,
HttpRequest request) {
return (value, asyncBody) -> {
value.stream().map(BufferUtil::copy).forEach(bb -> sb.append(StandardCharsets.UTF_8.decode(bb)));
consumer.consume(value, asyncBody);
};
}
});
String receivedResponse;
// When
try (HttpClient client = builder.build()) {
receivedResponse = client.sendAsync(
client.newHttpRequestBuilder().uri(server.url("/look-at-my-body")).build(),
String.class)
.get(10, TimeUnit.SECONDS).body();
}
// Then
assertThat(sb).hasToString("I\nHave\r\nNice Body\r").hasToString(receivedResponse);
}

@Test
@DisplayName("interceptors (HTTP) should be applied in the order they were added")
public void interceptorsHttpAreAppliedInOrder() throws Exception {
Expand Down