org.elasticsearch.xpack.watcher.common.http.HttpClientTests.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.xpack.watcher.common.http.HttpClientTests.java

Source

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.xpack.watcher.common.http;

import com.carrotsearch.randomizedtesting.generators.RandomStrings;
import org.apache.http.HttpHeaders;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.mocksocket.MockServerSocket;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.http.MockResponse;
import org.elasticsearch.test.http.MockWebServer;
import org.elasticsearch.test.junit.annotations.Network;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.core.ssl.TestsSSLService;
import org.elasticsearch.xpack.core.ssl.VerificationMode;
import org.elasticsearch.xpack.watcher.common.http.auth.HttpAuthRegistry;
import org.elasticsearch.xpack.watcher.common.http.auth.basic.BasicAuth;
import org.elasticsearch.xpack.watcher.common.http.auth.basic.BasicAuthFactory;
import org.junit.After;
import org.junit.Before;

import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.core.Is.is;

public class HttpClientTests extends ESTestCase {

    private MockWebServer webServer = new MockWebServer();
    private HttpClient httpClient;
    private HttpAuthRegistry authRegistry;
    private Environment environment = TestEnvironment
            .newEnvironment(Settings.builder().put("path.home", createTempDir()).build());

    @Before
    public void init() throws Exception {
        authRegistry = new HttpAuthRegistry(singletonMap(BasicAuth.TYPE, new BasicAuthFactory(null)));
        webServer.start();
        httpClient = new HttpClient(Settings.EMPTY, authRegistry,
                new SSLService(environment.settings(), environment));
    }

    @After
    public void shutdown() throws Exception {
        webServer.close();
        httpClient.close();
    }

