Skip to content

Commit 4d832d0

Browse files
committed
Issue #9529 - Expose TCP connection establishment information.
Cleaned up contribution by @arsenalzp. Added more test cases for blocking and non-blocking. Added documentation. Signed-off-by: Simone Bordet <[email protected]>
1 parent 9e86909 commit 4d832d0

File tree

8 files changed

+295
-210
lines changed

8 files changed

+295
-210
lines changed

documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import java.net.SocketAddress;
2121
import java.net.URI;
2222
import java.nio.ByteBuffer;
23+
import java.nio.channels.SocketChannel;
2324
import java.nio.file.Path;
2425
import java.nio.file.Paths;
2526
import java.util.List;
2627
import java.util.concurrent.CompletableFuture;
28+
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.concurrent.ConcurrentMap;
2730
import java.util.concurrent.TimeUnit;
2831
import javax.net.ssl.SSLEngine;
2932
import javax.net.ssl.SSLException;
@@ -1233,4 +1236,38 @@ public void connectionInformation() throws Exception
12331236
.send();
12341237
// end::connectionInformation[]
12351238
}
1239+
1240+
public void connectListener() throws Exception
1241+
{
1242+
// tag::connectListener[]
1243+
ClientConnector clientConnector = new ClientConnector();
1244+
clientConnector.addEventListener(new ClientConnector.ConnectListener()
1245+
{
1246+
private final ConcurrentMap<SocketChannel, Long> times = new ConcurrentHashMap<>();
1247+
1248+
@Override
1249+
public void onConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
1250+
{
1251+
times.put(socketChannel, System.nanoTime());
1252+
}
1253+
1254+
@Override
1255+
public void onConnectSuccess(SocketChannel socketChannel)
1256+
{
1257+
Long begin = times.remove(socketChannel);
1258+
System.getLogger("connection").log(INFO, "established in %d ns", System.nanoTime() - begin);
1259+
}
1260+
1261+
@Override
1262+
public void onConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable failure)
1263+
{
1264+
Long begin = times.remove(socketChannel);
1265+
System.getLogger("connection").log(INFO, "failed in %d ns", System.nanoTime() - begin);
1266+
}
1267+
});
1268+
1269+
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector));
1270+
httpClient.start();
1271+
// end::connectListener[]
1272+
}
12361273
}

documentation/jetty/modules/programming-guide/pages/client/http.adoc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/ht
541541
----
542542

543543
[[connection-information]]
544-
=== Request Connection Information
544+
=== Connection Information
545545

546546
In order to send a request, it is necessary to obtain a connection, as explained in the xref:request-processing[request processing section].
547547

@@ -562,6 +562,25 @@ This means that the connection is not available in the _request queued_ event, b
562562
For more information about request events, see xref:non-blocking[this section].
563563
====
564564

565+
[[connection-events]]
566+
=== Connection Events
567+
568+
In order to send HTTP requests, a connection is necessary, as explained in the xref:request-processing[request processing section].
569+
570+
HTTP/1.1 and HTTP/2 use `SocketChannel.connect(SocketAddress)` to establish a connection with the server, either via the TCP transport or via the Unix-Domain transport.
571+
572+
You can listen to these `connect` events using a `ClientConnector.ConnectListener`, for example to record connection establishment times:
573+
574+
[,java,indent=0]
575+
----
576+
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=connectListener]
577+
----
578+
579+
This could be particularly useful when you notice that your client application seem "slow" to send requests.
580+
The `connect begin` and `connect success` events, along with the `request queued` and `request begin` event (detailed xref:non-blocking[here]), allow you to understand whether it is the server being slow at accepting connections, or it is the client being slow at processing queued requests.
581+
582+
Once the low-level connection has been established, you can be notified of connection events using a `Connection.Listener`, or more concretely `ConnectionStatistics`, as described in xref:arch/io.adoc#connection-listener[this section].
583+
565584
[[configuration]]
566585
== HttpClient Configuration
567586

jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java

Lines changed: 102 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,11 @@ else if (networkChannel instanceof DatagramChannel)
457457
boolean connected = true;
458458
if (channel instanceof SocketChannel socketChannel)
459459
{
460-
final SocketAddress socketAddress = address;
461460
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
462461
if (LOG.isDebugEnabled())
463462
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
464463

465-
notifyConnectBegin(socketChannel, socketAddress);
464+
notifyConnectBegin(socketChannel, address);
466465
if (blocking)
467466
{
468467
socketChannel.socket().connect(address, (int)getConnectTimeout().toMillis());
@@ -473,6 +472,8 @@ else if (networkChannel instanceof DatagramChannel)
473472
{
474473
socketChannel.configureBlocking(false);
475474
connected = socketChannel.connect(address);
475+
if (connected)
476+
notifyConnectSuccess(socketChannel);
476477
}
477478
}
478479
else
@@ -494,7 +495,7 @@ else if (networkChannel instanceof DatagramChannel)
494495
if (x.getClass() == SocketException.class)
495496
x = new SocketException("Could not connect to " + address).initCause(x);
496497
IO.close(channel);
497-
connectFailed(channel, x, context);
498+
connectFailed(channel, address, x, context);
498499
}
499500
}
500501

