Skip to content

Commit d685e23

Browse files
Copilotchickenlj
andcommitted
Add tests and comprehensive documentation for WebClient starter
Co-authored-by: chickenlj <18097545+chickenlj@users.noreply.github.com>
1 parent a6e26af commit d685e23

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Fixing Connection Reset Errors - Usage Guide
2+
3+
## Problem Description
4+
5+
When using Spring AI Alibaba with DashScope (or other AI providers), you may encounter the following error after your application has been idle for a while:
6+
7+
```
8+
org.springframework.web.reactive.function.client.WebClientRequestException: Connection reset
9+
Caused by: java.net.SocketException: Connection reset
10+
```
11+
12+
This typically happens in the following scenario:
13+
1. Your application makes its first API call successfully
14+
2. The application becomes idle (no API calls for a period of time)
15+
3. The next API call fails with "Connection reset"
16+
17+
## Root Cause
18+
19+
The issue occurs because:
20+
1. Spring AI's DashScope integration uses Spring WebFlux's WebClient with Reactor Netty
21+
2. Reactor Netty maintains a connection pool to reuse HTTP connections
22+
3. When connections are idle, the server (dashscope.aliyuncs.com) may close them after its own timeout
23+
4. When your application tries to reuse these closed connections, it gets a "Connection reset" error
24+
25+
## Solution
26+
27+
Use the `spring-ai-alibaba-starter-webclient-config` starter which automatically configures WebClient with proper connection pool management.
28+
29+
### Step 1: Add Dependency
30+
31+
Add this dependency to your `pom.xml`:
32+
33+
```xml
34+
<dependency>
35+
<groupId>com.alibaba.cloud.ai</groupId>
36+
<artifactId>spring-ai-alibaba-starter-webclient-config</artifactId>
37+
</dependency>
38+
```
39+
40+
For Gradle:
41+
42+
```gradle
43+
implementation 'com.alibaba.cloud.ai:spring-ai-alibaba-starter-webclient-config'
44+
```
45+
46+
### Step 2: That's It!
47+
48+
The starter will automatically configure WebClient with the following optimizations:
49+
- **Idle connection eviction**: Removes connections that have been idle for 30 seconds (default)
50+
- **Background cleanup**: Periodically scans and removes stale connections every 10 seconds
51+
- **Connection lifetime limit**: Replaces connections after 5 minutes
52+
- **Connection validation**: Validates connections before reuse
53+
54+
### Step 3: (Optional) Customize Configuration
55+
56+
You can customize the connection pool settings in your `application.yml`:
57+
58+
```yaml
59+
spring:
60+
ai:
61+
alibaba:
62+
webclient:
63+
enabled: true
64+
max-connections: 500 # Maximum connections per host
65+
max-idle-time: 30s # Remove connections idle for this long
66+
max-life-time: 5m # Replace connections after this time
67+
eviction-interval: 10s # Scan for stale connections every X seconds
68+
```
69+
70+
Or in `application.properties`:
71+
72+
```properties
73+
spring.ai.alibaba.webclient.enabled=true
74+
spring.ai.alibaba.webclient.max-connections=500
75+
spring.ai.alibaba.webclient.max-idle-time=30s
76+
spring.ai.alibaba.webclient.max-life-time=5m
77+
spring.ai.alibaba.webclient.eviction-interval=10s
78+
```
79+
80+
## Configuration Properties
81+
82+
| Property | Default | Description |
83+
|----------|---------|-------------|
84+
| `max-idle-time` | `30s` | **Most important setting** - Connections idle longer than this are removed. Should be less than the server's idle timeout |
85+
| `eviction-interval` | `10s` | How often to scan for and remove idle connections |
86+
| `max-life-time` | `5m` | Maximum time a connection can exist, regardless of use |
87+
| `max-connections` | `500` | Maximum number of connections per host |
88+
89+
## Tuning Recommendations
90+
91+
### If you still see connection reset errors:
92+
93+
1. **Reduce `max-idle-time`**: Try `15s` or `20s` if the server closes connections quickly
94+
```yaml
95+
spring.ai.alibaba.webclient.max-idle-time: 15s
96+
```
97+
98+
2. **Increase `eviction-interval` frequency**: Scan more frequently for stale connections
99+
```yaml
100+
spring.ai.alibaba.webclient.eviction-interval: 5s
101+
```
102+
103+
### For high-traffic applications:
104+
105+
1. **Increase `max-connections`**: Support more concurrent requests
106+
```yaml
107+
spring.ai.alibaba.webclient.max-connections: 1000
108+
```
109+
110+
2. **Increase `max-life-time`**: Keep connections longer if they're frequently reused
111+
```yaml
112+
spring.ai.alibaba.webclient.max-life-time: 10m
113+
```
114+
115+
### For low-traffic applications:
116+
117+
1. **Decrease `max-idle-time`**: Remove idle connections more aggressively
118+
```yaml
119+
spring.ai.alibaba.webclient.max-idle-time: 15s
120+
```
121+
122+
2. **Reduce `max-connections`**: Save resources
123+
```yaml
124+
spring.ai.alibaba.webclient.max-connections: 100
125+
```
126+
127+
## Verification
128+
129+
After adding the starter, you should see a log message like this at startup:
130+
131+
```
132+
INFO com.alibaba.cloud.ai.autoconfigure.webclient.WebClientAutoConfiguration - Configuring WebClient with connection pool settings: maxConnections=500, maxIdleTime=PT30S, maxLifeTime=PT5M, evictionInterval=PT10S
133+
```
134+
135+
## Disabling
136+
137+
If you need to disable this configuration for any reason:
138+
139+
```yaml
140+
spring:
141+
ai:
142+
alibaba:
143+
webclient:
144+
enabled: false
145+
```
146+
147+
## How It Works Internally
148+
149+
The starter provides a `WebClient.Builder` bean that Spring AI's DashScope integration automatically uses. This builder is configured with a custom `ConnectionProvider` from Reactor Netty that:
150+
151+
1. **Evicts idle connections**: Checks connection idle time and removes connections that exceed `max-idle-time`
152+
2. **Background eviction**: Runs a background task every `eviction-interval` to proactively clean up stale connections
153+
3. **Connection lifetime management**: Ensures no connection lives longer than `max-life-time`
154+
4. **Proper pooling**: Maintains a pool of up to `max-connections` connections per host
155+
156+
This prevents the application from ever attempting to reuse a connection that the server has already closed.
157+
158+
## Additional Resources
159+
160+
- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
161+
- [Reactor Netty Connection Pool Documentation](https://projectreactor.io/docs/netty/release/reference/index.html#_connection_pool)
162+
- [Spring AI Alibaba Documentation](https://java2ai.com)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2024-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+
package com.alibaba.cloud.ai.autoconfigure.webclient;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.boot.autoconfigure.AutoConfigurations;
20+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
21+
import org.springframework.web.reactive.function.client.WebClient;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
/**
26+
* Tests for {@link WebClientAutoConfiguration}.
27+
*
28+
* @author GitHub Copilot
29+
*/
30+
class WebClientAutoConfigurationTest {
31+
32+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
33+
.withConfiguration(AutoConfigurations.of(WebClientAutoConfiguration.class));
34+
35+
@Test
36+
void autoConfigurationIsEnabled() {
37+
this.contextRunner.run(context -> {
38+
assertThat(context).hasSingleBean(WebClient.Builder.class);
39+
assertThat(context).hasSingleBean(WebClientConfigProperties.class);
40+
});
41+
}
42+
43+
@Test
44+
void autoConfigurationCanBeDisabled() {
45+
this.contextRunner.withPropertyValues("spring.ai.alibaba.webclient.enabled=false").run(context -> {
46+
assertThat(context).doesNotHaveBean(WebClient.Builder.class);
47+
});
48+
}
49+
50+
@Test
51+
void propertiesAreConfigurable() {
52+
this.contextRunner
53+
.withPropertyValues("spring.ai.alibaba.webclient.max-connections=1000",
54+
"spring.ai.alibaba.webclient.max-idle-time=60s",
55+
"spring.ai.alibaba.webclient.max-life-time=10m")
56+
.run(context -> {
57+
assertThat(context).hasSingleBean(WebClientConfigProperties.class);
58+
WebClientConfigProperties properties = context.getBean(WebClientConfigProperties.class);
59+
assertThat(properties.getMaxConnections()).isEqualTo(1000);
60+
assertThat(properties.getMaxIdleTime()).hasSeconds(60);
61+
assertThat(properties.getMaxLifeTime()).hasMinutes(10);
62+
});
63+
}
64+
65+
@Test
66+
void webClientBuilderIsCreated() {
67+
this.contextRunner.run(context -> {
68+
assertThat(context).hasSingleBean(WebClient.Builder.class);
69+
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
70+
assertThat(builder).isNotNull();
71+
72+
// Verify we can build a WebClient
73+
WebClient webClient = builder.build();
74+
assertThat(webClient).isNotNull();
75+
});
76+
}
77+
78+
}

0 commit comments

Comments
 (0)