Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ public void onSuccess(Channel channel, InetSocketAddress remoteAddress) {

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

// For HTTPS proxies, establish SSL connection to the proxy server first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ public void clean() {

void appendRemoteAddress(StringBuilder sb) {
InetSocketAddress remoteAddress = timeoutsHolder.remoteAddress();

// Guard against null remoteAddress which can happen when the TimeoutsHolder
// was created without an original remote address (for example when using a
// pooled channel whose remoteAddress() returned null). In that case fall
// back to the URI host/port from the request to avoid a NPE and provide
// a useful diagnostic.
if (remoteAddress == null) {
if (nettyResponseFuture != null && nettyResponseFuture.getTargetRequest() != null) {
try {
String host = nettyResponseFuture.getTargetRequest().getUri().getHost();
int port = nettyResponseFuture.getTargetRequest().getUri().getExplicitPort();
sb.append(host == null ? "unknown" : host);
sb.append(':').append(port);
} catch (Exception ignored) {
sb.append("unknown:0");
}
} else {
sb.append("unknown:0");
}
return;
}

sb.append(remoteAddress.getHostString());
if (!remoteAddress.isUnresolved()) {
sb.append('/').append(remoteAddress.getAddress().getHostAddress());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ public void cancel() {
}

private Timeout newTimeout(TimerTask task, long delay) {
return requestSender.isClosed() ? null : nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
// requestSender or nettyTimer might be null in unit tests or in some edge
// cases where a channel's remote address wasn't available. In such cases
// avoid scheduling any timeouts rather than throwing a NPE.
if (requestSender == null || nettyTimer == null || requestSender.isClosed()) {
return null;
}
return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2014-2025 AsyncHttpClient Project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asynchttpclient.netty.timeout;

import org.asynchttpclient.AsyncCompletionHandler;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Request;
import org.asynchttpclient.RequestBuilder;
import org.asynchttpclient.channel.ChannelPoolPartitioning;
import org.asynchttpclient.netty.NettyResponseFuture;
import org.junit.jupiter.api.Test;

import java.net.InetSocketAddress;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class TimeoutTimerTaskTest {

@Test
public void appendRemoteAddressShouldNotThrowWhenRemoteAddressIsNull() {
Request request = new RequestBuilder().setUrl("http://example.com:12345").build();
NettyResponseFuture<?> future = new NettyResponseFuture<>(request, new AsyncCompletionHandler<Object>() {
@Override
public Object onCompleted(org.asynchttpclient.Response response) throws Exception {
return null;
}
}, null,
0, ChannelPoolPartitioning.PerHostChannelPoolPartitioning.INSTANCE, null, null);

// create TimeoutsHolder without an original remote address
TimeoutsHolder timeoutsHolder = new TimeoutsHolder(null, future, null, new DefaultAsyncHttpClientConfig.Builder().build(), null);

TimeoutTimerTask task = new TimeoutTimerTask(future, null, timeoutsHolder) {
@Override
public void run(io.netty.util.Timeout timeout) {
// no-op
}
};

StringBuilder sb = new StringBuilder();
task.appendRemoteAddress(sb);

// fallback should include URI host/port
assertTrue(sb.toString().contains("example.com:12345"), sb.toString());
}

@Test
public void appendRemoteAddressShouldPrintResolvedAddressIfAvailable() {
Request request = new RequestBuilder().setUrl("http://example.com:12345").build();
NettyResponseFuture<?> future = new NettyResponseFuture<>(request, new AsyncCompletionHandler<Object>() {
@Override
public Object onCompleted(org.asynchttpclient.Response response) throws Exception {
return null;
}
}, null,
0, ChannelPoolPartitioning.PerHostChannelPoolPartitioning.INSTANCE, null, null);

TimeoutsHolder timeoutsHolder = new TimeoutsHolder(null, future, null, new DefaultAsyncHttpClientConfig.Builder().build(), null);

// set a resolved remote address
timeoutsHolder.setResolvedRemoteAddress(new InetSocketAddress("127.0.0.1", 8080));

TimeoutTimerTask task = new TimeoutTimerTask(future, null, timeoutsHolder) {
@Override
public void run(io.netty.util.Timeout timeout) {
// no-op
}
};

StringBuilder sb = new StringBuilder();
task.appendRemoteAddress(sb);
assertTrue(sb.toString().contains(":8080"), sb.toString());
}
}
Loading