@@ -580,16 +581,77 @@ protected void acceptFailed(Throwable failure, SelectableChannel channel, Map<St
580581
promise.failed(failure);
581582
}
582583

583-
protected void connectFailed(SelectableChannel channel, Throwable failure, Map<String, Object> context)
584+
protected void connectFailed(SelectableChannel channel, SocketAddress address, Throwable failure, Map<String, Object> context)
584585
{
585586
if (LOG.isDebugEnabled())
586-
LOG.debug("Could not connect to {}", context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY));
587-
notifyConnectFailure((SocketChannel)channel, failure);
587+
LOG.debug("Could not connect to {}", address);
588+
notifyConnectFailure((SocketChannel)channel, address, failure);
588589
Promise<?> promise = (Promise<?>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
589590
if (promise != null)
590591
promise.failed(failure);
591592
}
592593

594+
@Override
595+
public boolean addEventListener(EventListener listener)
596+
{
597+
if (listener instanceof ConnectListener connectListener)
598+
return listeners.add(connectListener);
599+
return super.addEventListener(listener);
600+
}
601+
602+
@Override
603+
public boolean removeEventListener(EventListener listener)
604+
{
605+
if (listener instanceof ConnectListener connectListener)
606+
return listeners.remove(connectListener);
607+
return super.removeEventListener(listener);
608+
}
609+
610+
private void notifyConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
611+
{
612+
for (ConnectListener listener : listeners)
613+
{
614+
try
615+
{
616+
listener.onConnectBegin(socketChannel, socketAddress);
617+
}
618+
catch (Throwable x)
619+
{
620+
LOG.info("failure notifying listener {}", listener, x);
621+
}
622+
}
623+
}
624+
625+
private void notifyConnectSuccess(SocketChannel socketChannel)
626+
{
627+
for (ConnectListener listener : listeners)
628+
{
629+
try
630+
{
631+
listener.onConnectSuccess(socketChannel);
632+
}
633+
catch (Throwable x)
634+
{
635+
LOG.info("failure notifying listener {}", listener, x);
636+
}
637+
}
638+
}
639+
640+
private void notifyConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable throwable)
641+
{
642+
for (ConnectListener listener : listeners)
643+
{
644+
try
645+
{
646+
listener.onConnectFailure(socketChannel, socketAddress, throwable);
647+
}
648+
catch (Throwable x)
649+
{
650+
LOG.info("failure notifying listener {}", listener, x);
651+
}
652+
}
653+
}
654+
593655
protected class ClientSelectorManager extends SelectorManager
594656
{
595657
public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
@@ -634,17 +696,20 @@ public void connectionOpened(Connection connection, Object context)
634696
}
635697

636698
@Override
637-
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
699+
public void connectionSucceeded(SelectableChannel channel)
638700
{
639-
@SuppressWarnings("unchecked")
640-
Map<String, Object> context = (Map<String, Object>)attachment;
641-
connectFailed(channel, failure, context);
701+
super.connectionSucceeded(channel);
702+
notifyConnectSuccess((SocketChannel)channel);
642703
}
643704

644705
@Override
645-
public void connectSuccess(SelectableChannel channel)
706+
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
646707
{
647-
listeners.forEach(listener -> listener.onConnectSuccess((SocketChannel)channel));
708+
super.connectionFailed(channel, failure, attachment);
709+
@SuppressWarnings("unchecked")
710+
Map<String, Object> context = (Map<String, Object>)attachment;
711+
SocketAddress address = (SocketAddress)context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
712+
connectFailed(channel, address, failure, context);
648713
}
649714
}
650715

@@ -770,119 +835,44 @@ public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector,
770835
}
771836

