Replace rest test client with low level RestClient

We still have a wrapper called RestTestClient that is very specific to Rest tests, as well as RestTestResponse etc. but all the low level bits around http connections etc. are now handled by RestClient.
This commit is contained in:
javanna 2016-05-19 15:18:17 +02:00 committed by Luca Cavanna
parent 325b723930
commit eae914ae8e
14 changed files with 258 additions and 712 deletions

View File

@ -1311,9 +1311,6 @@
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]engine[/\\]MockEngineSupport.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]hamcrest[/\\]ElasticsearchAssertions.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]junit[/\\]listeners[/\\]LoggingListener.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]ESRestTestCase.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]RestTestExecutionContext.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]client[/\\]http[/\\]HttpRequestBuilder.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]json[/\\]JsonPath.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]parser[/\\]GreaterThanEqualToParser.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]parser[/\\]GreaterThanParser.java" checks="LineLength" />
@ -1321,7 +1318,6 @@
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]parser[/\\]LessThanParser.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]parser[/\\]RestTestSuiteParseContext.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]parser[/\\]RestTestSuiteParser.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]section[/\\]DoSection.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]section[/\\]GreaterThanAssertion.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]section[/\\]GreaterThanEqualToAssertion.java" checks="LineLength" />
<suppress files="test[/\\]framework[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]test[/\\]rest[/\\]section[/\\]LengthAssertion.java" checks="LineLength" />

View File

