Skip to content

Commit cb5a6c6

Browse files
committed
Merged branch 'jetty-12.0.x' into 'jetty-12.1.x'.
Signed-off-by: Simone Bordet <[email protected]>
2 parents 2f21cd4 + 4d832d0 commit cb5a6c6

File tree

12 files changed

+380
-36
lines changed

12 files changed

+380
-36
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,7 @@ updates:
246246
- dependency-name: "org.apache.avro:*"
247247
versions: [ ">=1.12" ]
248248

249+
- package-ecosystem: "github-actions"
250+
directory: "/"
251+
schedule:
252+
interval: "weekly"

.github/workflows/codeql-analysis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ jobs:
3131

3232
steps:
3333
- name: Checkout repository
34-
uses: actions/checkout@v3
34+
uses: actions/checkout@v4
3535

3636
# Install and setup JDK 11
3737
- name: Setup JDK 11
38-
uses: actions/setup-java@v3
38+
uses: actions/setup-java@v4
3939
if: ${{
4040
startsWith(github.ref, 'refs/heads/jetty-10.') ||
4141
startsWith(github.ref, 'refs/heads/jetty-11.') ||
@@ -49,7 +49,7 @@ jobs:
4949

5050
# Install and setup JDK 17
5151
- name: Setup JDK 17
52-
uses: actions/setup-java@v3
52+
uses: actions/setup-java@v4
5353
if: ${{
5454
startsWith(github.ref, 'refs/heads/jetty-12.') ||
5555
startsWith(github.base_ref, 'jetty-12.')
@@ -61,7 +61,7 @@ jobs:
6161

6262
# Initializes the CodeQL tools for scanning.
6363
- name: Initialize CodeQL
64-
uses: github/codeql-action/init@v2
64+
uses: github/codeql-action/init@v3
6565
with:
6666
languages: ${{ matrix.languages }}
6767
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -74,7 +74,7 @@ jobs:
7474

7575
- name: Set up Maven
7676
run:
77-
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.6"
77+
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.9"
7878

7979
- name: Clean install dependencies and build
8080
env:
@@ -94,4 +94,4 @@ jobs:
9494
# ./location_of_script_within_repo/buildscript.sh
9595

9696
- name: Perform CodeQL Analysis
97-
uses: github/codeql-action/analyze@v2
97+
uses: github/codeql-action/analyze@v3

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: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import java.nio.channels.SelectionKey;
2626
import java.nio.channels.SocketChannel;
2727
import java.time.Duration;
28+
import java.util.EventListener;
29+
import java.util.List;
2830
import java.util.Map;
31+
import java.util.concurrent.CopyOnWriteArrayList;
2932
import java.util.concurrent.Executor;
3033

3134
import org.eclipse.jetty.util.IO;
@@ -75,6 +78,7 @@ public class ClientConnector extends ContainerLifeCycle
7578
public static final String APPLICATION_PROTOCOLS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".applicationProtocols";
7679
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
7780

81+
private final List<ConnectListener> listeners = new CopyOnWriteArrayList<>();
7882
private Executor executor;
7983
private Scheduler scheduler;
8084
private ByteBufferPool byteBufferPool;
@@ -393,15 +397,20 @@ else if (networkChannel instanceof DatagramChannel)
393397
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
394398
if (LOG.isDebugEnabled())
395399
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
400+
401+
notifyConnectBegin(socketChannel, address);
396402
if (blocking)
397403
{
398404
socketChannel.socket().connect(address, (int)getConnectTimeout().toMillis());
405+
notifyConnectSuccess(socketChannel);
399406
socketChannel.configureBlocking(false);
400407
}
401408
else
402409
{
403410
socketChannel.configureBlocking(false);
404411
connected = socketChannel.connect(address);
412+
if (connected)
413+
notifyConnectSuccess(socketChannel);
405414
}
406415
}
407416
else
@@ -423,7 +432,7 @@ else if (networkChannel instanceof DatagramChannel)
423432
if (x.getClass() == SocketException.class)
424433
x = new SocketException("Could not connect to " + address).initCause(x);
425434
IO.close(channel);
426-
connectFailed(x, context);
435+
connectFailed(channel, address, x, context);
427436
}
428437
}
429438

@@ -509,15 +518,77 @@ protected void acceptFailed(Throwable failure, SelectableChannel channel, Map<St
509518
promise.failed(failure);
510519
}
511520

512-
protected void connectFailed(Throwable failure, Map<String, Object> context)
521+
protected void connectFailed(SelectableChannel channel, SocketAddress address, Throwable failure, Map<String, Object> context)
513522
{
514523
if (LOG.isDebugEnabled())
515-
LOG.debug("Could not connect to {}", context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY));
524+
LOG.debug("Could not connect to {}", address);
525+
notifyConnectFailure((SocketChannel)channel, address, failure);
516526
Promise<?> promise = (Promise<?>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
517527
if (promise != null)
518528
promise.failed(failure);
519529
}
520530

531+
@Override
532+
public boolean addEventListener(EventListener listener)
533+
{
534+
if (listener instanceof ConnectListener connectListener)
535+
return listeners.add(connectListener);
536+
return super.addEventListener(listener);
537+
}
538+
539+
@Override
540+
public boolean removeEventListener(EventListener listener)
541+
{
542+
if (listener instanceof ConnectListener connectListener)
543+
return listeners.remove(connectListener);
544+
return super.removeEventListener(listener);
545+
}
546+
547+
private void notifyConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
548+
{
549+
for (ConnectListener listener : listeners)
550+
{
551+
try
552+
{
553+
listener.onConnectBegin(socketChannel, socketAddress);
554+
}
555+
catch (Throwable x)
556+
{
557+
LOG.info("failure notifying listener {}", listener, x);
558+
}
559+
}
560+
}
561+
562+
private void notifyConnectSuccess(SocketChannel socketChannel)
563+
{
564+
for (ConnectListener listener : listeners)
565+
{
566+
try
567+
{
568+
listener.onConnectSuccess(socketChannel);
569+
}
570+
catch (Throwable x)
571+
{
572+
LOG.info("failure notifying listener {}", listener, x);
573+
}
574+
}
575+
}
576+
577+
private void notifyConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable throwable)
578+
{
579+
for (ConnectListener listener : listeners)
580+
{
581+
try
582+
{
583+
listener.onConnectFailure(socketChannel, socketAddress, throwable);
584+
}
585+
catch (Throwable x)
586+
{
587+
LOG.info("failure notifying listener {}", listener, x);
588+
}
589+
}
590+
}
591+
521592
protected class ClientSelectorManager extends SelectorManager
522593
{
523594
public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
@@ -561,12 +632,63 @@ public void connectionOpened(Connection connection, Object context)
561632
}
562633
}
563634

