Skip to content

Commit 9246eab

Browse files
authored
Throw a TransportException when an error response cannot be parsed (#579)
1 parent d45687f commit 9246eab

File tree

3 files changed

+105
-10
lines changed

3 files changed

+105
-10
lines changed

Diff for: java-client/src/main/java/co/elastic/clients/transport/TransportException.java

+15-5
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,32 @@
2424

2525
public class TransportException extends IOException {
2626

27+
private final int statusCode;
2728
private final String endpointId;
2829

29-
public TransportException(String message, String endpointId) {
30-
this(message, endpointId, null);
30+
public TransportException(int statusCode, String message, String endpointId) {
31+
this(statusCode, message, endpointId, null);
3132
}
3233

33-
public TransportException(String message, String endpointId, Throwable cause) {
34-
super(endpointId == null ? message : "[" + endpointId + "] " + message, cause);
34+
public TransportException(int statusCode, String message, String endpointId, Throwable cause) {
35+
super("status: " + statusCode + ", " + (endpointId == null ? message : "[" + endpointId + "] " + message), cause);
36+
this.statusCode = statusCode;
3537
this.endpointId = endpointId;
3638
}
3739

40+
/**
41+
* Status code returned by the http resquest
42+
*/
43+
public int statusCode() {
44+
return statusCode;
45+
}
46+
3847
/**
3948
* Identifier of the API endpoint that caused the exception, if known.
4049
*/
4150
@Nullable
42-
String getEndpointId() {
51+
public String endpointId() {
4352
return endpointId;
4453
}
54+
4555
}

Diff for: java-client/src/main/java/co/elastic/clients/transport/rest_client/RestClientTransport.java

+15-5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import co.elastic.clients.util.ApiTypeHelper;
3737
import co.elastic.clients.util.BinaryData;
3838
import co.elastic.clients.util.MissingRequiredPropertyException;
39+
import jakarta.json.JsonException;
3940
import jakarta.json.stream.JsonGenerator;
4041
import jakarta.json.stream.JsonParser;
4142
import org.apache.http.HttpEntity;
@@ -85,7 +86,7 @@ public class RestClientTransport implements ElasticsearchTransport {
8586

8687
/**
8788
* The {@code Future} implementation returned by async requests.
88-
* It wraps the RestClient's cancellable and progagates cancellation.
89+
* It wraps the RestClient's cancellable and propagates cancellation.
8990
*/
9091
private static class RequestFuture<T> extends CompletableFuture<T> {
9192
private volatile Cancellable cancellable;
@@ -310,6 +311,7 @@ private <ResponseT, ErrorT> ResponseT getHighLevelResponse(
310311
JsonpDeserializer<ErrorT> errorDeserializer = endpoint.errorDeserializer(statusCode);
311312
if (errorDeserializer == null) {
312313
throw new TransportException(
314+
statusCode,
313315
"Request failed with status code '" + statusCode + "'",
314316
endpoint.id(), new ResponseException(clientResp)
315317
);
@@ -318,6 +320,7 @@ private <ResponseT, ErrorT> ResponseT getHighLevelResponse(
318320
HttpEntity entity = clientResp.getEntity();
319321
if (entity == null) {
320322
throw new TransportException(
323+
statusCode,
321324
"Expecting a response body, but none was sent",
322325
endpoint.id(), new ResponseException(clientResp)
323326
);
@@ -333,14 +336,17 @@ private <ResponseT, ErrorT> ResponseT getHighLevelResponse(
333336
// TODO: have the endpoint provide the exception constructor
334337
throw new ElasticsearchException(endpoint.id(), (ErrorResponse) error);
335338
}
336-
} catch(MissingRequiredPropertyException errorEx) {
339+
} catch(JsonException | MissingRequiredPropertyException errorEx) {
337340
// Could not decode exception, try the response type
338341
try {
339342
ResponseT response = decodeResponse(statusCode, entity, clientResp, endpoint);
340343
return response;
341344
} catch(Exception respEx) {
342345
// No better luck: throw the original error decoding exception
343-
throw new TransportException("Failed to decode error response", endpoint.id(), new ResponseException(clientResp));
346+
throw new TransportException(statusCode,
347+
"Failed to decode error response, check exception cause for additional details", endpoint.id(),
348+
new ResponseException(clientResp)
349+
);
344350
}
345351
}
346352
} else {
@@ -368,6 +374,7 @@ private <ResponseT> ResponseT decodeResponse(
368374
// Expecting a body
369375
if (entity == null) {
370376
throw new TransportException(
377+
statusCode,
371378
"Expecting a response body, but none was sent",
372379
endpoint.id(), new ResponseException(clientResp)
373380
);
@@ -395,7 +402,7 @@ private <ResponseT> ResponseT decodeResponse(
395402
return response;
396403

397404
} else {
398-
throw new TransportException("Unhandled endpoint type: '" + endpoint.getClass().getName() + "'", endpoint.id());
405+
throw new TransportException(statusCode, "Unhandled endpoint type: '" + endpoint.getClass().getName() + "'", endpoint.id());
399406
}
400407
}
401408

@@ -411,6 +418,7 @@ private void checkProductHeader(Response clientResp, Endpoint<?, ?, ?> endpoint)
411418
return;
412419
}
413420
throw new TransportException(
421+
clientResp.getStatusLine().getStatusCode(),
414422
"Missing [X-Elastic-Product] header. Please check that you are connecting to an Elasticsearch "
415423
+ "instance, and that any networking filters are preserving that header.",
416424
endpoint.id(),
@@ -419,7 +427,9 @@ private void checkProductHeader(Response clientResp, Endpoint<?, ?, ?> endpoint)
419427
}
420428

421429
if (!"Elasticsearch".equals(header)) {
422-
throw new TransportException("Invalid value '" + header + "' for 'X-Elastic-Product' header.",
430+
throw new TransportException(
431+
clientResp.getStatusLine().getStatusCode(),
432+
"Invalid value '" + header + "' for 'X-Elastic-Product' header.",
423433
endpoint.id(),
424434
new ResponseException(clientResp)
425435
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.transport;
21+
22+
import co.elastic.clients.elasticsearch.ElasticsearchClient;
23+
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
24+
import co.elastic.clients.transport.rest_client.RestClientTransport;
25+
import com.sun.net.httpserver.HttpServer;
26+
import org.apache.http.HttpHost;
27+
import org.elasticsearch.client.ResponseException;
28+
import org.elasticsearch.client.RestClient;
29+
import org.junit.jupiter.api.Assertions;
30+
import org.junit.jupiter.api.Test;
31+
32+
import java.io.OutputStream;
33+
import java.net.InetAddress;
34+
import java.net.InetSocketAddress;
35+
import java.nio.charset.StandardCharsets;
36+
37+
public class TransportTest extends Assertions {
38+
39+
@Test
40+
public void testXMLResponse() throws Exception {
41+
HttpServer httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
42+
43+
httpServer.createContext("/_cat/indices", exchange -> {
44+
exchange.sendResponseHeaders(401, 0);
45+
OutputStream out = exchange.getResponseBody();
46+
out.write(
47+
"<?xml version=\"1.0\"?><error>Error</error>".getBytes(StandardCharsets.UTF_8)
48+
);
49+
out.close();
50+
});
51+
52+
httpServer.start();
53+
InetSocketAddress address = httpServer.getAddress();
54+
55+
RestClient restClient = RestClient
56+
.builder(new HttpHost(address.getHostString(), address.getPort(), "http"))
57+
.build();
58+
59+
ElasticsearchClient esClient = new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper()));
60+
61+
TransportException ex = Assertions.assertThrows(
62+
TransportException.class,
63+
() -> esClient.cat().indices()
64+
);
65+
66+
httpServer.stop(0);
67+
68+
assertEquals(401, ex.statusCode());
69+
assertEquals("es/cat.indices", ex.endpointId());
70+
71+
// Cause is transport-dependent
72+
ResponseException restException = (ResponseException) ex.getCause();
73+
assertEquals(401, restException.getResponse().getStatusLine().getStatusCode());
74+
}
75+
}

0 commit comments

Comments
 (0)