    public void testBasics() throws Exception {
        int responseCode = randomIntBetween(200, 203);
        String body = randomAlphaOfLengthBetween(2, 8096);
        webServer.enqueue(new MockResponse().setResponseCode(responseCode).setBody(body));

        HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                .method(HttpMethod.POST).path("/" + randomAlphaOfLength(5));

        String paramKey = randomAlphaOfLength(3);
        String paramValue = randomAlphaOfLength(3);
        requestBuilder.setParam(paramKey, paramValue);

        // Certain headers keys like via and host are illegal and the jdk http client ignores those, so lets
        // prepend all keys with `_`, so we don't run into a failure because randomly a restricted header was used:
        String headerKey = "_" + randomAlphaOfLength(3);
        String headerValue = randomAlphaOfLength(3);
        requestBuilder.setHeader(headerKey, headerValue);

        requestBuilder.body(randomAlphaOfLength(5));
        HttpRequest request = requestBuilder.build();

        HttpResponse response = httpClient.execute(request);
        assertThat(response.status(), equalTo(responseCode));
        assertThat(response.body().utf8ToString(), equalTo(body));
        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getPath(), equalTo(request.path()));
        assertThat(webServer.requests().get(0).getUri().getQuery(), equalTo(paramKey + "=" + paramValue));
        assertThat(webServer.requests().get(0).getHeader(headerKey), equalTo(headerValue));
    }

    public void testNoQueryString() throws Exception {
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
        HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                .method(HttpMethod.GET).path("/test");

        HttpResponse response = httpClient.execute(requestBuilder.build());
        assertThat(response.status(), equalTo(200));
        assertThat(response.body().utf8ToString(), equalTo("body"));

        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getPath(), is("/test"));
        assertThat(webServer.requests().get(0).getBody(), is(nullValue()));
    }

    public void testUrlEncodingWithQueryStrings() throws Exception {
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
        HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                .method(HttpMethod.GET).path("/test").setParam("key", "value 123:123");

        HttpResponse response = httpClient.execute(requestBuilder.build());
        assertThat(response.status(), equalTo(200));
        assertThat(response.body().utf8ToString(), equalTo("body"));

        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getPath(), is("/test"));
        assertThat(webServer.requests().get(0).getUri().getRawQuery(), is("key=value+123%3A123"));
        assertThat(webServer.requests().get(0).getBody(), is(nullValue()));
    }

    public void testBasicAuth() throws Exception {
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
        HttpRequest.Builder request = HttpRequest.builder("localhost", webServer.getPort()).method(HttpMethod.POST)
                .path("/test").auth(new BasicAuth("user", "pass".toCharArray())).body("body");
        HttpResponse response = httpClient.execute(request.build());
        assertThat(response.status(), equalTo(200));
        assertThat(response.body().utf8ToString(), equalTo("body"));

        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getPath(), is("/test"));
        assertThat(webServer.requests().get(0).getHeader("Authorization"), is("Basic dXNlcjpwYXNz"));
    }

    public void testNoPathSpecified() throws Exception {
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("doesntmatter"));
        HttpRequest.Builder request = HttpRequest.builder("localhost", webServer.getPort()).method(HttpMethod.GET);
        httpClient.execute(request.build());

        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getPath(), is("/"));
    }

    public void testHttps() throws Exception {
        Path resource = getDataPath("/org/elasticsearch/xpack/security/keystore/truststore-testnode-only.jks");
        MockSecureSettings secureSettings = new MockSecureSettings();
        Settings settings;
        if (randomBoolean()) {
            secureSettings.setString("xpack.http.ssl.truststore.secure_password", "truststore-testnode-only");
            settings = Settings.builder().put("xpack.http.ssl.truststore.path", resource.toString())
                    .setSecureSettings(secureSettings).build();
        } else {
            secureSettings.setString("xpack.ssl.truststore.secure_password", "truststore-testnode-only");
            settings = Settings.builder().put("xpack.ssl.truststore.path", resource.toString())
                    .setSecureSettings(secureSettings).build();
        }
        try (HttpClient client = new HttpClient(settings, authRegistry, new SSLService(settings, environment))) {
            secureSettings = new MockSecureSettings();
            // We can't use the client created above for the server since it is only a truststore
            secureSettings.setString("xpack.ssl.keystore.secure_password", "testnode");
            Settings settings2 = Settings.builder()
                    .put("xpack.ssl.keystore.path",
                            getDataPath("/org/elasticsearch/xpack/security/keystore/testnode.jks"))
                    .setSecureSettings(secureSettings).build();

            TestsSSLService sslService = new TestsSSLService(settings2, environment);
            testSslMockWebserver(client, sslService.sslContext(), false);
        }
    }

    public void testHttpsDisableHostnameVerification() throws Exception {
        Path resource = getDataPath(
                "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-no-subjaltname.jks");
        Settings settings;
        if (randomBoolean()) {
            MockSecureSettings secureSettings = new MockSecureSettings();
            secureSettings.setString("xpack.http.ssl.truststore.secure_password", "testnode-no-subjaltname");
            settings = Settings.builder().put("xpack.http.ssl.truststore.path", resource.toString())
                    .put("xpack.http.ssl.verification_mode",
                            randomFrom(VerificationMode.NONE, VerificationMode.CERTIFICATE))
                    .setSecureSettings(secureSettings).build();
        } else {
            MockSecureSettings secureSettings = new MockSecureSettings();
            secureSettings.setString("xpack.ssl.truststore.secure_password", "testnode-no-subjaltname");
            settings = Settings.builder().put("xpack.ssl.truststore.path", resource.toString())
                    .put("xpack.ssl.verification_mode",
                            randomFrom(VerificationMode.NONE, VerificationMode.CERTIFICATE))
                    .setSecureSettings(secureSettings).build();
        }
        try (HttpClient client = new HttpClient(settings, authRegistry, new SSLService(settings, environment))) {
            MockSecureSettings secureSettings = new MockSecureSettings();
            // We can't use the client created above for the server since it only defines a truststore
            secureSettings.setString("xpack.ssl.keystore.secure_password", "testnode-no-subjaltname");
            Settings settings2 = Settings.builder().put("xpack.ssl.keystore.path", getDataPath(
                    "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-no-subjaltname.jks"))
                    .setSecureSettings(secureSettings).build();

            TestsSSLService sslService = new TestsSSLService(settings2, environment);
            testSslMockWebserver(client, sslService.sslContext(), false);
        }
    }

    public void testHttpsClientAuth() throws Exception {
        Path resource = getDataPath("/org/elasticsearch/xpack/security/keystore/testnode.jks");
        MockSecureSettings secureSettings = new MockSecureSettings();
        secureSettings.setString("xpack.ssl.keystore.secure_password", "testnode");
        Settings settings = Settings.builder().put("xpack.ssl.keystore.path", resource.toString())
                .setSecureSettings(secureSettings).build();

        TestsSSLService sslService = new TestsSSLService(settings, environment);
        try (HttpClient client = new HttpClient(settings, authRegistry, sslService)) {
            testSslMockWebserver(client, sslService.sslContext(), true);
        }
    }

    private void testSslMockWebserver(HttpClient client, SSLContext sslContext, boolean needClientAuth)
            throws IOException {
        try (MockWebServer mockWebServer = new MockWebServer(sslContext, needClientAuth)) {
            mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
            mockWebServer.start();

            HttpRequest.Builder request = HttpRequest.builder("localhost", mockWebServer.getPort())
                    .scheme(Scheme.HTTPS).path("/test");
            HttpResponse response = client.execute(request.build());
            assertThat(response.status(), equalTo(200));
            assertThat(response.body().utf8ToString(), equalTo("body"));

            assertThat(mockWebServer.requests(), hasSize(1));
            assertThat(mockWebServer.requests().get(0).getUri().getPath(), is("/test"));
        }
    }

    public void testHttpResponseWithAnyStatusCodeCanReturnBody() throws Exception {
        int statusCode = randomFrom(200, 201, 400, 401, 403, 404, 405, 409, 413, 429, 500, 503);
        String body = RandomStrings.randomAsciiOfLength(random(), 100);
        boolean hasBody = usually();
        MockResponse mockResponse = new MockResponse().setResponseCode(statusCode);
        if (hasBody) {
            mockResponse.setBody(body);
        }
        webServer.enqueue(mockResponse);
        HttpRequest.Builder request = HttpRequest.builder("localhost", webServer.getPort()).method(HttpMethod.POST)
                .path("/test").auth(new BasicAuth("user", "pass".toCharArray())).body("body")
                .connectionTimeout(TimeValue.timeValueMillis(500)).readTimeout(TimeValue.timeValueMillis(500));
        HttpResponse response = httpClient.execute(request.build());
        assertThat(response.status(), equalTo(statusCode));
        assertThat(response.hasContent(), is(hasBody));
        if (hasBody) {
            assertThat(response.body().utf8ToString(), is(body));
        }
    }

    @Network
    public void testHttpsWithoutTruststore() throws Exception {
        try (HttpClient client = new HttpClient(Settings.EMPTY, authRegistry,
                new SSLService(Settings.EMPTY, environment))) {
            // Known server with a valid cert from a commercial CA
            HttpRequest.Builder request = HttpRequest.builder("www.elastic.co", 443).scheme(Scheme.HTTPS);
            HttpResponse response = client.execute(request.build());
            assertThat(response.status(), equalTo(200));
            assertThat(response.hasContent(), is(true));
            assertThat(response.body(), notNullValue());
        }
    }

    public void testThatProxyCanBeConfigured() throws Exception {
        // this test fakes a proxy server that sends a response instead of forwarding it to the mock web server
        try (MockWebServer proxyServer = new MockWebServer()) {
            proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody("fullProxiedContent"));
            proxyServer.start();
            Settings settings = Settings.builder().put(HttpSettings.PROXY_HOST.getKey(), "localhost")
                    .put(HttpSettings.PROXY_PORT.getKey(), proxyServer.getPort()).build();

            HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                    .method(HttpMethod.GET).path("/");

            try (HttpClient client = new HttpClient(settings, authRegistry,
                    new SSLService(settings, environment))) {
                HttpResponse response = client.execute(requestBuilder.build());
                assertThat(response.status(), equalTo(200));
                assertThat(response.body().utf8ToString(), equalTo("fullProxiedContent"));
            }

            // ensure we hit the proxyServer and not the webserver
            assertThat(webServer.requests(), hasSize(0));
            assertThat(proxyServer.requests(), hasSize(1));
        }
    }

    public void testSetProxy() throws Exception {
        HttpProxy localhostHttpProxy = new HttpProxy("localhost", 1234, Scheme.HTTP);
        RequestConfig.Builder config = RequestConfig.custom();

        // no proxy configured at all
        HttpClient.setProxy(config, HttpRequest.builder().fromUrl("https://elastic.co").build(),
                HttpProxy.NO_PROXY);
        assertThat(config.build().getProxy(), is(nullValue()));

        // no system wide proxy configured, proxy in request
        config = RequestConfig.custom();
        HttpClient.setProxy(config, HttpRequest.builder().fromUrl("https://elastic.co")
                .proxy(new HttpProxy("localhost", 23456)).build(), HttpProxy.NO_PROXY);
        assertThat(config.build().getProxy().toString(), is("http://localhost:23456"));

        // system wide proxy configured, no proxy in request
        config = RequestConfig.custom();
        HttpClient.setProxy(config, HttpRequest.builder().fromUrl("https://elastic.co").build(),
                localhostHttpProxy);
        assertThat(config.build().getProxy().toString(), is("http://localhost:1234"));

        // proxy in request, no system wide proxy configured. request
        config = RequestConfig.custom();
        HttpClient.setProxy(config, HttpRequest.builder().fromUrl("https://elastic.co")
                .proxy(new HttpProxy("localhost", 23456, Scheme.HTTP)).build(), HttpProxy.NO_PROXY);
        assertThat(config.build().getProxy().toString(), is("http://localhost:23456"));

        // proxy in request, system wide proxy configured. request wins
        config = RequestConfig.custom();
        HttpClient.setProxy(config, HttpRequest.builder().fromUrl("http://elastic.co")
                .proxy(new HttpProxy("localhost", 23456, Scheme.HTTPS)).build(), localhostHttpProxy);
        assertThat(config.build().getProxy().toString(), is("https://localhost:23456"));
    }

    public void testProxyCanHaveDifferentSchemeThanRequest() throws Exception {
        // this test fakes a proxy server that sends a response instead of forwarding it to the mock web server
        // on top of that the proxy request is HTTPS but the real request is HTTP only
        MockSecureSettings serverSecureSettings = new MockSecureSettings();
        // We can't use the client created above for the server since it is only a truststore
        serverSecureSettings.setString("xpack.ssl.keystore.secure_password", "testnode");
        Settings serverSettings = Settings.builder()
                .put("xpack.ssl.keystore.path",
                        getDataPath("/org/elasticsearch/xpack/security/keystore/testnode.jks"))
                .setSecureSettings(serverSecureSettings).build();
        TestsSSLService sslService = new TestsSSLService(serverSettings, environment);

        try (MockWebServer proxyServer = new MockWebServer(sslService.sslContext(), false)) {
            proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody("fullProxiedContent"));
            proxyServer.start();

            Path resource = getDataPath("/org/elasticsearch/xpack/security/keystore/truststore-testnode-only.jks");
            MockSecureSettings secureSettings = new MockSecureSettings();
            secureSettings.setString("xpack.http.ssl.truststore.secure_password", "truststore-testnode-only");
            Settings settings = Settings.builder().put(HttpSettings.PROXY_HOST.getKey(), "localhost")
                    .put(HttpSettings.PROXY_PORT.getKey(), proxyServer.getPort())
                    .put(HttpSettings.PROXY_SCHEME.getKey(), "https")
                    .put("xpack.http.ssl.truststore.path", resource.toString()).setSecureSettings(secureSettings)
                    .build();

            HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                    .method(HttpMethod.GET).scheme(Scheme.HTTP).path("/");

            try (HttpClient client = new HttpClient(settings, authRegistry,
                    new SSLService(settings, environment))) {
                HttpResponse response = client.execute(requestBuilder.build());
                assertThat(response.status(), equalTo(200));
                assertThat(response.body().utf8ToString(), equalTo("fullProxiedContent"));
            }

            // ensure we hit the proxyServer and not the webserver
            assertThat(webServer.requests(), hasSize(0));
            assertThat(proxyServer.requests(), hasSize(1));
        }
    }

    public void testThatProxyCanBeOverriddenByRequest() throws Exception {
        // this test fakes a proxy server that sends a response instead of forwarding it to the mock web server
        try (MockWebServer proxyServer = new MockWebServer()) {
            proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody("fullProxiedContent"));
            proxyServer.start();
            Settings settings = Settings.builder().put(HttpSettings.PROXY_HOST.getKey(), "localhost")
                    .put(HttpSettings.PROXY_PORT.getKey(), proxyServer.getPort() + 1)
                    .put(HttpSettings.PROXY_HOST.getKey(), "https").build();

            HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                    .method(HttpMethod.GET).proxy(new HttpProxy("localhost", proxyServer.getPort(), Scheme.HTTP))
                    .path("/");

            try (HttpClient client = new HttpClient(settings, authRegistry,
                    new SSLService(settings, environment))) {
                HttpResponse response = client.execute(requestBuilder.build());
                assertThat(response.status(), equalTo(200));
                assertThat(response.body().utf8ToString(), equalTo("fullProxiedContent"));
            }

            // ensure we hit the proxyServer and not the webserver
            assertThat(webServer.requests(), hasSize(0));
            assertThat(proxyServer.requests(), hasSize(1));
        }
    }

    public void testThatProxyConfigurationRequiresHostAndPort() {
        Settings.Builder settings = Settings.builder();
        if (randomBoolean()) {
            settings.put(HttpSettings.PROXY_HOST.getKey(), "localhost");
        } else {
            settings.put(HttpSettings.PROXY_PORT.getKey(), 8080);
        }

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
                () -> new HttpClient(settings.build(), authRegistry,
                        new SSLService(settings.build(), environment)));
        assertThat(e.getMessage(), containsString(
                "HTTP proxy requires both settings: [xpack.http.proxy.host] and [xpack.http.proxy.port]"));
    }

    public void testThatUrlPathIsNotEncoded() throws Exception {
        // %2F is a slash that needs to be encoded to not be misinterpreted as a path
        String path = "/%3Clogstash-%7Bnow%2Fd%7D%3E/_search";
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("foo"));
        HttpRequest request;
        if (randomBoolean()) {
            request = HttpRequest.builder("localhost", webServer.getPort()).path(path).build();
        } else {
            // ensure that fromUrl acts the same way than the above builder
            request = HttpRequest.builder()
                    .fromUrl(String.format(Locale.ROOT, "http://localhost:%s%s", webServer.getPort(), path))
                    .build();
        }
        httpClient.execute(request);

        assertThat(webServer.requests(), hasSize(1));

        // under no circumstances have a double encode of %2F => %25 (percent sign)
        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getRawPath(), not(containsString("%25")));
        assertThat(webServer.requests().get(0).getUri().getPath(), is("/<logstash-{now/d}>/_search"));
    }

    public void testThatDuplicateHeaderKeysAreReturned() throws Exception {
        MockResponse mockResponse = new MockResponse().setResponseCode(200).setBody("foo").addHeader("foo", "bar")
                .addHeader("foo", "baz").addHeader("Content-Length", "3");
        webServer.enqueue(mockResponse);

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/").build();
        HttpResponse httpResponse = httpClient.execute(request);

        assertThat(webServer.requests(), hasSize(1));

        assertThat(httpResponse.headers(), hasKey("foo"));
        assertThat(httpResponse.headers().get("foo"), containsInAnyOrder("bar", "baz"));
    }

    // finally fixing https://github.com/elastic/x-plugins/issues/1141 - yay! Fixed due to switching to apache http client internally!
    public void testThatClientTakesTimeoutsIntoAccountAfterHeadersAreSent() throws Exception {
        webServer.enqueue(
                new MockResponse().setResponseCode(200).setBody("foo").setBodyDelay(TimeValue.timeValueSeconds(2)));

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/foo")
                .method(HttpMethod.POST).body("foo").connectionTimeout(TimeValue.timeValueMillis(500))
                .readTimeout(TimeValue.timeValueMillis(500)).build();
        SocketTimeoutException e = expectThrows(SocketTimeoutException.class, () -> httpClient.execute(request));
        assertThat(e.getMessage(), is("Read timed out"));
    }

    public void testThatHttpClientFailsOnNonHttpResponse() throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        AtomicReference<Exception> hasExceptionHappened = new AtomicReference();
        try (ServerSocket serverSocket = new MockServerSocket(0, 50, InetAddress.getByName("localhost"))) {
            executor.execute(() -> {
                try (Socket socket = serverSocket.accept()) {
                    BufferedReader in = new BufferedReader(
                            new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                    in.readLine();
                    socket.getOutputStream().write("This is not a HTTP response".getBytes(StandardCharsets.UTF_8));
                    socket.getOutputStream().flush();
                } catch (Exception e) {
                    hasExceptionHappened.set(e);
                    logger.error((Supplier<?>) () -> new ParameterizedMessage("Error in writing non HTTP response"),
                            e);
                }
            });
            HttpRequest request = HttpRequest.builder("localhost", serverSocket.getLocalPort()).path("/").build();
            expectThrows(ClientProtocolException.class, () -> httpClient.execute(request));
            assertThat("A server side exception occured, but shouldn't", hasExceptionHappened.get(),
                    is(nullValue()));
        } finally {
            terminate(executor);
        }
    }

    public void testNoContentResponse() throws Exception {
        int noContentStatusCode = 204;
        webServer.enqueue(new MockResponse().setResponseCode(noContentStatusCode));
        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/foo").build();
        HttpResponse response = httpClient.execute(request);
        assertThat(response.status(), is(noContentStatusCode));
        assertThat(response.body(), is(nullValue()));
    }

    public void testMaxHttpResponseSize() throws Exception {
        int randomBytesLength = scaledRandomIntBetween(2, 100);
        String data = randomAlphaOfLength(randomBytesLength);
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody(data));

        Settings settings = Settings.builder().put(HttpSettings.MAX_HTTP_RESPONSE_SIZE.getKey(),
                new ByteSizeValue(randomBytesLength - 1, ByteSizeUnit.BYTES)).build();

        HttpRequest.Builder requestBuilder = HttpRequest.builder("localhost", webServer.getPort())
                .method(HttpMethod.GET).path("/");

        try (HttpClient client = new HttpClient(settings, authRegistry,
                new SSLService(environment.settings(), environment))) {
            IOException e = expectThrows(IOException.class, () -> client.execute(requestBuilder.build()));
            assertThat(e.getMessage(), startsWith("Maximum limit of"));
        }
    }

    public void testThatGetRedirectIsFollowed() throws Exception {
        String redirectUrl = "http://" + webServer.getHostName() + ":" + webServer.getPort() + "/foo";
        webServer.enqueue(new MockResponse().setResponseCode(302).addHeader("Location", redirectUrl));
        HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.HEAD);

        if (method == HttpMethod.GET) {
            webServer.enqueue(new MockResponse().setResponseCode(200).setBody("shouldBeRead"));
        } else if (method == HttpMethod.HEAD) {
            webServer.enqueue(new MockResponse().setResponseCode(200));
        }

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/").method(method)
                .build();
        HttpResponse response = httpClient.execute(request);

        assertThat(webServer.requests(), hasSize(2));
        if (method == HttpMethod.GET) {
            assertThat(response.body().utf8ToString(), is("shouldBeRead"));
        } else if (method == HttpMethod.HEAD) {
            assertThat(response.body(), is(nullValue()));
        }
    }

    // not allowed by RFC, only allowed for GET or HEAD
    public void testThatPostRedirectIsNotFollowed() throws Exception {
        String redirectUrl = "http://" + webServer.getHostName() + ":" + webServer.getPort() + "/foo";
        webServer.enqueue(new MockResponse().setResponseCode(302).addHeader("Location", redirectUrl));
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("shouldNeverBeRead"));

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/")
                .method(HttpMethod.POST).build();
        HttpResponse response = httpClient.execute(request);
        assertThat(response.body(), is(nullValue()));
        assertThat(webServer.requests(), hasSize(1));
    }

    public void testThatBodyWithUTF8Content() throws Exception {
        String body = "title ?????";
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody(body));

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("/")
                .setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()).body(body).build();
        HttpResponse response = httpClient.execute(request);
        assertThat(response.body().utf8ToString(), is(body));

        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getHeader(HttpHeaders.CONTENT_TYPE),
                is(XContentType.JSON.mediaType()));
        assertThat(webServer.requests().get(0).getBody(), is(body));
    }

    public void testThatUrlDoesNotContainQuestionMarkAtTheEnd() throws Exception {
        webServer.enqueue(new MockResponse().setResponseCode(200).setBody("whatever"));

        HttpRequest request = HttpRequest.builder("localhost", webServer.getPort()).path("foo").build();
        httpClient.execute(request);
        assertThat(webServer.requests(), hasSize(1));
        assertThat(webServer.requests().get(0).getUri().getRawPath(), is("/foo"));
    }
}