Polish "Support Jetty in ClientHttpRequestFactories"

See gh-36116
This commit is contained in:
Andy Wilkinson 2023-07-04 13:57:46 +01:00
parent 7ceece3d3d
commit c3e2c9d684
7 changed files with 38 additions and 27 deletions

View File

@ -25,6 +25,7 @@ In order of preference, the following clients are supported:
. Apache HttpClient . Apache HttpClient
. OkHttp . OkHttp
. Jetty HttpClient
. Simple JDK client (`HttpURLConnection`) . Simple JDK client (`HttpURLConnection`)
If multiple clients are available on the classpath, the most preferred client will be used. If multiple clients are available on the classpath, the most preferred client will be used.

View File

@ -241,14 +241,12 @@ public final class ClientHttpRequestFactories {
private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) { private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) {
if (sslBundle != null) { if (sslBundle != null) {
SSLContext sslContext = sslBundle.createSslContext(); SSLContext sslContext = sslBundle.createSslContext();
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setSslContext(sslContext); sslContextFactory.setSslContext(sslContext);
ClientConnector connector = new ClientConnector(); ClientConnector connector = new ClientConnector();
connector.setSslContextFactory(sslContextFactory); connector.setSslContextFactory(sslContextFactory);
org.eclipse.jetty.client.HttpClient httpClient = org.eclipse.jetty.client.HttpClient httpClient = new org.eclipse.jetty.client.HttpClient(
new org.eclipse.jetty.client.HttpClient(new HttpClientTransportDynamic(connector)); new HttpClientTransportDynamic(connector));
return new JettyClientHttpRequestFactory(httpClient); return new JettyClientHttpRequestFactory(httpClient);
} }
return new JettyClientHttpRequestFactory(); return new JettyClientHttpRequestFactory();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -59,6 +60,10 @@ class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar {
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS)); typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS));
registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class); registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class);
}); });
hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.JETTY_CLIENT_CLASS, (typeHint) -> {
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.JETTY_CLIENT_CLASS));
registerReflectionHints(hints, JettyClientHttpRequestFactory.class, long.class);
});
hints.registerType(SimpleClientHttpRequestFactory.class, (typeHint) -> { hints.registerType(SimpleClientHttpRequestFactory.class, (typeHint) -> {
typeHint.onReachableType(HttpURLConnection.class); typeHint.onReachableType(HttpURLConnection.class);
registerReflectionHints(hints, SimpleClientHttpRequestFactory.class); registerReflectionHints(hints, SimpleClientHttpRequestFactory.class);
@ -67,8 +72,13 @@ class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar {
private void registerReflectionHints(ReflectionHints hints, private void registerReflectionHints(ReflectionHints hints,
Class<? extends ClientHttpRequestFactory> requestFactoryType) { Class<? extends ClientHttpRequestFactory> requestFactoryType) {
registerReflectionHints(hints, requestFactoryType, int.class);
}
private void registerReflectionHints(ReflectionHints hints,
Class<? extends ClientHttpRequestFactory> requestFactoryType, Class<?> readTimeoutType) {
registerMethod(hints, requestFactoryType, "setConnectTimeout", int.class); registerMethod(hints, requestFactoryType, "setConnectTimeout", int.class);
registerMethod(hints, requestFactoryType, "setReadTimeout", int.class); registerMethod(hints, requestFactoryType, "setReadTimeout", readTimeoutType);
} }
private void registerMethod(ReflectionHints hints, Class<? extends ClientHttpRequestFactory> requestFactoryType, private void registerMethod(ReflectionHints hints, Class<? extends ClientHttpRequestFactory> requestFactoryType,

View File

@ -23,8 +23,7 @@ import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
/** /**
* Tests for {@link ClientHttpRequestFactories} when Jetty is the * Tests for {@link ClientHttpRequestFactories} when Jetty is the predominant HTTP client.
* predominant HTTP client.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */
@ -38,14 +37,12 @@ class ClientHttpRequestFactoriesJettyTests
@Override @Override
protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) { protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
HttpClient client = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient"); return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();
return client.getConnectTimeout();
} }
@Override @Override
@SuppressWarnings("unchecked")
protected long readTimeout(JettyClientHttpRequestFactory requestFactory) { protected long readTimeout(JettyClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout"); return (long) ReflectionTestUtils.getField(requestFactory, "readTimeout");
} }
} }

View File

@ -26,6 +26,7 @@ import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -73,6 +74,17 @@ class ClientHttpRequestFactoriesRuntimeHintsTests {
assertThat(hints.reflection().getTypeHint(OkHttp3ClientHttpRequestFactory.class).methods()).hasSize(2); assertThat(hints.reflection().getTypeHint(OkHttp3ClientHttpRequestFactory.class).methods()).hasSize(2);
} }
@Test
void shouldRegisterJettyClientHints() {
RuntimeHints hints = new RuntimeHints();
new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(method(JettyClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(method(JettyClientHttpRequestFactory.class, "setReadTimeout", long.class)))
.accepts(hints);
}
@Test @Test
void shouldRegisterSimpleHttpHints() { void shouldRegisterSimpleHttpHints() {
RuntimeHints hints = new RuntimeHints(); RuntimeHints hints = new RuntimeHints();

View File

@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
@ClassPathExclusions({ "httpclient5-*.jar", "okhttp*.jar" }) @ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" })
class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests { class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests {
private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder();

View File

@ -16,10 +16,6 @@
package smoketest.jetty; package smoketest.jetty;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import smoketest.jetty.util.RandomStringUtil; import smoketest.jetty.util.RandomStringUtil;
@ -35,7 +31,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -65,16 +60,14 @@ class SampleJettyApplicationTests {
} }
@Test @Test
void testCompression() throws Exception { void testCompression() {
HttpHeaders requestHeaders = new HttpHeaders(); // Jetty HttpClient sends Accept-Encoding: gzip by default
requestHeaders.set("Accept-Encoding", "gzip"); ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
HttpEntity<?> requestEntity = new HttpEntity<>(requestHeaders);
ResponseEntity<byte[]> entity = this.restTemplate.exchange("/", HttpMethod.GET, requestEntity, byte[].class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isNotNull(); assertThat(entity.getBody()).isEqualTo("Hello World");
try (GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(entity.getBody()))) { // Jetty HttpClient decodes gzip reponses automatically
assertThat(StreamUtils.copyToString(inflater, StandardCharsets.UTF_8)).isEqualTo("Hello World"); // Check that we received a gzip-encoded response
} assertThat(entity.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING)).isEqualTo("gzip");
} }
@Test @Test