/* * 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.client; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.util.EntityUtils; import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; import org.elasticsearch.mocksocket.MockHttpServer; import org.junit.AfterClass; import org.junit.BeforeClass; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.elasticsearch.client.RestClientTestUtil.getAllStatusCodes; import static org.elasticsearch.client.RestClientTestUtil.getHttpMethods; import static org.elasticsearch.client.RestClientTestUtil.randomStatusCode; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * Integration test to check interaction between {@link RestClient} and {@link org.apache.http.client.HttpClient}. * Works against a real http server, one single host. */ //animal-sniffer doesn't like our usage of com.sun.net.httpserver.* classes @IgnoreJRERequirement public class RestClientSingleHostIntegTests extends RestClientTestCase { private static HttpServer httpServer; private static RestClient restClient; private static String pathPrefix; private static Header[] defaultHeaders; @BeforeClass public static void startHttpServer() throws Exception { pathPrefix = randomBoolean() ? "/testPathPrefix/" + randomAsciiOfLengthBetween(1, 5) : ""; httpServer = createHttpServer(); defaultHeaders = RestClientTestUtil.randomHeaders(getRandom(), "Header-default"); restClient = createRestClient(false, true); } private static HttpServer createHttpServer() throws Exception { HttpServer httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); httpServer.start(); //returns a different status code depending on the path for (int statusCode : getAllStatusCodes()) { httpServer.createContext(pathPrefix + "/" + statusCode, new ResponseHandler(statusCode)); } return httpServer; } //animal-sniffer doesn't like our usage of com.sun.net.httpserver.* classes @IgnoreJRERequirement private static class ResponseHandler implements HttpHandler { private final int statusCode; ResponseHandler(int statusCode) { this.statusCode = statusCode; } @Override public void handle(HttpExchange httpExchange) throws IOException { StringBuilder body = new StringBuilder(); try (InputStreamReader reader = new InputStreamReader(httpExchange.getRequestBody(), Consts.UTF_8)) { char[] buffer = new char[256]; int read; while ((read = reader.read(buffer)) != -1) { body.append(buffer, 0, read); } } Headers requestHeaders = httpExchange.getRequestHeaders(); Headers responseHeaders = httpExchange.getResponseHeaders(); for (Map.Entry<String, List<String>> header : requestHeaders.entrySet()) { responseHeaders.put(header.getKey(), header.getValue()); } httpExchange.getRequestBody().close(); httpExchange.sendResponseHeaders(statusCode, body.length() == 0 ? -1 : body.length()); if (body.length() > 0) { try (OutputStream out = httpExchange.getResponseBody()) { out.write(body.toString().getBytes(Consts.UTF_8)); } } httpExchange.close(); } } private static RestClient createRestClient(final boolean useAuth, final boolean usePreemptiveAuth) { // provide the username/password for every request final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("user", "pass")); final RestClientBuilder restClientBuilder = RestClient.builder( new HttpHost(httpServer.getAddress().getHostString(), httpServer.getAddress().getPort())).setDefaultHeaders(defaultHeaders); if (pathPrefix.length() > 0) { // sometimes cut off the leading slash restClientBuilder.setPathPrefix(randomBoolean() ? pathPrefix.substring(1) : pathPrefix); } if (useAuth) { restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(final HttpAsyncClientBuilder httpClientBuilder) { if (usePreemptiveAuth == false) { // disable preemptive auth by ignoring any authcache httpClientBuilder.disableAuthCaching(); } return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } }); } return restClientBuilder.build(); } @AfterClass public static void stopHttpServers() throws IOException { restClient.close(); restClient = null; httpServer.stop(0); httpServer = null; } /** * End to end test for headers. We test it explicitly against a real http client as there are different ways * to set/add headers to the {@link org.apache.http.client.HttpClient}. * Exercises the test http server ability to send back whatever headers it received. */ public void testHeaders() throws IOException { for (String method : getHttpMethods()) { final Set<String> standardHeaders = new HashSet<>(Arrays.asList("Connection", "Host", "User-agent", "Date")); if (method.equals("HEAD") == false) { standardHeaders.add("Content-length"); } final Header[] requestHeaders = RestClientTestUtil.randomHeaders(getRandom(), "Header"); final int statusCode = randomStatusCode(getRandom()); Response esResponse; try { esResponse = restClient.performRequest(method, "/" + statusCode, Collections.<String, String>emptyMap(), requestHeaders); } catch(ResponseException e) { esResponse = e.getResponse(); } assertEquals(method, esResponse.getRequestLine().getMethod()); assertEquals(statusCode, esResponse.getStatusLine().getStatusCode()); assertEquals(pathPrefix + "/" + statusCode, esResponse.getRequestLine().getUri()); assertHeaders(defaultHeaders, requestHeaders, esResponse.getHeaders(), standardHeaders); for (final Header responseHeader : esResponse.getHeaders()) { String name = responseHeader.getName(); if (name.startsWith("Header") == false) { assertTrue("unknown header was returned " + name, standardHeaders.remove(name)); } } assertTrue("some expected standard headers weren't returned: " + standardHeaders, standardHeaders.isEmpty()); } } /** * End to end test for delete with body. We test it explicitly as it is not supported * out of the box by {@link org.apache.http.client.HttpClient}. * Exercises the test http server ability to send back whatever body it received. */ public void testDeleteWithBody() throws IOException { bodyTest("DELETE"); } /** * End to end test for get with body. We test it explicitly as it is not supported * out of the box by {@link org.apache.http.client.HttpClient}. * Exercises the test http server ability to send back whatever body it received. */ public void testGetWithBody() throws IOException { bodyTest("GET"); } /** * Verify that credentials are sent on the first request with preemptive auth enabled (default when provided with credentials). */ public void testPreemptiveAuthEnabled() throws IOException { final String[] methods = { "POST", "PUT", "GET", "DELETE" }; try (RestClient restClient = createRestClient(true, true)) { for (final String method : methods) { final Response response = bodyTest(restClient, method); assertThat(response.getHeader("Authorization"), startsWith("Basic")); } } } /** * Verify that credentials are <em>not</em> sent on the first request with preemptive auth disabled. */ public void testPreemptiveAuthDisabled() throws IOException { final String[] methods = { "POST", "PUT", "GET", "DELETE" }; try (RestClient restClient = createRestClient(true, false)) { for (final String method : methods) { final Response response = bodyTest(restClient, method); assertThat(response.getHeader("Authorization"), nullValue()); } } } private Response bodyTest(final String method) throws IOException { return bodyTest(restClient, method); } private Response bodyTest(final RestClient restClient, final String method) throws IOException { String requestBody = "{ \"field\": \"value\" }"; StringEntity entity = new StringEntity(requestBody, ContentType.APPLICATION_JSON); int statusCode = randomStatusCode(getRandom()); Response esResponse; try { esResponse = restClient.performRequest(method, "/" + statusCode, Collections.<String, String>emptyMap(), entity); } catch(ResponseException e) { esResponse = e.getResponse(); } assertEquals(method, esResponse.getRequestLine().getMethod()); assertEquals(statusCode, esResponse.getStatusLine().getStatusCode()); assertEquals(pathPrefix + "/" + statusCode, esResponse.getRequestLine().getUri()); assertEquals(requestBody, EntityUtils.toString(esResponse.getEntity())); return esResponse; } }