diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2023-01-04 09:21:28 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-04 09:21:28 +0100 |
commit | 7db83f1e63c06b50fd2dadf271deda9b489fe14d (patch) | |
tree | b7b9905c0656b66112153b6e220986736b6cc6e2 | |
parent | a47adad9f0fbb6d7e1dc24f600d4ad0e78cf485b (diff) | |
parent | eb0bf8c5bb18ad89ba03d290c6cedc6773b321bc (diff) |
Merge pull request #25381 from vespa-engine/jonmv/feed-client-gzip
Add --gzip-requests to compress request bodies in feed client
12 files changed, 232 insertions, 25 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java index a93741fd8fb..e23f0205e5a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java @@ -79,8 +79,7 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase { @Override protected Optional<ErrorResponse> filter(DiscFilterRequest request) { try { - Principal principal = request.getUserPrincipal(); - if (principal instanceof AthenzPrincipal) { + if (request.getUserPrincipal() instanceof AthenzPrincipal principal) { Optional<DecodedJWT> oktaAt = Optional.ofNullable((String) request.getAttribute("okta.access-token")).map(JWT::decode); Optional<X509Certificate> cert = request.getClientCertificateChain().stream().findFirst(); Instant issuedAt = cert.map(X509Certificate::getNotBefore) @@ -89,9 +88,8 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase { Instant expireAt = cert.map(X509Certificate::getNotAfter) .or(() -> oktaAt.map(Payload::getExpiresAt)) .map(Date::toInstant).orElse(Instant.MAX); - request.setAttribute(SecurityContext.ATTRIBUTE_NAME, new SecurityContext(principal, - roles((AthenzPrincipal) principal, request.getUri()), - issuedAt, expireAt)); + request.setAttribute(SecurityContext.ATTRIBUTE_NAME, + new SecurityContext(principal, roles(principal, request.getUri()), issuedAt, expireAt)); } } catch (Exception e) { diff --git a/vespa-feed-client-api/abi-spec.json b/vespa-feed-client-api/abi-spec.json index 137c7f32bfe..64b049dc75d 100644 --- a/vespa-feed-client-api/abi-spec.json +++ b/vespa-feed-client-api/abi-spec.json @@ -112,6 +112,23 @@ ], "fields" : [ ] }, + "ai.vespa.feed.client.FeedClientBuilder$Compression" : { + "superClass" : "java.lang.Enum", + "interfaces" : [ ], + "attributes" : [ + "public", + "final", + "enum" + ], + "methods" : [ + "public static ai.vespa.feed.client.FeedClientBuilder$Compression[] values()", + "public static ai.vespa.feed.client.FeedClientBuilder$Compression valueOf(java.lang.String)" + ], + "fields" : [ + "public static final enum ai.vespa.feed.client.FeedClientBuilder$Compression none", + "public static final enum ai.vespa.feed.client.FeedClientBuilder$Compression gzip" + ] + }, "ai.vespa.feed.client.FeedClientBuilder" : { "superClass" : "java.lang.Object", "interfaces" : [ ], @@ -142,6 +159,7 @@ "public abstract ai.vespa.feed.client.FeedClientBuilder setCaCertificates(java.util.Collection)", "public abstract ai.vespa.feed.client.FeedClientBuilder setEndpointUris(java.util.List)", "public abstract ai.vespa.feed.client.FeedClientBuilder setProxy(java.net.URI)", + "public abstract ai.vespa.feed.client.FeedClientBuilder setCompression(ai.vespa.feed.client.FeedClientBuilder$Compression)", "public abstract ai.vespa.feed.client.FeedClient build()" ], "fields" : [ diff --git a/vespa-feed-client-api/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java b/vespa-feed-client-api/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java index e8cb5344aff..d48c3c31348 100644 --- a/vespa-feed-client-api/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java +++ b/vespa-feed-client-api/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java @@ -123,6 +123,11 @@ public interface FeedClientBuilder { /** Specify HTTP(S) proxy for all endpoints */ FeedClientBuilder setProxy(URI uri); + /** What compression to use for request bodies; default {@code NONE}. */ + FeedClientBuilder setCompression(Compression compression); + + enum Compression { none, gzip } + /** Constructs instance of {@link FeedClient} from builder configuration */ FeedClient build(); diff --git a/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliArguments.java b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliArguments.java index fd36749b109..42f9713c54e 100644 --- a/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliArguments.java +++ b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliArguments.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.feed.client.impl; +import ai.vespa.feed.client.FeedClientBuilder.Compression; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -24,6 +25,8 @@ import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; +import static ai.vespa.feed.client.FeedClientBuilder.Compression.none; + /** * Parses command line arguments * @@ -58,6 +61,7 @@ class CliArguments { private static final String STDIN_OPTION = "stdin"; private static final String DOOM_OPTION = "max-failure-seconds"; private static final String PROXY_OPTION = "proxy"; + private static final String COMPRESSION = "compression"; private final CommandLine arguments; @@ -181,6 +185,15 @@ class CliArguments { boolean speedTest() { return has(SPEED_TEST_OPTION); } + Compression compression() throws CliArgumentsException { + try { + return stringValue(COMPRESSION).map(Compression::valueOf).orElse(none); + } + catch (IllegalArgumentException e) { + throw new CliArgumentsException("Invalid " + COMPRESSION + " argument: " + e.getMessage(), e); + } + } + OptionalInt testPayloadSize() throws CliArgumentsException { return intValue(TEST_PAYLOAD_SIZE_OPTION); } Optional<URI> proxy() throws CliArgumentsException { @@ -354,6 +367,12 @@ class CliArguments { .desc("URI to proxy endpoint") .hasArg() .type(URL.class) + .build()) + .addOption(Option.builder() + .longOpt(COMPRESSION) + .desc("Compression mode for feed requests: 'none' (default), 'gzip'") + .hasArg() + .type(Compression.class) .build()); } diff --git a/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliClient.java b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliClient.java index 43a16c2abf0..39462d8ba68 100644 --- a/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliClient.java +++ b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/impl/CliClient.java @@ -1,7 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.feed.client.impl; -import ai.vespa.feed.client.DocumentId; import ai.vespa.feed.client.FeedClient; import ai.vespa.feed.client.FeedClientBuilder; import ai.vespa.feed.client.FeedException; @@ -10,7 +9,6 @@ import ai.vespa.feed.client.JsonFeeder.ResultCallback; import ai.vespa.feed.client.OperationStats; import ai.vespa.feed.client.Result; import ai.vespa.feed.client.ResultException; -import ai.vespa.feed.client.impl.CliArguments.CliArgumentsException; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; @@ -29,14 +27,12 @@ import java.time.Instant; import java.util.Enumeration; import java.util.Map; import java.util.Random; -import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; -import java.util.stream.Collectors; import java.util.stream.IntStream; import static java.util.stream.Collectors.joining; @@ -163,6 +159,7 @@ public class CliClient { cliArgs.headers().forEach(builder::addRequestHeader); builder.setDryrun(cliArgs.dryrunEnabled()); builder.setSpeedTest(cliArgs.speedTest()); + builder.setCompression(cliArgs.compression()); cliArgs.doomSeconds().ifPresent(doom -> builder.setCircuitBreaker(new GracePeriodCircuitBreaker(Duration.ofSeconds(10), Duration.ofSeconds(doom)))); cliArgs.proxy().ifPresent(builder::setProxy); diff --git a/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/impl/CliArgumentsTest.java b/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/impl/CliArgumentsTest.java index 073ea4a58db..21e279b0584 100644 --- a/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/impl/CliArgumentsTest.java +++ b/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/impl/CliArgumentsTest.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.feed.client.impl; +import ai.vespa.feed.client.FeedClientBuilder.Compression; import ai.vespa.feed.client.impl.CliArguments.CliArgumentsException; import org.junit.jupiter.api.Test; @@ -24,12 +25,27 @@ class CliArgumentsTest { @Test void parses_parameters_correctly() throws CliArguments.CliArgumentsException { CliArguments args = CliArguments.fromRawArgs(new String[]{ - "--endpoint=https://vespa.ai:4443/", "--file=feed.json", "--connections=10", - "--max-streams-per-connection=128", "--certificate=cert.pem", "--private-key=key.pem", - "--ca-certificates=ca-certs.pem", "--disable-ssl-hostname-verification", - "--header=\"My-Header: my-value\"", "--header", "Another-Header: another-value", "--benchmark", - "--route=myroute", "--timeout=0.125", "--trace=9", "--verbose", "--silent", - "--show-errors", "--show-all", "--max-failure-seconds=30", "--proxy", "https://myproxy:1234"}); + "--endpoint", "https://vespa.ai:4443/", + "--file", "feed.json", + "--connections", "10", + "--max-streams-per-connection", "128", + "--certificate", "cert.pem", + "--private-key", "key.pem", + "--ca-certificates", "ca-certs.pem", + "--disable-ssl-hostname-verification", + "--header", "\"My-Header: my-value\"", + "--header", "Another-Header: another-value", + "--benchmark", + "--route", "myroute", + "--timeout", "0.125", + "--trace", "9", + "--verbose", + "--silent", + "--compression", "gzip", + "--show-errors", + "--show-all", + "--max-failure-seconds", "30", + "--proxy", "https://myproxy:1234"}); assertEquals(URI.create("https://vespa.ai:4443/"), args.endpoint()); assertEquals(Paths.get("feed.json"), args.inputFile().get()); assertEquals(10, args.connections().getAsInt()); @@ -52,14 +68,15 @@ class CliArgumentsTest { assertTrue(args.showErrors()); assertTrue(args.showSuccesses()); assertFalse(args.showProgress()); + assertEquals(Compression.gzip, args.compression()); assertEquals(URI.create("https://myproxy:1234"), args.proxy().orElse(null)); } @Test void fails_on_missing_parameters() { - CliArguments.CliArgumentsException exception = assertThrows( + CliArguments.CliArgumentsException exception = assertThrows( CliArguments.CliArgumentsException.class, - () -> CliArguments.fromRawArgs(new String[] {"--file=/path/to/file", "--stdin"})); + () -> CliArguments.fromRawArgs(new String[] {"--file", "/path/to/file", "--stdin"})); assertEquals("Endpoint must be specified", exception.getMessage()); } @@ -67,20 +84,20 @@ class CliArgumentsTest { void fails_on_conflicting_parameters() throws CliArgumentsException { assertEquals("Exactly one of 'file' and 'stdin' must be specified", assertThrows(CliArgumentsException.class, - () -> CliArguments.fromRawArgs(new String[] {"--endpoint=https://endpoint", "--file=/path/to/file", "--stdin"})) + () -> CliArguments.fromRawArgs(new String[] {"--endpoint", "https://endpoint", "--file", "/path/to/file", "--stdin"})) .getMessage()); assertEquals("Exactly one of 'file' and 'stdin' must be specified", assertThrows(CliArgumentsException.class, - () -> CliArguments.fromRawArgs(new String[] {"--endpoint=https://endpoint"})) + () -> CliArguments.fromRawArgs(new String[] {"--endpoint", "https://endpoint"})) .getMessage()); assertEquals("At most one of 'file', 'stdin' and 'test-payload-size' may be specified", assertThrows(CliArgumentsException.class, - () -> CliArguments.fromRawArgs(new String[] {"--endpoint=https://endpoint", "--speed-test", "--test-payload-size=123", "--file=file"})) + () -> CliArguments.fromRawArgs(new String[] {"--endpoint", "https://endpoint", "--speed-test", "--test-payload-size", "123", "--file", "file"})) .getMessage()); - CliArguments.fromRawArgs(new String[] {"--endpoint=foo", "--speed-test"}); + CliArguments.fromRawArgs(new String[] {"--endpoint", "foo", "--speed-test"}); } @Test diff --git a/vespa-feed-client-cli/src/test/resources/help.txt b/vespa-feed-client-cli/src/test/resources/help.txt index e41a78bc932..f33dde82f7b 100644 --- a/vespa-feed-client-cli/src/test/resources/help.txt +++ b/vespa-feed-client-cli/src/test/resources/help.txt @@ -6,6 +6,9 @@ Vespa feed client certificates encoded as PEM --certificate <arg> Path to PEM encoded X.509 certificate file + --compression <arg> Compression mode for feed + requests: 'none' (default), + 'gzip' --connections <arg> Number of concurrent HTTP/2 connections --disable-ssl-hostname-verification Disable SSL hostname diff --git a/vespa-feed-client/pom.xml b/vespa-feed-client/pom.xml index 1cc2f2adee1..01b9b00b8a0 100644 --- a/vespa-feed-client/pom.xml +++ b/vespa-feed-client/pom.xml @@ -46,6 +46,11 @@ <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.github.tomakehurst</groupId> + <artifactId>wiremock-jre8-standalone</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java index 3ffbaf136f2..1dda8912046 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.feed.client.impl; +import ai.vespa.feed.client.FeedClientBuilder.Compression; import ai.vespa.feed.client.HttpResponse; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; @@ -10,6 +11,8 @@ import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http2.config.H2Config; @@ -18,6 +21,7 @@ import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; import javax.net.ssl.SSLContext; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -29,8 +33,8 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hc.core5.http.ssl.TlsCiphers.excludeH2Blacklisted; import static org.apache.hc.core5.http.ssl.TlsCiphers.excludeWeak; @@ -40,9 +44,11 @@ import static org.apache.hc.core5.http.ssl.TlsCiphers.excludeWeak; class ApacheCluster implements Cluster { private final List<Endpoint> endpoints = new ArrayList<>(); - private final List<BasicHeader> defaultHeaders = Arrays.asList(new BasicHeader("User-Agent", String.format("vespa-feed-client/%s", Vespa.VERSION)), + private final List<BasicHeader> defaultHeaders = Arrays.asList(new BasicHeader(HttpHeaders.USER_AGENT, String.format("vespa-feed-client/%s", Vespa.VERSION)), new BasicHeader("Vespa-Client-Version", Vespa.VERSION)); + private final Header gzipEncodingHeader = new BasicHeader(HttpHeaders.CONTENT_ENCODING, "gzip"); private final RequestConfig requestConfig; + private final boolean gzip; private int someNumber = 0; private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(t -> new Thread(t, "request-timeout-thread")); @@ -52,6 +58,7 @@ class ApacheCluster implements Cluster { for (URI endpoint : builder.endpoints) endpoints.add(new Endpoint(createHttpClient(builder), endpoint)); this.requestConfig = createRequestConfig(builder); + this.gzip = builder.compression == Compression.gzip; } @Override @@ -77,8 +84,14 @@ class ApacheCluster implements Cluster { request.setConfig(requestConfig); defaultHeaders.forEach(request::setHeader); wrapped.headers().forEach((name, value) -> request.setHeader(name, value.get())); - if (wrapped.body() != null) - request.setBody(wrapped.body(), ContentType.APPLICATION_JSON); + if (wrapped.body() != null) { + byte[] body = wrapped.body(); + if (gzip) { + request.setHeader(gzipEncodingHeader); + body = gzipped(body); + } + request.setBody(body, ContentType.APPLICATION_JSON); + } Future<?> future = endpoint.client.execute(request, new FutureCallback<SimpleHttpResponse>() { @@ -96,6 +109,14 @@ class ApacheCluster implements Cluster { vessel.whenComplete((__, ___) -> endpoint.inflight.decrementAndGet()); } + private byte[] gzipped(byte[] content) throws IOException{ + ByteArrayOutputStream buffer = new ByteArrayOutputStream(1 << 10); + try (GZIPOutputStream zip = new GZIPOutputStream(buffer)) { + zip.write(content); + } + return buffer.toByteArray(); + } + @Override public void close() { Throwable thrown = null; diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java index 1960991792f..6886dc3d2b9 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import static ai.vespa.feed.client.FeedClientBuilder.Compression.none; import static java.util.Objects.requireNonNull; /** @@ -50,6 +51,7 @@ public class FeedClientBuilderImpl implements FeedClientBuilder { boolean benchmark = true; boolean dryrun = false; boolean speedTest = false; + Compression compression = none; URI proxy; @@ -200,6 +202,12 @@ public class FeedClientBuilderImpl implements FeedClientBuilder { return this; } + @Override + public FeedClientBuilderImpl setCompression(Compression compression) { + this.compression = compression; + return this; + } + /** Constructs instance of {@link ai.vespa.feed.client.FeedClient} from builder configuration */ @Override public FeedClient build() { diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java new file mode 100644 index 00000000000..33c043ea271 --- /dev/null +++ b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/ApacheClusterTest.java @@ -0,0 +1,74 @@ +package ai.vespa.feed.client.impl; + +import ai.vespa.feed.client.FeedClientBuilder.Compression; +import ai.vespa.feed.client.HttpResponse; +import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.zip.GZIPOutputStream; + +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ApacheClusterTest { + + @RegisterExtension + final WireMockExtension server = new WireMockExtension(); + + @Test + void testClient() throws IOException, ExecutionException, InterruptedException, TimeoutException { + for (Compression compression : Compression.values()) { + try (ApacheCluster cluster = new ApacheCluster(new FeedClientBuilderImpl(List.of(URI.create("http://localhost:" + server.port()))) + .setCompression(compression))) { + server.stubFor(any(anyUrl())) + .setResponse(okJson("{}").build()); + + CompletableFuture<HttpResponse> vessel = new CompletableFuture<>(); + cluster.dispatch(new HttpRequest("POST", + "/path", + Map.of("name1", () -> "value1", + "name2", () -> "value2"), + "content".getBytes(UTF_8), + Duration.ofSeconds(1)), + vessel); + HttpResponse response = vessel.get(5, TimeUnit.SECONDS); + assertEquals("{}", new String(response.body(), UTF_8)); + assertEquals(200, response.code()); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (OutputStream zip = new GZIPOutputStream(buffer)) { zip.write("content".getBytes(UTF_8)); } + server.verify(1, anyRequestedFor(anyUrl())); + RequestPatternBuilder expected = postRequestedFor(urlEqualTo("/path")).withHeader("name1", equalTo("value1")) + .withHeader("name2", equalTo("value2")) + .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withRequestBody(equalTo("content")); + expected = switch (compression) { + case none -> expected.withoutHeader("Content-Encoding"); + case gzip -> expected.withHeader("Content-Encoding", equalTo("gzip")); + }; + server.verify(1, expected); + server.resetRequests(); + } + } + } + +} diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/WireMockExtension.java b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/WireMockExtension.java new file mode 100644 index 00000000000..ef61213889b --- /dev/null +++ b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/WireMockExtension.java @@ -0,0 +1,42 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.feed.client.impl; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Allows wiremock to be used as a JUnit 5 extension, like + * <pre> + * + * @RegisterExtension + * WireMockExtension mockServer1 = new WireMockExtension(); + * </pre> + */ +public class WireMockExtension extends WireMockServer implements BeforeEachCallback, AfterEachCallback { + + public WireMockExtension() { + this(WireMockConfiguration.options() + .dynamicPort() + .dynamicHttpsPort()); + } + + public WireMockExtension(Options options) { + super(options); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) { + start(); + } + + @Override + public void afterEach(ExtensionContext extensionContext) { + stop(); + resetAll(); + } + +} |