Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8350279: HttpClient: Add a new HttpResponse method to identify connections #24154

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
17 changes: 17 additions & 0 deletions src/java.net.http/share/classes/java/net/http/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ public interface HttpResponse<T> {
*/
public int statusCode();

/**
* {@return if present, a label identifying the connection on which the
* response was received}
* <p>
* The format of the string is opaque, but the content should be unique
* for the lifetime of the {@link HttpClient} instance.
*
* @implSpec
* The default implementation of this method returns
* {@link Optional#empty() Optional.empty()}.
*
* @since 25
*/
default Optional<String> connectionLabel() {
return Optional.empty();
}

/**
* Returns the {@link HttpRequest} corresponding to this response.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ abstract class AbstractAsyncSSLConnection extends HttpConnection
AbstractAsyncSSLConnection(InetSocketAddress addr,
HttpClientImpl client,
ServerName serverName, int port,
String[] alpn) {
super(addr, client);
String[] alpn,
String label) {
super(addr, client, label);
this.sniServerNames = formSNIServerNames(serverName, client);
SSLContext context = client.theSSLContext();
sslParameters = createSSLParameters(client, this.sniServerNames, alpn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ class AsyncSSLConnection extends AbstractAsyncSSLConnection {

AsyncSSLConnection(InetSocketAddress addr,
HttpClientImpl client,
String[] alpn) {
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn);
plainConnection = new PlainHttpConnection(addr, client);
String[] alpn,
String label) {
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn, label);
plainConnection = new PlainHttpConnection(addr, client, label);
writePublisher = new PlainHttpPublisher();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
HttpClientImpl client,
String[] alpn,
InetSocketAddress proxy,
ProxyHeaders proxyHeaders)
ProxyHeaders proxyHeaders,
String label)
{
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn);
this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders);
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn, label);
this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders, label);
this.writePublisher = new PlainHttpPublisher();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -28,6 +28,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
Expand All @@ -39,6 +40,7 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.net.http.HttpClient;
Expand Down Expand Up @@ -76,17 +78,48 @@ abstract class HttpConnection implements Closeable {
public static final Comparator<HttpConnection> COMPARE_BY_ID
= Comparator.comparing(HttpConnection::id);

private static final AtomicLong LABEL_COUNTER = new AtomicLong();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the API documentation on HttpResponse.connectionLabel() we talk about the connection label being unique within a HttpClient scope. i.e. two different HttpClient instances could have the same connectionLabel for a connection. I think that's the right scoping.

So given that, having a static field on a HttpConnection which keeps track of a connection label, may not be the right place to keep track of that state. In this proposed form, no two connections in two different HttpClient instances will ever have the same connectionLabel. I think this counter should probably be present on the HttpClientImpl as an instance field.

Copy link
Member

@dfuch dfuch Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that's OK. We could go either way - but I'd rather keep the label unique regardless of the client because otherwise we would also need to include the client id in the debug traces (ATM we only have the label). If it's unique in the scope of the VM it's a fortiori unique in the scope of the client instance.


/** The address this connection is connected to. Could be a server or a proxy. */
final InetSocketAddress address;
private final HttpClientImpl client;
private final TrailingOperations trailingOperations;

/**
* A unique identifier that provides a total order among instances.
*/
private final long id;

HttpConnection(InetSocketAddress address, HttpClientImpl client) {
/**
* A label to identify the connection.
* <p>
* This label helps with associating multiple components participating in a
* connection. For instance, an {@link AsyncSSLConnection} and the
* {@link PlainHttpConnection} it wraps will share the same label. As a
* result, compared to {@link #id}, this label does not give a total order
* among instances.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Volkan, I think we shouldn't talk about any kind of ordering here. I see that the id field has been updated with a comment stating that it provides ordering among instances. Even there, I don't think that comment about ordering is needed. So far we haven't used the id to mean anything other than a logical identifier and these values have played no role in ordering of connections at any place. So adding the comments about ordering, I think, makes it confusing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only use of HttpConnection.id is for ordering - so that connections can be placed in a ConcurrentSkipListSet. So maybe here we should not speak of ordering (remove the last sentence in that paragraph) - but I think it helps to keep it for id.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only use of HttpConnection.id is for ordering - so that connections can be placed in a ConcurrentSkipListSet.

I wasn't aware of that. Now that you mentioned it, I looked up the code which uses the Set to store these connections. And from what I can see, the order is only used during the closing of a HttpClient, to close these opened connections in that specific order. Did I miss any other usages of the order?

In any case, now that you corrected me about the usage of the id order, I agree that having the comment on the id field is fine and only remove it from the paragraph here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we close a connection, we take it out of the set. So it's not about ordering the connection but about quickly finding the connection in that set.

* </p>
*/
final String label;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be private.


HttpConnection(InetSocketAddress address, HttpClientImpl client, String label) {
this.address = address;
this.client = client;
trailingOperations = new TrailingOperations();
this.id = newConnectionId(client);
this.label = label;
}

private static String nextLabel() {
return "" + LABEL_COUNTER.getAndIncrement();
}

/**
* {@return a label identifying the connection to facilitate
* {@link HttpResponse#connectionLabel() HttpResponse::connectionLabel}}
*/
public String label() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps make this method final?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not. We could want to extend it to give an indication that the connection is TCP or TLS - or Quic in the future...

return label;
}

// This is overridden in tests
Expand Down Expand Up @@ -303,11 +336,13 @@ private static HttpConnection getSSLConnection(InetSocketAddress addr,
String[] alpn,
HttpRequestImpl request,
HttpClientImpl client) {
String label = nextLabel();
if (proxy != null)
return new AsyncSSLTunnelConnection(addr, client, alpn, proxy,
proxyTunnelHeaders(request));
proxyTunnelHeaders(request),
label);
else
return new AsyncSSLConnection(addr, client, alpn);
return new AsyncSSLConnection(addr, client, alpn, label);
}

