diff options
Diffstat (limited to 'vespa-feed-client')
-rw-r--r-- | vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java | 84 | ||||
-rw-r--r-- | vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java | 12 |
2 files changed, 67 insertions, 29 deletions
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java index 80a107fbdc6..5c1c6ce0a91 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java @@ -15,16 +15,22 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; +import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import static ai.vespa.feed.client.OperationParameters.empty; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -51,6 +57,7 @@ class HttpFeedClient implements FeedClient { this.requestHeaders = new HashMap<>(builder.requestHeaders); this.requestStrategy = requestStrategy; this.speedTest = builder.speedTest; + verifyConnection(builder); } @Override @@ -113,31 +120,57 @@ class HttpFeedClient implements FeedClient { return promise; } + private void verifyConnection(FeedClientBuilderImpl builder) { + if (builder.dryrun) return; + try (Cluster cluster = new ApacheCluster(builder)) { + HttpRequest request = new HttpRequest("POST", + getPath(DocumentId.of("feeder", "handshake", "dummy")) + getQuery(empty(), true), + requestHeaders, + null, + Duration.ofSeconds(10)); + CompletableFuture<HttpResponse> future = new CompletableFuture<>(); + cluster.dispatch(request, future); + HttpResponse response = future.get(20, TimeUnit.SECONDS); + if (response.code() != 200) { + throw new FeedException("non-200 response: " + response); + } + } + catch (IOException e) { + throw new FeedException("failed handshake with server: " + e, e); + } + catch (ExecutionException e) { + throw new FeedException("failed handshake with server: " + e.getCause(), e.getCause()); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new FeedException("interrupted during handshake with server", e); + } + catch (TimeoutException e) { + throw new FeedException("timed out during handshake with server", e); + } + } + private enum Outcome { success, conditionNotMet, vespaFailure, transportFailure }; static Result.Type toResultType(Outcome outcome) { - switch (outcome) { - case success: return Result.Type.success; - case conditionNotMet: return Result.Type.conditionNotMet; - default: throw new IllegalArgumentException("No corresponding result type for '" + outcome + "'"); - } + return switch (outcome) { + case success -> Result.Type.success; + case conditionNotMet -> Result.Type.conditionNotMet; + default -> throw new IllegalArgumentException("No corresponding result type for '" + outcome + "'"); + }; } static Result toResult(HttpRequest request, HttpResponse response, DocumentId documentId) { - Outcome outcome; - switch (response.code()) { - case 200: outcome = Outcome.success; break; - case 412: outcome = Outcome.conditionNotMet; break; - case 502: - case 504: - case 507: outcome = Outcome.vespaFailure; break; - default: outcome = Outcome.transportFailure; - } + Outcome outcome = switch (response.code()) { + case 200 -> Outcome.success; + case 412 -> Outcome.conditionNotMet; + case 502, 504, 507 -> Outcome.vespaFailure; + default -> Outcome.transportFailure; + }; String message = null; String trace = null; - try { - JsonParser parser = factory.createParser(response.body()); + try (JsonParser parser = factory.createParser(response.body())) { if (parser.nextToken() != JsonToken.START_OBJECT) throw new ResultParseException( documentId, @@ -147,8 +180,8 @@ class HttpFeedClient implements FeedClient { String name; while ((name = parser.nextFieldName()) != null) { switch (name) { - case "message": message = parser.nextTextValue(); break; - case "trace": { + case "message" -> message = parser.nextTextValue(); + case "trace" -> { if (parser.nextToken() != JsonToken.START_ARRAY) throw new ResultParseException(documentId, "Expected 'trace' to be an array, but got '" + parser.currentToken() + "' in: " + @@ -156,13 +189,13 @@ class HttpFeedClient implements FeedClient { int start = (int) parser.getTokenLocation().getByteOffset(); int depth = 1; while (depth > 0) switch (parser.nextToken()) { - case START_ARRAY: ++depth; break; - case END_ARRAY: --depth; break; + case START_ARRAY -> ++depth; + case END_ARRAY -> --depth; } int end = (int) parser.getTokenLocation().getByteOffset() + 1; trace = new String(response.body(), start, end - start, UTF_8); - }; break; - default: parser.nextToken(); + } + default -> parser.nextToken(); } } @@ -211,12 +244,7 @@ class HttpFeedClient implements FeedClient { } static String encode(String raw) { - try { - return URLEncoder.encode(raw, UTF_8.name()); - } - catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } + return URLEncoder.encode(raw, UTF_8); } static String getQuery(OperationParameters params, boolean speedTest) { diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java index 5353ab92fb6..e9c996ae560 100644 --- a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java +++ b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java @@ -4,6 +4,7 @@ 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; import ai.vespa.feed.client.HttpResponse; import ai.vespa.feed.client.OperationParameters; import ai.vespa.feed.client.OperationStats; @@ -42,7 +43,8 @@ class HttpFeedClientTest { @Override public void await() { throw new UnsupportedOperationException(); } @Override public CompletableFuture<HttpResponse> enqueue(DocumentId documentId, HttpRequest request) { return dispatch.get().apply(documentId, request); } } - FeedClient client = new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))), new MockRequestStrategy()); + FeedClient client = new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))).setDryrun(true), + new MockRequestStrategy()); // Update is a PUT, and 200 OK is a success. dispatch.set((documentId, request) -> { @@ -203,4 +205,12 @@ class HttpFeedClientTest { assertEquals("Status 500 executing 'POST /document/v1/ns/type/docid/0': Alla ska i jorden.", expected.getCause().getMessage()); } + @Test + void testHandshake() { + assertEquals("failed handshake with server: java.net.UnknownHostException: dummy: nodename nor servname provided, or not known", + assertThrows(FeedException.class, + () -> new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))), null)) + .getMessage()); + } + } |