@ -121,7 +121,6 @@ public final class RestClient implements Closeable {
ElasticsearchResponse elasticsearchResponse = new ElasticsearchResponse(request.getRequestLine(),
connection.getHost(), response);
int statusCode = response.getStatusLine().getStatusCode();
//TODO make ignore status code configurable. rest-spec and tests support that parameter (ignore_missing)
if (statusCode < 300 || (request.getMethod().equals(HttpHead.METHOD_NAME) && statusCode == 404) ) {
RequestLogger.log(logger, "request succeeded", request, connection.getHost(), response);
onSuccess(connection);

View File

@ -121,7 +121,6 @@ public class ExceptionSerializationTests extends ESTestCase {
.resolve("org").resolve("elasticsearch");
final Set<? extends Class<?>> ignore = Sets.newHashSet(
org.elasticsearch.test.rest.parser.RestTestParseException.class,
org.elasticsearch.test.rest.client.RestException.class,
CancellableThreadsTests.CustomException.class,
org.elasticsearch.rest.BytesRestResponseTests.WithHeadersException.class,
AbstractClientHeadersTestCase.InternalException.class);

View File

@ -19,6 +19,31 @@
package org.elasticsearch.test.rest;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksAction;
import org.elasticsearch.client.ElasticsearchResponseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.client.RestTestResponse;
import org.elasticsearch.test.rest.parser.RestTestParseException;
import org.elasticsearch.test.rest.parser.RestTestSuiteParser;
import org.elasticsearch.test.rest.section.DoSection;
import org.elasticsearch.test.rest.section.ExecutableSection;
import org.elasticsearch.test.rest.section.RestTestSuite;
import org.elasticsearch.test.rest.section.SkipSection;
import org.elasticsearch.test.rest.section.TestSection;
import org.elasticsearch.test.rest.spec.RestApi;
import org.elasticsearch.test.rest.spec.RestSpec;
import org.elasticsearch.test.rest.support.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@ -38,32 +63,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksAction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.client.RestException;
import org.elasticsearch.test.rest.client.RestResponse;
import org.elasticsearch.test.rest.parser.RestTestParseException;
import org.elasticsearch.test.rest.parser.RestTestSuiteParser;
import org.elasticsearch.test.rest.section.DoSection;
import org.elasticsearch.test.rest.section.ExecutableSection;
import org.elasticsearch.test.rest.section.RestTestSuite;
import org.elasticsearch.test.rest.section.SkipSection;
import org.elasticsearch.test.rest.section.TestSection;
import org.elasticsearch.test.rest.spec.RestApi;
import org.elasticsearch.test.rest.spec.RestSpec;
import org.elasticsearch.test.rest.support.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.sort;
@ -98,8 +97,8 @@ public abstract class ESRestTestCase extends ESTestCase {
private static final String DEFAULT_SPEC_PATH = "/rest-api-spec/api";
/**
* This separator pattern matches ',' except it is preceded by a '\'. This allows us to support ',' within paths when it is escaped with
* a slash.
* This separator pattern matches ',' except it is preceded by a '\'.
* This allows us to support ',' within paths when it is escaped with a slash.
*
* For example, the path string "/a/b/c\,d/e/f,/foo/bar,/baz" is separated to "/a/b/c\,d/e/f", "/foo/bar" and "/baz".
*
@ -233,7 +232,7 @@ public abstract class ESRestTestCase extends ESTestCase {
}
@BeforeClass
public static void initExecutionContext() throws IOException, RestException {
public static void initExecutionContext() throws IOException {
String[] specPaths = resolvePathsProperty(REST_TESTS_SPEC, DEFAULT_SPEC_PATH);
RestSpec restSpec = null;
FileSystem fileSystem = getFileSystem();
@ -277,9 +276,9 @@ public abstract class ESRestTestCase extends ESTestCase {
deleteIndicesArgs.put("index", "*");
try {
adminExecutionContext.callApi("indices.delete", deleteIndicesArgs, Collections.emptyList(), Collections.emptyMap());
} catch (RestException e) {
} catch (ElasticsearchResponseException e) {
// 404 here just means we had no indexes
if (e.statusCode() != 404) {
if (e.getElasticsearchResponse().getStatusLine().getStatusCode() != 404) {
throw e;
}
}
@ -300,8 +299,8 @@ public abstract class ESRestTestCase extends ESTestCase {
* other tests.
*/
@After
public void logIfThereAreRunningTasks() throws InterruptedException, IOException, RestException {
RestResponse tasks = adminExecutionContext.callApi("tasks.list", emptyMap(), emptyList(), emptyMap());
public void logIfThereAreRunningTasks() throws InterruptedException, IOException {
RestTestResponse tasks = adminExecutionContext.callApi("tasks.list", emptyMap(), emptyList(), emptyMap());
Set<String> runningTasks = runningTasks(tasks);
// Ignore the task list API - it doens't count against us
runningTasks.remove(ListTasksAction.NAME);
@ -347,7 +346,7 @@ public abstract class ESRestTestCase extends ESTestCase {
}
@Before
public void reset() throws IOException, RestException {
public void reset() throws IOException {
// admin context must be available for @After always, regardless of whether the test was blacklisted
adminExecutionContext.initClient(clusterUrls, restAdminSettings());
adminExecutionContext.clear();
@ -355,7 +354,8 @@ public abstract class ESRestTestCase extends ESTestCase {
//skip test if it matches one of the blacklist globs
for (BlacklistedPathPatternMatcher blacklistedPathMatcher : blacklistPathMatchers) {
String testPath = testCandidate.getSuitePath() + "/" + testCandidate.getTestSection().getName();
assumeFalse("[" + testCandidate.getTestPath() + "] skipped, reason: blacklisted", blacklistedPathMatcher.isSuffixMatch(testPath));
assumeFalse("[" + testCandidate.getTestPath() + "] skipped, reason: blacklisted", blacklistedPathMatcher
.isSuffixMatch(testPath));
}
//The client needs non static info to get initialized, therefore it can't be initialized in the before class
restTestExecutionContext.initClient(clusterUrls, restClientSettings());
@ -374,7 +374,8 @@ public abstract class ESRestTestCase extends ESTestCase {
if (skipSection.isVersionCheck()) {
messageBuilder.append("[").append(description).append("] skipped, reason: [").append(skipSection.getReason()).append("] ");
} else {
messageBuilder.append("[").append(description).append("] skipped, reason: features ").append(skipSection.getFeatures()).append(" not supported");
messageBuilder.append("[").append(description).append("] skipped, reason: features ")
.append(skipSection.getFeatures()).append(" not supported");
}
return messageBuilder.toString();
}
@ -401,7 +402,7 @@ public abstract class ESRestTestCase extends ESTestCase {
}
@SuppressWarnings("unchecked")
public Set<String> runningTasks(RestResponse response) throws IOException {
public Set<String> runningTasks(RestTestResponse response) throws IOException {
Set<String> runningTasks = new HashSet<>();
Map<String, Object> nodes = (Map<String, Object>) response.evaluate("nodes");
for (Map.Entry<String, Object> node : nodes.entrySet()) {

View File

@ -19,18 +19,17 @@
package org.elasticsearch.test.rest;
import org.elasticsearch.Version;
import org.elasticsearch.client.ElasticsearchResponseException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.rest.client.RestClient;
import org.elasticsearch.test.rest.client.RestException;
import org.elasticsearch.test.rest.client.RestResponse;
import org.elasticsearch.test.rest.client.RestTestClient;
import org.elasticsearch.test.rest.client.RestTestResponse;
import org.elasticsearch.test.rest.spec.RestSpec;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
@ -50,9 +49,9 @@ public class RestTestExecutionContext implements Closeable {
private final RestSpec restSpec;
private RestClient restClient;
private RestTestClient restTestClient;
private RestResponse response;
private RestTestResponse response;
public RestTestExecutionContext(RestSpec restSpec) {
this.restSpec = restSpec;
@ -61,10 +60,9 @@ public class RestTestExecutionContext implements Closeable {
/**
* Calls an elasticsearch api with the parameters and request body provided as arguments.
* Saves the obtained response in the execution context.
* @throws RestException if the returned status code is non ok
*/
public RestResponse callApi(String apiName, Map<String, String> params, List<Map<String, Object>> bodies,
Map<String, String> headers) throws IOException, RestException {
public RestTestResponse callApi(String apiName, Map<String, String> params, List<Map<String, Object>> bodies,
Map<String, String> headers) throws IOException {
//makes a copy of the parameters before modifying them for this specific request
HashMap<String, String> requestParams = new HashMap<>(params);
for (Map.Entry<String, String> entry : requestParams.entrySet()) {
@ -80,8 +78,8 @@ public class RestTestExecutionContext implements Closeable {
//we always stash the last response body
stash.stashResponse(response);
return response;
} catch(RestException e) {
response = e.restResponse();
} catch(ElasticsearchResponseException e) {
response = new RestTestResponse(e);
throw e;
}
}
@ -106,8 +104,9 @@ public class RestTestExecutionContext implements Closeable {
return XContentFactory.jsonBuilder().map(body).string();
}
private RestResponse callApiInternal(String apiName, Map<String, String> params, String body, Map<String, String> headers) throws IOException, RestException {
return restClient.callApi(apiName, params, body, headers);
private RestTestResponse callApiInternal(String apiName, Map<String, String> params, String body, Map<String, String> headers)
throws IOException {
return restTestClient.callApi(apiName, params, body, headers);
}
/**
@ -120,9 +119,9 @@ public class RestTestExecutionContext implements Closeable {
/**
* Creates the embedded REST client when needed. Needs to be called before each test.
*/
public void initClient(URL[] urls, Settings settings) throws IOException, RestException {
if (restClient == null) {
restClient = new RestClient(restSpec, settings, urls);
public void initClient(URL[] urls, Settings settings) throws IOException {
if (restTestClient == null) {
restTestClient = new RestTestClient(restSpec, settings, urls);
}
}
@ -143,7 +142,7 @@ public class RestTestExecutionContext implements Closeable {
* Returns the current es version as a string
*/
public Version esVersion() {
return restClient.getEsVersion();
return restTestClient.getEsVersion();
}
/**
@ -151,8 +150,8 @@ public class RestTestExecutionContext implements Closeable {
*/
@Override
public void close() {
if (restClient != null) {
restClient.close();
if (restTestClient != null) {
restTestClient.close();
}
}
}

View File

@ -19,17 +19,17 @@
package org.elasticsearch.test.rest;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.test.rest.client.RestResponse;
import org.elasticsearch.test.rest.client.RestTestResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Allows to cache the last obtained test response and or part of it within variables
@ -42,7 +42,7 @@ public class Stash implements ToXContent {
public static final Stash EMPTY = new Stash();
private final Map<String, Object> stash = new HashMap<>();
private RestResponse response;
private RestTestResponse response;
/**
* Allows to saved a specific field in the stash as key-value pair
@ -55,7 +55,7 @@ public class Stash implements ToXContent {
}
}
public void stashResponse(RestResponse response) throws IOException {
public void stashResponse(RestTestResponse response) throws IOException {
// TODO we can almost certainly save time by lazily evaluating the body
stashValue("body", response.getBody());
this.response = response;

View File

@ -1,41 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.client;
/**
* Thrown when a status code that holds an error is received (unless needs to be ignored)
* Holds the original {@link RestResponse}
*/
public class RestException extends Exception {
private final RestResponse restResponse;
public RestException(String message, RestResponse restResponse) {
super(message);
this.restResponse = restResponse;
}
public RestResponse restResponse() {
return restResponse;
}
public int statusCode() {
return restResponse.getStatusCode();
}
}

View File

@ -19,17 +19,25 @@
package org.elasticsearch.test.rest.client;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.message.BasicHeader;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Version;
import org.elasticsearch.client.ElasticsearchResponse;
import org.elasticsearch.client.ElasticsearchResponseException;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.ESLogger;
@ -37,8 +45,6 @@ import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
import org.elasticsearch.test.rest.client.http.HttpResponse;
import org.elasticsearch.test.rest.spec.RestApi;
import org.elasticsearch.test.rest.spec.RestSpec;
@ -46,6 +52,8 @@ import javax.net.ssl.SSLContext;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@ -55,61 +63,53 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static java.util.Objects.requireNonNull;
/**
* REST client used to test the elasticsearch REST layer
* Holds the {@link RestSpec} used to translate api calls into REST calls
*/
public class RestClient implements Closeable {
public class RestTestClient implements Closeable {
public static final String PROTOCOL = "protocol";
public static final String TRUSTSTORE_PATH = "truststore.path";
public static final String TRUSTSTORE_PASSWORD = "truststore.password";
private static final ESLogger logger = Loggers.getLogger(RestClient.class);
private static final ESLogger logger = Loggers.getLogger(RestTestClient.class);
//query_string params that don't need to be declared in the spec, thay are supported by default
private static final Set<String> ALWAYS_ACCEPTED_QUERY_STRING_PARAMS = Sets.newHashSet("pretty", "source", "filter_path");
private final String protocol;
private final RestSpec restSpec;
private final CloseableHttpClient httpClient;
private final URL[] urls;
private final RestClient restClient;
private final Version esVersion;
private final ThreadContext threadContext;
public RestClient(RestSpec restSpec, Settings settings, URL[] urls) throws IOException, RestException {
public RestTestClient(RestSpec restSpec, Settings settings, URL[] urls) throws IOException {
assert urls.length > 0;
this.restSpec = restSpec;
this.protocol = settings.get(PROTOCOL, "http");
this.httpClient = createHttpClient(settings);
this.threadContext = new ThreadContext(settings);
this.urls = urls;
this.esVersion = readAndCheckVersion();
this.restClient = createRestClient(urls, settings);
this.esVersion = readAndCheckVersion(urls);
logger.info("REST client initialized {}, elasticsearch version: [{}]", urls, esVersion);
}
private Version readAndCheckVersion() throws IOException, RestException {
//we make a manual call here without using callApi method, mainly because we are initializing
//and the randomized context doesn't exist for the current thread (would be used to choose the method otherwise)
private Version readAndCheckVersion(URL[] urls) throws IOException {
RestApi restApi = restApi("info");
assert restApi.getPaths().size() == 1;
assert restApi.getMethods().size() == 1;
String version = null;
for (URL url : urls) {
RestResponse restResponse = new RestResponse(httpRequestBuilder(url)
.path(restApi.getPaths().get(0))
.method(restApi.getMethods().get(0)).execute());
checkStatusCode(restResponse);
Object latestVersion = restResponse.evaluate("version.number");
for (URL ignored : urls) {
//we don't really use the urls here, we rely on the client doing round-robin to touch all the nodes in the cluster
String method = restApi.getMethods().get(0);
String endpoint = restApi.getPaths().get(0);
ElasticsearchResponse elasticsearchResponse = restClient.performRequest(method, endpoint, Collections.emptyMap(), null);
RestTestResponse restTestResponse = new RestTestResponse(elasticsearchResponse);
Object latestVersion = restTestResponse.evaluate("version.number");
if (latestVersion == null) {
throw new RuntimeException("elasticsearch version not found in the response");
}
@ -130,77 +130,41 @@ public class RestClient implements Closeable {
/**
* Calls an api with the provided parameters and body
* @throws RestException if the obtained status code is non ok, unless the specific error code needs to be ignored
* according to the ignore parameter received as input (which won't get sent to elasticsearch)
*/
public RestResponse callApi(String apiName, Map<String, String> params, String body, Map<String, String> headers)
throws IOException, RestException {
public RestTestResponse callApi(String apiName, Map<String, String> params, String body, Map<String, String> headers)
throws IOException {
List<Integer> ignores = new ArrayList<>();
Map<String, String> requestParams = null;
if (params != null) {
//makes a copy of the parameters before modifying them for this specific request
requestParams = new HashMap<>(params);
//ignore is a special parameter supported by the clients, shouldn't be sent to es
String ignoreString = requestParams.remove("ignore");
if (Strings.hasLength(ignoreString)) {
try {
ignores.add(Integer.valueOf(ignoreString));
} catch(NumberFormatException e) {
throw new IllegalArgumentException("ignore value should be a number, found [" + ignoreString + "] instead");
}
}
}
HttpRequestBuilder httpRequestBuilder = callApiBuilder(apiName, requestParams, body);
for (Map.Entry<String, String> header : headers.entrySet()) {
logger.error("Adding header {}\n with value {}", header.getKey(), header.getValue());
httpRequestBuilder.addHeader(header.getKey(), header.getValue());
}
logger.debug("calling api [{}]", apiName);
HttpResponse httpResponse = httpRequestBuilder.execute();
// http HEAD doesn't support response body
// For the few api (exists class of api) that use it we need to accept 404 too
if (!httpResponse.supportsBody()) {
ignores.add(404);
}
RestResponse restResponse = new RestResponse(httpResponse);
checkStatusCode(restResponse, ignores);
return restResponse;
}
private void checkStatusCode(RestResponse restResponse, List<Integer> ignores) throws RestException {
//ignore is a catch within the client, to prevent the client from throwing error if it gets non ok codes back
if (ignores.contains(restResponse.getStatusCode())) {
if (logger.isDebugEnabled()) {
logger.debug("ignored non ok status codes {} as requested", ignores);
}
return;
}
checkStatusCode(restResponse);
}
private void checkStatusCode(RestResponse restResponse) throws RestException {
if (restResponse.isError()) {
throw new RestException("non ok status code [" + restResponse.getStatusCode() + "] returned", restResponse);
}
}
private HttpRequestBuilder callApiBuilder(String apiName, Map<String, String> params, String body) {
if ("raw".equals(apiName)) {
// Raw requests are bit simpler....
HttpRequestBuilder httpRequestBuilder = httpRequestBuilder();
httpRequestBuilder.method(requireNonNull(params.remove("method"), "Method must be set to use raw request"));
httpRequestBuilder.path("/"+ requireNonNull(params.remove("path"), "Path must be set to use raw request"));
httpRequestBuilder.body(body);
// And everything else is a url parameter!
for (Map.Entry<String, String> entry : params.entrySet()) {
httpRequestBuilder.addParam(entry.getKey(), entry.getValue());
HashMap<String, String> queryStringParams = new HashMap<>(params);
String method = Objects.requireNonNull(queryStringParams.remove("method"), "Method must be set to use raw request");
String path = "/"+ Objects.requireNonNull(queryStringParams.remove("path"), "Path must be set to use raw request");
HttpEntity entity = null;
if (body != null && body.length() > 0) {
entity = new StringEntity(body, RestClient.JSON_CONTENT_TYPE);
}
// And everything else is a url parameter!
ElasticsearchResponse response = restClient.performRequest(method, path, queryStringParams, entity);
return new RestTestResponse(response);
}
List<Integer> ignores = new ArrayList<>();
Map<String, String> requestParams;
if (params == null) {
requestParams = Collections.emptyMap();
} else {
requestParams = new HashMap<>(params);
if (params.isEmpty() == false) {
//ignore is a special parameter supported by the clients, shouldn't be sent to es
String ignoreString = requestParams.remove("ignore");
if (ignoreString != null) {
try {
ignores.add(Integer.valueOf(ignoreString));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("ignore value should be a number, found [" + ignoreString + "] instead");
}
}
}
return httpRequestBuilder;
}
//create doesn't exist in the spec but is supported in the clients (index with op_type=create)
@ -208,51 +172,91 @@ public class RestClient implements Closeable {
String api = indexCreateApi ? "index" : apiName;
RestApi restApi = restApi(api);
HttpRequestBuilder httpRequestBuilder = httpRequestBuilder();
//divide params between ones that go within query string and ones that go within path
Map<String, String> pathParts = new HashMap<>();
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
if (restApi.getPathParts().contains(entry.getKey())) {
pathParts.put(entry.getKey(), entry.getValue());
Map<String, String> queryStringParams = new HashMap<>();
for (Map.Entry<String, String> entry : requestParams.entrySet()) {
if (restApi.getPathParts().contains(entry.getKey())) {
pathParts.put(entry.getKey(), entry.getValue());
} else {
if (restApi.getParams().contains(entry.getKey()) || ALWAYS_ACCEPTED_QUERY_STRING_PARAMS.contains(entry.getKey())) {
queryStringParams.put(entry.getKey(), entry.getValue());
} else {
if (restApi.getParams().contains(entry.getKey()) || ALWAYS_ACCEPTED_QUERY_STRING_PARAMS.contains(entry.getKey())) {
httpRequestBuilder.addParam(entry.getKey(), entry.getValue());
} else {
throw new IllegalArgumentException("param [" + entry.getKey() +
"] not supported in [" + restApi.getName() + "] api");
}
throw new IllegalArgumentException("param [" + entry.getKey() + "] not supported in ["
+ restApi.getName() + "] " + "api");
}
}
}
if (indexCreateApi) {
httpRequestBuilder.addParam("op_type", "create");
queryStringParams.put("op_type", "create");
}
List<String> supportedMethods = restApi.getSupportedMethods(pathParts.keySet());
String requestMethod;
StringEntity requestBody = null;
if (Strings.hasLength(body)) {
if (!restApi.isBodySupported()) {
throw new IllegalArgumentException("body is not supported by [" + restApi.getName() + "] api");
}
//test the GET with source param instead of GET/POST with body
//randomly test the GET with source param instead of GET/POST with body
if (supportedMethods.contains("GET") && RandomizedTest.rarely()) {
logger.debug("sending the request body as source param with GET method");
httpRequestBuilder.addParam("source", body).method("GET");
queryStringParams.put("source", body);
requestMethod = "GET";
} else {
httpRequestBuilder.body(body).method(RandomizedTest.randomFrom(supportedMethods));
requestMethod = RandomizedTest.randomFrom(supportedMethods);
requestBody = new StringEntity(body, RestClient.JSON_CONTENT_TYPE);
}
} else {
if (restApi.isBodyRequired()) {
throw new IllegalArgumentException("body is required by [" + restApi.getName() + "] api");
}
httpRequestBuilder.method(RandomizedTest.randomFrom(supportedMethods));
requestMethod = RandomizedTest.randomFrom(supportedMethods);
}
//the rest path to use is randomized out of the matching ones (if more than one)
RestPath restPath = RandomizedTest.randomFrom(restApi.getFinalPaths(pathParts));
return httpRequestBuilder.pathParts(restPath.getPathParts());
//Encode rules for path and query string parameters are different. We use URI to encode the path.
//We need to encode each path part separately, as each one might contain slashes that need to be escaped, which needs to
//be done manually.
String requestPath;
if (restPath.getPathParts().length == 0) {
requestPath = "/";
} else {
StringBuilder finalPath = new StringBuilder();
for (String pathPart : restPath.getPathParts()) {
try {
finalPath.append('/');
// We append "/" to the path part to handle parts that start with - or other invalid characters
URI uri = new URI(null, null, null, -1, "/" + pathPart, null, null);
//manually escape any slash that each part may contain
finalPath.append(uri.getRawPath().substring(1).replaceAll("/", "%2F"));
} catch (URISyntaxException e) {
throw new RuntimeException("unable to build uri", e);
}
}
requestPath = finalPath.toString();
}
Header[] requestHeaders = new Header[headers.size()];
int index = 0;
for (Map.Entry<String, String> header : headers.entrySet()) {
logger.info("Adding header {}\n with value {}", header.getKey(), header.getValue());
requestHeaders[index++] = new BasicHeader(header.getKey(), header.getValue());
}
logger.debug("calling api [{}]", apiName);
try {
ElasticsearchResponse response = restClient.performRequest(requestMethod, requestPath,
queryStringParams, requestBody, requestHeaders);
return new RestTestResponse(response);
} catch(ElasticsearchResponseException e) {
if (ignores.contains(e.getElasticsearchResponse().getStatusLine().getStatusCode())) {
return new RestTestResponse(e);
}
throw e;
}
}
private RestApi restApi(String apiName) {
@ -263,21 +267,7 @@ public class RestClient implements Closeable {
return restApi;
}
protected HttpRequestBuilder httpRequestBuilder(URL url) {
return new HttpRequestBuilder(httpClient)
.addHeaders(threadContext.getHeaders())
.protocol(protocol)
.host(url.getHost())
.port(url.getPort());
}
protected HttpRequestBuilder httpRequestBuilder() {
//the address used is randomized between the available ones
URL url = RandomizedTest.randomFrom(urls);
return httpRequestBuilder(url);
}
protected CloseableHttpClient createHttpClient(Settings settings) throws IOException {
protected RestClient createRestClient(URL[] urls, Settings settings) throws IOException {
SSLConnectionSocketFactory sslsf;
String keystorePath = settings.get(TRUSTSTORE_PATH);
if (keystorePath != null) {
@ -307,8 +297,24 @@ public class RestClient implements Closeable {
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf)
.build();
return HttpClients.createMinimal(
new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, 15, TimeUnit.SECONDS));
List<Header> headers = new ArrayList<>();
try (ThreadContext threadContext = new ThreadContext(settings)) {
for (Map.Entry<String, String> entry : threadContext.getHeaders().entrySet()) {
headers.add(new BasicHeader(entry.getKey(), entry.getValue()));
}
}
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultHeaders(headers)
.setConnectionManager(new PoolingHttpClientConnectionManager(socketFactoryRegistry)).build();
String protocol = settings.get(PROTOCOL, "http");
HttpHost[] hosts = new HttpHost[urls.length];
for (int i = 0; i < hosts.length; i++) {
URL url = urls[i];
hosts[i] = new HttpHost(url.getHost(), url.getPort(), protocol);
}
return RestClient.builder().setHttpClient(httpClient).setHosts(hosts).build();
}
/**
@ -316,6 +322,6 @@ public class RestClient implements Closeable {
*/
@Override
public void close() {
IOUtils.closeWhileHandlingException(httpClient);
IOUtils.closeWhileHandlingException(restClient);
}
}

View File

@ -18,31 +18,54 @@
*/
package org.elasticsearch.test.rest.client;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.util.EntityUtils;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.client.ElasticsearchResponse;
import org.elasticsearch.client.ElasticsearchResponseException;
import org.elasticsearch.test.rest.Stash;
import org.elasticsearch.test.rest.client.http.HttpResponse;
import org.elasticsearch.test.rest.json.JsonPath;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Response obtained from a REST call
* Supports parsing the response body as json when needed and returning specific values extracted from it
* Response obtained from a REST call, eagerly reads the response body into a string for later optional parsing.
* Supports parsing the response body as json when needed and returning specific values extracted from it.
*/
public class RestResponse {
public class RestTestResponse {
private final HttpResponse response;
private final ElasticsearchResponse response;
private final String body;
private JsonPath parsedResponse;
public RestResponse(HttpResponse response) {
public RestTestResponse(ElasticsearchResponse response) {
this.response = response;
if (response.getEntity() != null) {
try {
this.body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
} catch (IOException e) {
EntityUtils.consumeQuietly(response.getEntity());
throw new RuntimeException(e);
} finally {
IOUtils.closeWhileHandlingException(response);
}
} else {
this.body = null;
}
}
public RestTestResponse(ElasticsearchResponseException responseException) {
this.response = responseException.getElasticsearchResponse();
this.body = responseException.getResponseBody();
}
public int getStatusCode() {
return response.getStatusCode();
return response.getStatusLine().getStatusCode();
}
public String getReasonPhrase() {
return response.getReasonPhrase();
return response.getStatusLine().getReasonPhrase();
}
/**
@ -57,18 +80,18 @@ public class RestResponse {
}
return parsedResponse.evaluate("");
}
return response.getBody();
return body;
}
/**
* Returns the body as a string
*/
public String getBodyAsString() {
return response.getBody();
return body;
}
public boolean isError() {
return response.isError();
return response.getStatusLine().getStatusCode() >= 400;
}
/**
@ -82,7 +105,6 @@ public class RestResponse {
* Parses the response body as json and extracts a specific value from it (identified by the provided path)
*/
public Object evaluate(String path, Stash stash) throws IOException {
if (response == null) {
return null;
}
@ -93,8 +115,8 @@ public class RestResponse {
//special case: api that don't support body (e.g. exists) return true if 200, false if 404, even if no body
//is_true: '' means the response had no body but the client returned true (caused by 200)
//is_false: '' means the response had no body but the client returned false (caused by 404)
if ("".equals(path) && !response.supportsBody()) {
return !response.isError();
if ("".equals(path) && HttpHead.METHOD_NAME.equals(response.getRequestLine().getMethod())) {
return isError() == false;
}
return null;
}
@ -103,7 +125,7 @@ public class RestResponse {
}
private boolean isJson() {
String contentType = response.getHeaders().get("Content-Type");
String contentType = response.getFirstHeader("Content-Type");
return contentType != null && contentType.contains("application/json");
}
@ -111,9 +133,9 @@ public class RestResponse {
if (parsedResponse != null) {
return parsedResponse;
}
if (response == null || !response.hasBody()) {
if (response == null || body == null) {
return null;
}
return parsedResponse = new JsonPath(response.getBody());
return parsedResponse = new JsonPath(body);
}
}

View File

@ -1,40 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.client.http;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import java.net.URI;
/**
* Allows to send DELETE requests providing a body (not supported out of the box)
*/
public class HttpDeleteWithEntity extends HttpEntityEnclosingRequestBase {
public final static String METHOD_NAME = "DELETE";
public HttpDeleteWithEntity(final URI uri) {
setURI(uri);
}
@Override
public String getMethod() {
return METHOD_NAME;
}
}

View File

@ -1,40 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.client.http;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import java.net.URI;
/**
* Allows to send GET requests providing a body (not supported out of the box)
*/
public class HttpGetWithEntity extends HttpEntityEnclosingRequestBase {
public final static String METHOD_NAME = "GET";
public HttpGetWithEntity(final URI uri) {
setURI(uri);
}
@Override
public String getMethod() {
return METHOD_NAME;
}
}

View File

@ -1,250 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.client.http;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.http.HttpServerTransport;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Executable builder for an http request
* Holds an {@link org.apache.http.client.HttpClient} that is used to send the built http request
*/
public class HttpRequestBuilder {
private static final ESLogger logger = Loggers.getLogger(HttpRequestBuilder.class);
static final Charset DEFAULT_CHARSET = Charset.forName("utf-8");
private final CloseableHttpClient httpClient;
private String protocol = "http";
private String host;
private int port;
private String path = "";
private final Map<String, String> params = new HashMap<>();
private final Map<String, String> headers = new HashMap<>();
private String method = HttpGetWithEntity.METHOD_NAME;
private String body;
public HttpRequestBuilder(CloseableHttpClient httpClient) {
this.httpClient = httpClient;
}
public HttpRequestBuilder host(String host) {
this.host = host;
return this;
}
public HttpRequestBuilder httpTransport(HttpServerTransport httpServerTransport) {
InetSocketTransportAddress transportAddress = (InetSocketTransportAddress) httpServerTransport.boundAddress().publishAddress();
return host(NetworkAddress.format(transportAddress.address().getAddress())).port(transportAddress.address().getPort());
}
public HttpRequestBuilder port(int port) {
this.port = port;
return this;
}
/**
* Sets the path to send the request to. Url encoding needs to be applied by the caller.
* Use {@link #pathParts(String...)} instead if the path needs to be encoded, part by part.
*/
public HttpRequestBuilder path(String path) {
this.path = path;
return this;
}
/**
* Sets the path by providing the different parts (without slashes), which will be properly encoded.
*/
public HttpRequestBuilder pathParts(String... path) {
//encode rules for path and query string parameters are different. We use URI to encode the path, and URLEncoder for each query string parameter (see addParam).
//We need to encode each path part separately though, as each one might contain slashes that need to be escaped, which needs to be done manually.
if (path.length == 0) {
this.path = "/";
return this;
}
StringBuilder finalPath = new StringBuilder();
for (String pathPart : path) {
try {
finalPath.append('/');
// We append "/" to the path part to handle parts that start with - or other invalid characters
URI uri = new URI(null, null, null, -1, "/" + pathPart, null, null);
//manually escape any slash that each part may contain
finalPath.append(uri.getRawPath().substring(1).replaceAll("/", "%2F"));
} catch(URISyntaxException e) {
throw new RuntimeException("unable to build uri", e);
}
}
this.path = finalPath.toString();
return this;
}
public HttpRequestBuilder addParam(String name, String value) {
try {
this.params.put(name, URLEncoder.encode(value, "utf-8"));
return this;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public HttpRequestBuilder addHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public HttpRequestBuilder addHeader(String name, String value) {
this.headers.put(name, value);
return this;
}
public HttpRequestBuilder protocol(String protocol) {
this.protocol = protocol;
return this;
}
public HttpRequestBuilder method(String method) {
this.method = method;
return this;
}
public HttpRequestBuilder body(String body) {
if (Strings.hasLength(body)) {
this.body = body;
}
return this;
}
public HttpResponse execute() throws IOException {
HttpUriRequest httpUriRequest = buildRequest();
if (logger.isTraceEnabled()) {
StringBuilder stringBuilder = new StringBuilder(httpUriRequest.getMethod()).append(" ").append(httpUriRequest.getURI());
if (Strings.hasLength(body)) {
stringBuilder.append("\n").append(body);
}
logger.trace("sending request \n{}", stringBuilder.toString());
}
for (Map.Entry<String, String> entry : this.headers.entrySet()) {
logger.trace("adding header [{} => {}]", entry.getKey(), entry.getValue());
httpUriRequest.addHeader(entry.getKey(), entry.getValue());
}
try (CloseableHttpResponse closeableHttpResponse = httpClient.execute(httpUriRequest)) {
HttpResponse httpResponse = new HttpResponse(httpUriRequest, closeableHttpResponse);
logger.trace("got response \n{}\n{}", closeableHttpResponse, httpResponse.hasBody() ? httpResponse.getBody() : "");
return httpResponse;
}
}
private HttpUriRequest buildRequest() {
if (HttpGetWithEntity.METHOD_NAME.equalsIgnoreCase(method)) {
return addOptionalBody(new HttpGetWithEntity(buildUri()));
}
if (HttpHead.METHOD_NAME.equalsIgnoreCase(method)) {
checkBodyNotSupported();
return new HttpHead(buildUri());
}
if (HttpOptions.METHOD_NAME.equalsIgnoreCase(method)) {
checkBodyNotSupported();
return new HttpOptions(buildUri());
}
if (HttpDeleteWithEntity.METHOD_NAME.equalsIgnoreCase(method)) {
return addOptionalBody(new HttpDeleteWithEntity(buildUri()));
}
if (HttpPut.METHOD_NAME.equalsIgnoreCase(method)) {
return addOptionalBody(new HttpPut(buildUri()));
}
if (HttpPost.METHOD_NAME.equalsIgnoreCase(method)) {
return addOptionalBody(new HttpPost(buildUri()));
}
throw new UnsupportedOperationException("method [" + method + "] not supported");
}
private URI buildUri() {
StringBuilder uriBuilder = new StringBuilder(protocol).append("://").append(host).append(":").append(port).append(path);
if (params.size() > 0) {
uriBuilder.append("?").append(params.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")));
}
//using this constructor no url encoding happens, as we did everything upfront in addParam and pathPart methods
return URI.create(uriBuilder.toString());
}
private HttpEntityEnclosingRequestBase addOptionalBody(HttpEntityEnclosingRequestBase requestBase) {
if (Strings.hasText(body)) {
requestBase.setEntity(new StringEntity(body, DEFAULT_CHARSET));
}
return requestBase;
}
private void checkBodyNotSupported() {
if (Strings.hasText(body)) {
throw new IllegalArgumentException("request body not supported with head request");
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder(method).append(" '")
.append(host).append(":").append(port).append(path).append("'");
if (!params.isEmpty()) {
stringBuilder.append(", params=").append(params);
}
if (Strings.hasLength(body)) {
stringBuilder.append(", body=\n").append(body);
}
return stringBuilder.toString();
}
}

View File

@ -1,108 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.client.http;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Response obtained from an http request
* Always consumes the whole response body loading it entirely into a string
*/
public class HttpResponse {
private static final ESLogger logger = Loggers.getLogger(HttpResponse.class);
private final HttpUriRequest httpRequest;
private final int statusCode;
private final String reasonPhrase;
private final String body;
private final Map<String, String> headers = new HashMap<>();
HttpResponse(HttpUriRequest httpRequest, CloseableHttpResponse httpResponse) {
this.httpRequest = httpRequest;
this.statusCode = httpResponse.getStatusLine().getStatusCode();
this.reasonPhrase = httpResponse.getStatusLine().getReasonPhrase();
for (Header header : httpResponse.getAllHeaders()) {
this.headers.put(header.getName(), header.getValue());
}
if (httpResponse.getEntity() != null) {
try {
this.body = EntityUtils.toString(httpResponse.getEntity(), HttpRequestBuilder.DEFAULT_CHARSET);
} catch (IOException e) {
EntityUtils.consumeQuietly(httpResponse.getEntity());
throw new RuntimeException(e);
} finally {
try {
httpResponse.close();
} catch (IOException e) {
logger.error("Failed closing response", e);
}
}
} else {
this.body = null;
}
}
public boolean isError() {
return statusCode >= 400;
}
public int getStatusCode() {
return statusCode;
}
public String getReasonPhrase() {
return reasonPhrase;
}
public String getBody() {
return body;
}
public boolean hasBody() {
return body != null;
}
public boolean supportsBody() {
return !HttpHead.METHOD_NAME.equals(httpRequest.getMethod());
}
public Map<String, String> getHeaders() {
return headers;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder(statusCode).append(" ").append(reasonPhrase);
if (hasBody()) {
stringBuilder.append("\n").append(body);
}
return stringBuilder.toString();
}
}

View File

@ -18,13 +18,13 @@
*/
package org.elasticsearch.test.rest.section;
import org.elasticsearch.client.ElasticsearchResponseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.test.rest.RestTestExecutionContext;
import org.elasticsearch.test.rest.client.RestException;
import org.elasticsearch.test.rest.client.RestResponse;
import org.elasticsearch.test.rest.client.RestTestResponse;
import java.io.IOException;
import java.util.HashMap;
@ -89,7 +89,7 @@ public class DoSection implements ExecutableSection {
}
try {
RestResponse restResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
RestTestResponse restTestResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
apiCallSection.getBodies(), apiCallSection.getHeaders());
if (Strings.hasLength(catchParam)) {
String catchStatusCode;
@ -100,16 +100,18 @@ public class DoSection implements ExecutableSection {
} else {
throw new UnsupportedOperationException("catch value [" + catchParam + "] not supported");
}
fail(formatStatusCodeMessage(restResponse, catchStatusCode));
fail(formatStatusCodeMessage(restTestResponse, catchStatusCode));
}
} catch(RestException e) {
} catch(ElasticsearchResponseException e) {
RestTestResponse restTestResponse = new RestTestResponse(e);
if (!Strings.hasLength(catchParam)) {
fail(formatStatusCodeMessage(e.restResponse(), "2xx"));
fail(formatStatusCodeMessage(restTestResponse, "2xx"));
} else if (catches.containsKey(catchParam)) {
assertStatusCode(e.restResponse());
assertStatusCode(restTestResponse);
} else if (catchParam.length() > 2 && catchParam.startsWith("/") && catchParam.endsWith("/")) {
//the text of the error message matches regular expression
assertThat(formatStatusCodeMessage(e.restResponse(), "4xx|5xx"), e.statusCode(), greaterThanOrEqualTo(400));
assertThat(formatStatusCodeMessage(restTestResponse, "4xx|5xx"),
e.getElasticsearchResponse().getStatusLine().getStatusCode(), greaterThanOrEqualTo(400));
Object error = executionContext.response("error");
assertThat("error was expected in the response", error, notNullValue());
//remove delimiters from regex
@ -122,19 +124,19 @@ public class DoSection implements ExecutableSection {
}
}
private void assertStatusCode(RestResponse restResponse) {
private void assertStatusCode(RestTestResponse restTestResponse) {
Tuple<String, org.hamcrest.Matcher<Integer>> stringMatcherTuple = catches.get(catchParam);
assertThat(formatStatusCodeMessage(restResponse, stringMatcherTuple.v1()),
restResponse.getStatusCode(), stringMatcherTuple.v2());
assertThat(formatStatusCodeMessage(restTestResponse, stringMatcherTuple.v1()),
restTestResponse.getStatusCode(), stringMatcherTuple.v2());
}
private String formatStatusCodeMessage(RestResponse restResponse, String expected) {
private String formatStatusCodeMessage(RestTestResponse restTestResponse, String expected) {
String api = apiCallSection.getApi();
if ("raw".equals(api)) {
api += "[method=" + apiCallSection.getParams().get("method") + " path=" + apiCallSection.getParams().get("path") + "]";
}
return "expected [" + expected + "] status code but api [" + api + "] returned ["
+ restResponse.getStatusCode() + " " + restResponse.getReasonPhrase() + "] [" + restResponse.getBodyAsString() + "]";
return "expected [" + expected + "] status code but api [" + api + "] returned [" + restTestResponse.getStatusCode() +
" " + restTestResponse.getReasonPhrase() + "] [" + restTestResponse.getBodyAsString() + "]";
}
private static Map<String, Tuple<String, org.hamcrest.Matcher<Integer>>> catches = new HashMap<>();
@ -145,6 +147,7 @@ public class DoSection implements ExecutableSection {
catches.put("forbidden", tuple("403", equalTo(403)));
catches.put("request_timeout", tuple("408", equalTo(408)));
catches.put("unavailable", tuple("503", equalTo(503)));
catches.put("request", tuple("4xx|5xx", allOf(greaterThanOrEqualTo(400), not(equalTo(404)), not(equalTo(408)), not(equalTo(409)), not(equalTo(403)))));
catches.put("request", tuple("4xx|5xx",
allOf(greaterThanOrEqualTo(400), not(equalTo(404)), not(equalTo(408)), not(equalTo(409)), not(equalTo(403)))));
}
}