diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 86b8b64f4c..89696767ff 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -56,6 +56,7 @@ dependencies { optional("org.apache.activemq:artemis-jms-client") optional("org.apache.activemq:artemis-jms-server") optional("org.apache.commons:commons-dbcp2") + optional("org.apache.httpcomponents.client5:httpclient5") optional("org.apache.kafka:kafka-streams") optional("org.apache.solr:solr-solrj") optional("org.apache.tomcat.embed:tomcat-embed-core") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java index 1d34e8333e..5da7cd7d80 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ import org.springframework.web.reactive.function.client.WebClient; */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebClient.class) -@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class, ClientHttpConnectorConfiguration.JettyClient.class }) +@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class, ClientHttpConnectorConfiguration.JettyClient.class, + ClientHttpConnectorConfiguration.HttpClient5.class }) public class ClientHttpConnectorAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java index 6fc950ce30..7e65578a57 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.web.reactive.function.client; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -26,6 +28,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.JettyResourceFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; @@ -45,17 +48,17 @@ class ClientHttpConnectorConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(reactor.netty.http.client.HttpClient.class) @ConditionalOnMissingBean(ClientHttpConnector.class) - public static class ReactorNetty { + static class ReactorNetty { @Bean @ConditionalOnMissingBean - public ReactorResourceFactory reactorClientResourceFactory() { + ReactorResourceFactory reactorClientResourceFactory() { return new ReactorResourceFactory(); } @Bean @Lazy - public ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory reactorResourceFactory, + ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory reactorResourceFactory, ObjectProvider mapperProvider) { ReactorNettyHttpClientMapper mapper = mapperProvider.orderedStream() .reduce((before, after) -> (client) -> after.configure(before.configure(client))) @@ -68,17 +71,17 @@ class ClientHttpConnectorConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(org.eclipse.jetty.reactive.client.ReactiveRequest.class) @ConditionalOnMissingBean(ClientHttpConnector.class) - public static class JettyClient { + static class JettyClient { @Bean @ConditionalOnMissingBean - public JettyResourceFactory jettyClientResourceFactory() { + JettyResourceFactory jettyClientResourceFactory() { return new JettyResourceFactory(); } @Bean @Lazy - public JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyResourceFactory) { + JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyResourceFactory) { SslContextFactory sslContextFactory = new SslContextFactory.Client(); HttpClient httpClient = new HttpClient(sslContextFactory); return new JettyClientHttpConnector(httpClient, jettyResourceFactory); @@ -86,4 +89,17 @@ class ClientHttpConnectorConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ HttpAsyncClients.class, AsyncRequestProducer.class }) + @ConditionalOnMissingBean(ClientHttpConnector.class) + static class HttpClient5 { + + @Bean + @Lazy + HttpComponentsClientHttpConnector httpComponentsClientHttpConnector() { + return new HttpComponentsClientHttpConnector(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java index 2fec0a66a1..4826257c9b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,13 @@ package org.springframework.boot.autoconfigure.web.reactive.function.client; +import org.eclipse.jetty.reactive.client.ReactiveRequest; import org.junit.jupiter.api.Test; +import reactor.netty.http.client.HttpClient; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.context.annotation.Bean; @@ -46,7 +49,7 @@ class ClientHttpConnectorAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class)); @Test - void shouldCreateResourcesLazily() { + void whenReactorIsAvailableThenReactorBeansAreDefined() { this.contextRunner.run((context) -> { BeanDefinition customizerDefinition = context.getBeanFactory() .getBeanDefinition("clientConnectorCustomizer"); @@ -54,9 +57,35 @@ class ClientHttpConnectorAutoConfigurationTests { BeanDefinition connectorDefinition = context.getBeanFactory() .getBeanDefinition("reactorClientHttpConnector"); assertThat(connectorDefinition.isLazyInit()).isTrue(); + assertThat(context).hasBean("reactorClientResourceFactory"); }); } + @Test + void whenReactorIsUnavailableThenJettyBeansAreDefined() { + this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class)).run((context) -> { + BeanDefinition customizerDefinition = context.getBeanFactory() + .getBeanDefinition("clientConnectorCustomizer"); + assertThat(customizerDefinition.isLazyInit()).isTrue(); + BeanDefinition connectorDefinition = context.getBeanFactory().getBeanDefinition("jettyClientHttpConnector"); + assertThat(connectorDefinition.isLazyInit()).isTrue(); + assertThat(context).hasBean("jettyClientResourceFactory"); + }); + } + + @Test + void whenReactorAndJettyAreUnavailableThenHttpClientBeansAreDefined() { + this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class, ReactiveRequest.class)) + .run((context) -> { + BeanDefinition customizerDefinition = context.getBeanFactory() + .getBeanDefinition("clientConnectorCustomizer"); + assertThat(customizerDefinition.isLazyInit()).isTrue(); + BeanDefinition connectorDefinition = context.getBeanFactory() + .getBeanDefinition("httpComponentsClientHttpConnector"); + assertThat(connectorDefinition.isLazyInit()).isTrue(); + }); + } + @Test void shouldCreateHttpClientBeans() { this.contextRunner.run((context) -> { diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 721405dff9..2b25760fc6 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -483,14 +483,33 @@ bom { ] } } + library("HttpClient5", "5.0.3") { + group("org.apache.httpcomponents.client5") { + modules = [ + "httpclient5", + "httpclient5-cache", + "httpclient5-fluent", + "httpclient5-win", + ] + } + } library("HttpCore", "4.4.14") { - group("org.apache.httpcomponents") { + group("org.apache.httpcomponents.core5") { modules = [ "httpcore", "httpcore-nio" ] } } + library("HttpCore5", "5.1") { + group("org.apache.httpcomponents") { + modules = [ + "httpcore5", + "httpcore5-h2", + "httpcore5-reactive" + ] + } + } library("Infinispan", "12.0.2.Final") { group("org.infinispan") { imports = [