/**
Expand Down Expand Up @@ -381,14 +416,16 @@ private static HttpConnection getPlainConnection(InetSocketAddress addr,
InetSocketAddress proxy,
HttpRequestImpl request,
HttpClientImpl client) {
String label = nextLabel();
if (request.isWebSocket() && proxy != null)
return new PlainTunnelingConnection(addr, proxy, client,
proxyTunnelHeaders(request));
proxyTunnelHeaders(request),
label);

if (proxy == null)
return new PlainHttpConnection(addr, client);
return new PlainHttpConnection(addr, client, label);
else
return new PlainProxyConnection(proxy, client);
return new PlainProxyConnection(proxy, client, label);
}

void closeOrReturnToCache(HttpHeaders hdrs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright year on this file will need an update.


final int responseCode;
private final Optional<String> connectionLabel;
final HttpRequest initialRequest;
final Optional<HttpResponse<T>> previousResponse;
final HttpHeaders headers;
Expand All @@ -59,6 +60,7 @@ public HttpResponseImpl(HttpRequest initialRequest,
T body,
Exchange<T> exch) {
this.responseCode = response.statusCode();
this.connectionLabel = connectionLabel(exch);
this.initialRequest = initialRequest;
this.previousResponse = Optional.ofNullable(previousResponse);
this.headers = response.headers();
Expand All @@ -70,11 +72,32 @@ public HttpResponseImpl(HttpRequest initialRequest,
this.body = body;
}

private static Optional<String> connectionLabel(Exchange<?> exchange) {
if (exchange == null) {
return Optional.empty();
}
ExchangeImpl<?> exchImpl = exchange.exchImpl;
if (exchImpl == null) {
return Optional.empty();
}
@SuppressWarnings("resource")
HttpConnection connection = exchImpl.connection();
if (connection == null) {
return Optional.empty();
}
return Optional.of(connection.label());
}

@Override
public int statusCode() {
return responseCode;
}

@Override
public Optional<String> connectionLabel() {
return connectionLabel;
}

@Override
public HttpRequest request() {
return initialRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ final FlowTube getConnectionFlow() {
return tube;
}

PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client) {
super(addr, client);
PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client, String label) {
super(addr, client, label);
try {
this.chan = SocketChannel.open();
chan.configureBlocking(false);
Expand All @@ -335,7 +335,7 @@ final FlowTube getConnectionFlow() {
}
chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
// wrap the channel in a Tube for async reading and writing
tube = new SocketTube(client(), chan, Utils::getBuffer);
tube = new SocketTube(client(), chan, Utils::getBuffer, label);
} catch (IOException e) {
throw new InternalError(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -29,8 +29,8 @@

class PlainProxyConnection extends PlainHttpConnection {

PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client) {
super(proxy, client);
PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client, String label) {
super(proxy, client, label);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -54,11 +54,12 @@ final class PlainTunnelingConnection extends HttpConnection {
protected PlainTunnelingConnection(InetSocketAddress addr,
InetSocketAddress proxy,
HttpClientImpl client,
ProxyHeaders proxyHeaders) {
super(addr, client);
ProxyHeaders proxyHeaders,
String label) {
super(addr, client, label);
this.proxyAddr = proxy;
this.proxyHeaders = proxyHeaders;
delegate = new PlainHttpConnection(proxy, client);
delegate = new PlainHttpConnection(proxy, client, label);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -31,7 +31,6 @@
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
Expand Down Expand Up @@ -59,7 +58,6 @@
final class SocketTube implements FlowTube {

final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
static final AtomicLong IDS = new AtomicLong();

private final HttpClientImpl client;
private final SocketChannel channel;
Expand All @@ -68,14 +66,15 @@ final class SocketTube implements FlowTube {
private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
private final InternalReadPublisher readPublisher;
private final InternalWriteSubscriber writeSubscriber;
private final long id = IDS.incrementAndGet();
private final String connectionLabel;

public SocketTube(HttpClientImpl client, SocketChannel channel,
Supplier<ByteBuffer> buffersFactory) {
Supplier<ByteBuffer> buffersFactory,
String connectionLabel) {
this.client = client;
this.channel = channel;
this.sliceBuffersSource = new SliceBufferSource(buffersFactory);

this.connectionLabel = connectionLabel;
this.readPublisher = new InternalReadPublisher();
this.writeSubscriber = new InternalWriteSubscriber();
}
Expand Down Expand Up @@ -1343,7 +1342,7 @@ public String toString() {
}

final String dbgString() {
return "SocketTube("+id+")";
return "SocketTube("+ connectionLabel +")";
}

final String channelDescr() {
Expand Down
Loading