Skip to content

Commit dff2f69

Browse files
committed
Fix NPE in NettyConnectListener
.
1 parent 4382610 commit dff2f69

File tree

7 files changed

+126
-6
lines changed

7 files changed

+126
-6
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ Maven:
2020
<dependency>
2121
<groupId>org.asynchttpclient</groupId>
2222
<artifactId>async-http-client</artifactId>
23-
<version>3.0.3</version>
23+
<version>3.0.4</version>
2424
</dependency>
2525
</dependencies>
2626
```
2727

2828
Gradle:
2929
```groovy
3030
dependencies {
31-
implementation 'org.asynchttpclient:async-http-client:3.0.3'
31+
implementation 'org.asynchttpclient:async-http-client:3.0.4'
3232
}
3333
```
3434

client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<parent>
2020
<groupId>org.asynchttpclient</groupId>
2121
<artifactId>async-http-client-project</artifactId>
22-
<version>3.0.3</version>
22+
<version>3.0.4</version>
2323
</parent>
2424

2525
<modelVersion>4.0.0</modelVersion>

client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ public void onSuccess(Channel channel, InetSocketAddress remoteAddress) {
9898

9999
Request request = future.getTargetRequest();
100100
Uri uri = request.getUri();
101-
timeoutsHolder.setResolvedRemoteAddress(remoteAddress);
101+
// don't set a null resolved address - if the remoteAddress is null we keep
102+
// the previously scheduled (possibly unresolved) address to avoid NPEs in
103+
// timeout logging and keep useful diagnostic information
104+
if (remoteAddress != null) {
105+
timeoutsHolder.setResolvedRemoteAddress(remoteAddress);
106+
}
102107
ProxyServer proxyServer = future.getProxyServer();
103108

104109
// For HTTPS proxies, establish SSL connection to the proxy server first

client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ public void clean() {
5757

5858
void appendRemoteAddress(StringBuilder sb) {
5959
InetSocketAddress remoteAddress = timeoutsHolder.remoteAddress();
60+
61+
// Guard against null remoteAddress which can happen when the TimeoutsHolder
62+
// was created without an original remote address (for example when using a
63+
// pooled channel whose remoteAddress() returned null). In that case fall
64+
// back to the URI host/port from the request to avoid a NPE and provide
65+
// a useful diagnostic.
66+
if (remoteAddress == null) {
67+
if (nettyResponseFuture != null && nettyResponseFuture.getTargetRequest() != null) {
68+
try {
69+
String host = nettyResponseFuture.getTargetRequest().getUri().getHost();
70+
int port = nettyResponseFuture.getTargetRequest().getUri().getExplicitPort();
71+
sb.append(host == null ? "unknown" : host);
72+
sb.append(':').append(port);
73+
} catch (Exception ignored) {
74+
sb.append("unknown:0");
75+
}
76+
} else {
77+
sb.append("unknown:0");
78+
}
79+
return;
80+
}
81+
6082
sb.append(remoteAddress.getHostString());
6183
if (!remoteAddress.isUnresolved()) {
6284
sb.append('/').append(remoteAddress.getAddress().getHostAddress());

client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ public void cancel() {
110110
}
111111

112112
private Timeout newTimeout(TimerTask task, long delay) {
113-
return requestSender.isClosed() ? null : nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
113+
// requestSender or nettyTimer might be null in unit tests or in some edge
114+
// cases where a channel's remote address wasn't available. In such cases
115+
// avoid scheduling any timeouts rather than throwing a NPE.
116+
if (requestSender == null || nettyTimer == null || requestSender.isClosed()) {
117+
return null;
118+
}
119+
return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
114120
}
115121
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2014-2025 AsyncHttpClient Project. All rights reserved.
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+
* http://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 org.asynchttpclient.netty.timeout;
17+
18+
import org.asynchttpclient.AsyncCompletionHandler;
19+
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
20+
import org.asynchttpclient.Request;
21+
import org.asynchttpclient.RequestBuilder;
22+
import org.asynchttpclient.channel.ChannelPoolPartitioning;
23+
import org.asynchttpclient.netty.NettyResponseFuture;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.net.InetSocketAddress;
27+
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
30+
public class TimeoutTimerTaskTest {
31+
32+
@Test
33+
public void appendRemoteAddressShouldNotThrowWhenRemoteAddressIsNull() {
34+
Request request = new RequestBuilder().setUrl("http://example.com:12345").build();
35+
NettyResponseFuture<?> future = new NettyResponseFuture<>(request, new AsyncCompletionHandler<Object>() {
36+
@Override
37+
public Object onCompleted(org.asynchttpclient.Response response) throws Exception {
38+
return null;
39+
}
40+
}, null,
41+
0, ChannelPoolPartitioning.PerHostChannelPoolPartitioning.INSTANCE, null, null);
42+
43+
// create TimeoutsHolder without an original remote address
44+
TimeoutsHolder timeoutsHolder = new TimeoutsHolder(null, future, null, new DefaultAsyncHttpClientConfig.Builder().build(), null);
45+
46+
TimeoutTimerTask task = new TimeoutTimerTask(future, null, timeoutsHolder) {
47+
@Override
48+
public void run(io.netty.util.Timeout timeout) {
49+
// no-op
50+
}
51+
};
52+
53+
StringBuilder sb = new StringBuilder();
54+
task.appendRemoteAddress(sb);
55+
56+
// fallback should include URI host/port
57+
assertTrue(sb.toString().contains("example.com:12345"), sb.toString());
58+
}
59+
60+
@Test
61+
public void appendRemoteAddressShouldPrintResolvedAddressIfAvailable() {
62+
Request request = new RequestBuilder().setUrl("http://example.com:12345").build();
63+
NettyResponseFuture<?> future = new NettyResponseFuture<>(request, new AsyncCompletionHandler<Object>() {
64+
@Override
65+
public Object onCompleted(org.asynchttpclient.Response response) throws Exception {
66+
return null;
67+
}
68+
}, null,
69+
0, ChannelPoolPartitioning.PerHostChannelPoolPartitioning.INSTANCE, null, null);
70+
71+
TimeoutsHolder timeoutsHolder = new TimeoutsHolder(null, future, null, new DefaultAsyncHttpClientConfig.Builder().build(), null);
72+
73+
// set a resolved remote address
74+
timeoutsHolder.setResolvedRemoteAddress(new InetSocketAddress("127.0.0.1", 8080));
75+
76+
TimeoutTimerTask task = new TimeoutTimerTask(future, null, timeoutsHolder) {
77+
@Override
78+
public void run(io.netty.util.Timeout timeout) {
79+
// no-op
80+
}
81+
};
82+
83+
StringBuilder sb = new StringBuilder();
84+
task.appendRemoteAddress(sb);
85+
assertTrue(sb.toString().contains(":8080"), sb.toString());
86+
}
87+
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
<groupId>org.asynchttpclient</groupId>
2222
<artifactId>async-http-client-project</artifactId>
23-
<version>3.0.3</version>
23+
<version>3.0.4</version>
2424
<packaging>pom</packaging>
2525

2626
<name>AHC/Project</name>

0 commit comments

Comments
 (0)