635+
@Override
636+
public void connectionSucceeded(SelectableChannel channel)
637+
{
638+
super.connectionSucceeded(channel);
639+
notifyConnectSuccess((SocketChannel)channel);
640+
}
641+
564642
@Override
565643
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
566644
{
645+
super.connectionFailed(channel, failure, attachment);
567646
@SuppressWarnings("unchecked")
568647
Map<String, Object> context = (Map<String, Object>)attachment;
569-
connectFailed(failure, context);
648+
SocketAddress address = (SocketAddress)context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
649+
connectFailed(channel, address, failure, context);
650+
}
651+
}
652+
653+
/**
654+
* <p>A listener for events about {@link SocketChannel#connect(SocketAddress)}.</p>
655+
* <p>The events are:</p>
656+
* <ul>
657+
* <li>{@link ConnectListener#onConnectBegin(SocketChannel, SocketAddress) begin}, just before the {@code connect()} call</li>
658+
* <li>{@link ConnectListener#onConnectSuccess(SocketChannel) success}, when the {@code connect()} call succeeds</li>
659+
* <li>{@link ConnectListener#onConnectFailure(SocketChannel, SocketAddress, Throwable) failure}, when the {@code connect()} call fails</li>
660+
* </ul>
661+
*/
662+
public interface ConnectListener extends EventListener
663+
{
664+
/**
665+
* <p>Callback method invoked just before a {@link SocketChannel#connect(SocketAddress)} call.</p>
666+
*
667+
* @param socketChannel the local socket channel that is about to connect
668+
* @param socketAddress the remote socket address to connect to
669+
*/
670+
default void onConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
671+
{
672+
}
673+
674+
/**
675+
* <p>Callback method invoked when a {@link SocketChannel#connect(SocketAddress)} call completes successfully.</p>
676+
*
677+
* @param socketChannel the local socket channel that succeeded to connect to the remote socket address
678+
*/
679+
default void onConnectSuccess(SocketChannel socketChannel)
680+
{
681+
}
682+
683+
/**
684+
* <p>Callback method invoked when a {@link SocketChannel#connect(SocketAddress)} call completes with a failure.</p>
685+
*
686+
* @param socketChannel the local socket channel that failed to connect to the remote socket address
687+
* @param socketAddress the remote socket address to connect to
688+
* @param failure the failure cause
689+
*/
690+
default void onConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable failure)
691+
{
570692
}
571693
}
572694
}

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

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

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,27 @@ protected SelectableChannel doAccept(SelectableChannel server) throws IOExceptio
369369
}
370370

371371
/**
372-
* <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
373-
* <p>By default it just logs with level warning.</p>
372+
* <p>Callback method invoked when a non-blocking connect was succeeded.</p>
373+
*
374+
* @param channel the channel that attempted the connect
375+
*/
376+
protected void connectionSucceeded(SelectableChannel channel)
377+
{
378+
if (LOG.isDebugEnabled())
379+
LOG.debug("Connected {}", channel);
380+
}
381+
382+
/**
383+
* <p>Callback method invoked when a non-blocking connect failed.</p>
374384
*
375385
* @param channel the channel that attempted the connect
376386
* @param ex the exception that caused the connect to fail
377387
* @param attachment the attachment object associated at registration
378388
*/
379389
protected void connectionFailed(SelectableChannel channel, Throwable ex, Object attachment)
380390
{
381-
LOG.warn(String.format("%s - %s", channel, attachment), ex);
391+
if (LOG.isDebugEnabled())
392+
LOG.debug("Failed to connect {}", channel, ex);
382393
}
383394

384395
/**

jetty-core/jetty-tests/jetty-test-client-transports/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
<artifactId>jetty-alpn-java-server</artifactId>
2828
<scope>test</scope>
2929
</dependency>
30+
<dependency>
31+
<groupId>org.eclipse.jetty</groupId>
32+
<artifactId>jetty-io</artifactId>
33+
<scope>test</scope>
34+
</dependency>
3035
<dependency>
3136
<groupId>org.eclipse.jetty</groupId>
3237
<artifactId>jetty-proxy</artifactId>

0 commit comments

Comments
 (0)