Skip to content

Commit 91f695a

Browse files
committed
feat(http-client): add RestClient timeout config, WebClient support pending Boot 3.5+
1 parent c0bc623 commit 91f695a

File tree

6 files changed

+295
-0
lines changed

6 files changed

+295
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>org.springframework.ai</groupId>
8+
<artifactId>spring-ai-parent</artifactId>
9+
<version>1.0.0-SNAPSHOT</version>
10+
<relativePath>../../../../../pom.xml</relativePath>
11+
</parent>
12+
<artifactId>spring-ai-http-client</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Spring AI Http Client Auto Configuration</name>
15+
<description>Spring AI Http Client Auto Configuration</description>
16+
<url>https://github.com/spring-projects/spring-ai</url>
17+
18+
<scm>
19+
<url>https://github.com/spring-projects/spring-ai</url>
20+
<connection>git://github.com/spring-projects/spring-ai.git</connection>
21+
<developerConnection>[email protected]:spring-projects/spring-ai.git</developerConnection>
22+
</scm>
23+
24+
<dependencies>
25+
26+
<!-- Boot dependencies -->
27+
<dependency>
28+
<groupId>org.springframework.boot</groupId>
29+
<artifactId>spring-boot-starter</artifactId>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-web</artifactId>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-webflux</artifactId>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>org.springframework.boot</groupId>
44+
<artifactId>spring-boot-configuration-processor</artifactId>
45+
<optional>true</optional>
46+
</dependency>
47+
48+
<!-- Test dependencies -->
49+
<dependency>
50+
<groupId>org.springframework.ai</groupId>
51+
<artifactId>spring-ai-test</artifactId>
52+
<version>${project.parent.version}</version>
53+
<scope>test</scope>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.springframework.boot</groupId>
58+
<artifactId>spring-boot-starter-test</artifactId>
59+
<scope>test</scope>
60+
</dependency>
61+
62+
<dependency>
63+
<groupId>org.mockito</groupId>
64+
<artifactId>mockito-core</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
</dependencies>
68+
69+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.http.client.autoconfigure;
18+
19+
import org.springframework.boot.autoconfigure.AutoConfiguration;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
22+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
23+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
24+
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
25+
import org.springframework.boot.web.client.RestClientCustomizer;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.http.client.*;
28+
import org.springframework.web.client.RestClient;
29+
import org.springframework.web.reactive.function.client.WebClient;
30+
31+
/**
32+
* {@link AutoConfiguration Auto-configuration} for AI http-client.
33+
*
34+
* @author Song Jaegeun
35+
*/
36+
@AutoConfiguration
37+
@ConditionalOnClass({ RestClient.class, WebClient.class})
38+
@EnableConfigurationProperties({ SpringAiHttpClientProperties.class })
39+
public class SpringAiHttpClientAutoConfiguration {
40+
41+
@Bean
42+
@ConditionalOnMissingBean(RestClientCustomizer.class)
43+
public RestClientCustomizer restClientCustomizer(SpringAiHttpClientProperties props) {
44+
return restClientBuilder -> {
45+
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
46+
.withConnectTimeout(props.getConnectionTimeout())
47+
.withReadTimeout(props.getReadTimeout());
48+
49+
ClientHttpRequestFactory factory = ClientHttpRequestFactoryBuilder.detect().build(settings);
50+
restClientBuilder.requestFactory(factory);
51+
};
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.http.client.autoconfigure;
18+
19+
import java.time.Duration;
20+
21+
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
23+
/**
24+
* Properties for AI Retry.
25+
*
26+
* @author Christian Tzolov
27+
*/
28+
@ConfigurationProperties(SpringAiHttpClientProperties.CONFIG_PREFIX)
29+
public class SpringAiHttpClientProperties {
30+
31+
public static final String CONFIG_PREFIX = "spring.ai.rest-client";
32+
33+
private Duration connectionTimeout = Duration.ofSeconds(10);
34+
35+
private Duration readTimeout = Duration.ofSeconds(30);
36+
37+
public Duration getConnectionTimeout() {
38+
return connectionTimeout;
39+
}
40+
41+
public void setConnectionTimeout(Duration connectionTimeout) {
42+
this.connectionTimeout = connectionTimeout;
43+
}
44+
45+
public Duration getReadTimeout() {
46+
return readTimeout;
47+
}
48+
49+
public void setReadTimeout(Duration readTimeout) {
50+
this.readTimeout = readTimeout;
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Copyright 2025-2025 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
org.springframework.ai.http.client.autoconfigure.SpringAiHttpClientAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.http.client.autoconfigure;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
23+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
24+
import org.springframework.boot.web.client.RestClientCustomizer;
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* @author Song jaegeun
29+
*/
30+
public class SpringAiHttpClientAutoConfigurationIT {
31+
32+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
33+
AutoConfigurations.of(SpringAiHttpClientAutoConfigurationIT.class, RestClientAutoConfiguration.class));
34+
35+
@Test
36+
void testHttpClientAutoConfiguration() {
37+
this.contextRunner.run(context -> {
38+
assertThat(context).hasSingleBean(RestClientCustomizer.class);
39+
});
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.http.client.autoconfigure;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
23+
24+
import java.time.Duration;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
/**
29+
* Unit Tests for {@link SpringAiHttpClientPropertiesTests}.
30+
*
31+
* @author Song Jaegeun
32+
*/
33+
public class SpringAiHttpClientPropertiesTests {
34+
35+
@Test
36+
public void httpClientDefaultProperties() {
37+
38+
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(SpringAiHttpClientAutoConfiguration.class))
39+
.run(context -> {
40+
var httpClientProperties = context.getBean(SpringAiHttpClientProperties.class);
41+
42+
assertThat(httpClientProperties.getConnectionTimeout()).isEqualTo(Duration.ofSeconds(10));
43+
assertThat(httpClientProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
44+
});
45+
}
46+
47+
@Test
48+
public void httpClientCustomProperties() {
49+
new ApplicationContextRunner().withPropertyValues(
50+
// @formatter:off
51+
"spring.ai.rest-client.connection-timeout=10s",
52+
"spring.ai.rest-client-read-timeout=30s")
53+
// @formatter:on
54+
.withConfiguration(AutoConfigurations.of(SpringAiHttpClientAutoConfiguration.class))
55+
.run(context -> {
56+
var httpClientProperties = context.getBean(SpringAiHttpClientProperties.class);
57+
58+
assertThat(httpClientProperties.getConnectionTimeout()).isEqualTo(Duration.ofSeconds(10));
59+
assertThat(httpClientProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
60+
});
61+
}
62+
63+
}

0 commit comments

Comments
 (0)