772837
/**
773-
* <p>A listener emits events for TCP connection establishment and failure</p>
774-
* {@link #ClientConnector.ConnectListener} is notified of the events:
838+
* <p>A listener for events about {@link SocketChannel#connect(SocketAddress)}.</p>
839+
* <p>The events are:</p>
775840
* <ul>
776-
* <li>Beginning of connect attempt</li>
777-
* <li>Success of connect attempt</li>
778-
* <li>Failed connect attempt</li>
841+
* <li>{@link ConnectListener#onConnectBegin(SocketChannel, SocketAddress) begin}, just before the {@code connect()} call</li>
842+
* <li>{@link ConnectListener#onConnectSuccess(SocketChannel) success}, when the {@code connect()} call succeeds</li>
843+
* <li>{@link ConnectListener#onConnectFailure(SocketChannel, SocketAddress, Throwable) failure}, when the {@code connect()} call fails</li>
779844
* </ul>
780-
* As an example {@link #ClientConnector.ConnectListener} can be used to trace attempts of connect for HttpClient
781845
*/
782846
public interface ConnectListener extends EventListener
783847
{
784848
/**
785-
* <p>Callback method is invoked for the very beginning of connect attempt</p>
786-
* <p>The {@code socketChannel} parameter can be used to extract socket channel information</p>
787-
* <p>The {@code socketAddress} parameter can be used to extract socket address information</p>
788-
*
789-
* @param socketChannel the socket channel which is registered with connect attempt
790-
* @param socketAddress the socket address which is registered with connect attempt
791-
*/
792-
public void onConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress);
793-
794-
/**
795-
* <p>Callback method is invoked for successfully connect attempt</p>
796-
* <p>The {@code socketChannel} parameter can be used to extract socket channel information</p>
797-
*
798-
* @param socketChannel the socket channel which is registered with connect attempt
849+
* <p>Callback method invoked just before a {@link SocketChannel#connect(SocketAddress)} call.</p>
850+
*
851+
* @param socketChannel the local socket channel that is about to connect
852+
* @param socketAddress the remote socket address to connect to
799853
*/
800-
public void onConnectSuccess(SocketChannel socketChannel);
801-
854+
default void onConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
855+
{
856+
}
857+
802858
/**
803-
* <p>Callback method is invoked for failed connect attempt</p>
804-
* <p>The {@code socketChannel} parameter can be used to extract socket channel information</p>
805-
* <p>The {@code throwable} parameter can be used to extract exception information of connect attempt</p>
806-
*
807-
* @param socketChannel the socket channel which is registered with connect attempt
859+
* <p>Callback method invoked when a {@link SocketChannel#connect(SocketAddress)} call completes successfully.</p>
860+
*
861+
* @param socketChannel the local socket channel that succeeded to connect to the remote socket address
808862
*/
809-
public void onConnectFailure(SocketChannel socketChannel, Throwable throwable);
810-
}
811-
812-
@Override
813-
public boolean addEventListener(EventListener listener)
814-
{
815-
if (listener instanceof ConnectListener connectListener)
816-
return listeners.add(connectListener);
817-
return super.addEventListener(listener);
818-
}
819-
820-
@Override
821-
public boolean removeEventListener(EventListener listener)
822-
{
823-
if (listener instanceof ConnectListener connectListener)
824-
return listeners.remove(connectListener);
825-
return super.removeEventListener(listener);
826-
}
827-
828-
/**
829-
* <p>A wrapper for ConnectListener.notifyConnectBegin() call</p>
830-
* @param socketChannel {@link #SocketChannel} registered for connect attempt
831-
* @param socketAddress {@link #SocketAddress} registered for connect attempt
832-
*/
833-
private void notifyConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
834-
{
835-
836-
for (ConnectListener listener : listeners)
863+
default void onConnectSuccess(SocketChannel socketChannel)
837864
{
838-
try
839-
{
840-
listener.onConnectBegin(socketChannel, socketAddress);
841-
}
842-
catch (Throwable x)
843-
{
844-
x.printStackTrace();
845-
}
846865
}
847-
}
848-
849-
/**
850-
* <p>Wrapper for ConnectListener.onConnectSuccess() call</p>
851-
* @param socketChannel {@link #SocketChannel} for successfully connect attempt
852-
*/
853-
private void notifyConnectSuccess(SocketChannel socketChannel)
854-
{
855866

856-
for (ConnectListener listener : listeners)
857-
{
858-
try
859-
{
860-
listener.onConnectSuccess(socketChannel);
861-
}
862-
catch (Throwable x)
863-
{
864-
x.printStackTrace();
865-
}
866-
}
867-
}
868-
869-
/**
870-
* <p>Wrapper for ConnectListener.onConnectFailure() call</p>
871-
* @param socketChannel {@link #SocketChannel} registered for failed connect attempt
872-
* @param throwable {@link #Throwable} exception arisen with failed attempt
873-
*/
874-
private void notifyConnectFailure(SocketChannel socketChannel, Throwable throwable)
875-
{
876-
for (ConnectListener listener : listeners)
867+
/**
868+
* <p>Callback method invoked when a {@link SocketChannel#connect(SocketAddress)} call completes with a failure.</p>
869+
*
870+
* @param socketChannel the local socket channel that failed to connect to the remote socket address
871+
* @param socketAddress the remote socket address to connect to
872+
* @param failure the failure cause
873+
*/
874+
default void onConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable failure)
877875
{
878-
try
879-
{
880-
listener.onConnectFailure(socketChannel, throwable);
881-
}
882-
catch (Throwable x)
883-
{
884-
x.printStackTrace();
885-
}
886876
}
887877
}
888878
}

jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ private void processConnect(SelectionKey key, Connect connect)
350350
{
351351
if (connect.timeout.cancel())
352352
{
353-
_selectorManager.connectSuccess(channel);
353+
_selectorManager.connectionSucceeded(channel);
354354
key.interestOps(0);
355355
execute(new CreateEndPoint(connect, key));
356356
}

0 commit comments

Comments
 (0)