Merge remote-tracking branch 'origin/jetty-12.0.x' into fix/jetty-12-pathresource-alias-detection-in-jarfile

This commit is contained in:
Joakim Erdfelt 2022-09-06 19:36:53 -05:00
commit 54cc1a3ff9
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
685 changed files with 9214 additions and 2925 deletions

View File

@ -9,6 +9,9 @@ on:
schedule:
- cron: '22 1 * * 2'
permissions:
contents: read
jobs:
analyze:
name: Analyze

View File

@ -3,8 +3,14 @@ on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: read
jobs:
stale:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4

220
Jenkinsfile vendored
View File

@ -1,173 +1,79 @@
#!groovy
/**
* IMPORTANT: Changes here need to be reflected in 2 other files as well.
* pom.xml
* build/scripts/ci.sh
*/
pipeline {
agent {
node { label 'linux' }
}
agent any
// save some io during the build
options {
skipDefaultCheckout()
durabilityHint('PERFORMANCE_OPTIMIZED')
}
stages {
stage("Checkout Jetty") {
steps {
container('jetty-build') {
dir("${env.WORKSPACE}/buildy") {
checkout scm
stage("Parallel Stage") {
parallel {
stage("Build / Test - JDK17") {
agent { node { label 'linux' } }
steps {
container('jetty-build') {
timeout( time: 180, unit: 'MINUTES' ) {
checkout scm
mavenBuild( "jdk17", "clean install -Perrorprone", "maven3")
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
exclusionPattern: '' +
// build tools
'**/org/eclipse/jetty/ant/**' +
',*/org/eclipse/jetty/maven/its/**' +
',**/org/eclipse/jetty/its/**' +
// example code / documentation
',**/org/eclipse/jetty/embedded/**' +
',**/org/eclipse/jetty/asyncrest/**' +
',**/org/eclipse/jetty/demo/**' +
// special environments / late integrations
',**/org/eclipse/jetty/gcloud/**' +
',**/org/eclipse/jetty/infinispan/**' +
',**/org/eclipse/jetty/osgi/**' +
',**/org/eclipse/jetty/http/spi/**' +
// test classes
',**/org/eclipse/jetty/tests/**' +
',**/org/eclipse/jetty/test/**',
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'
recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne(), spotBugs()]
}
}
}
}
}
}
stage("Build & Test - JDK17") {
stages {
stage("Setup") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
echo "Install org.eclipse.jetty:build-resources"
mavenBuild("jdk17", "clean install -f build", "maven3")
echo "Install org.eclipse.jetty:jetty-project"
mavenBuild("jdk17", "-N clean install", "maven3")
}
}
}
}
}
stage("Module : /jetty-core/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
mavenBuild("jdk17", "clean install -f jetty-core", "maven3")
mavenBuild("jdk17", "clean -f jetty-core", "maven3")
}
}
}
}
}
stage("Module : /jetty-integrations/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -f jetty-integrations", "maven3")
mavenBuild("jdk17", "clean -f jetty-integrations", "maven3")
}
}
}
}
}
stage("Module : /jetty-ee10/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -f jetty-ee10", "maven3")
mavenBuild("jdk17", "clean -f jetty-ee10", "maven3")
}
}
}
}
}
stage("Module : /jetty-ee9/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -f jetty-ee9", "maven3")
mavenBuild("jdk17", "clean -f jetty-ee9", "maven3")
}
}
}
}
}
stage("Module : /jetty-ee8/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -f jetty-ee8", "maven3")
mavenBuild("jdk17", "clean -f jetty-ee8", "maven3")
}
}
}
}
}
stage("Module : /jetty-home/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -pl :jetty-home", "maven3")
mavenBuild("jdk17", "clean -pl :jetty-home", "maven3")
}
}
}
}
}
stage("Module : /tests/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
//cleanup all projects
mavenBuild("jdk17", "clean install -f tests", "maven3")
mavenBuild("jdk17", "clean -f tests", "maven3")
}
}
}
}
}
/*
stage("Module : /jetty-p2/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
mavenBuild("jdk17", "clean install -f jetty-p2", "maven3")
}
}
}
}
}
*/
stage("Module : /documentation/") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
mavenBuild("jdk17", "clean install -rf :documentation", "maven3")
}
}
}
}
}
stage("Javadocs") {
steps {
container('jetty-build') {
timeout(time: 120, unit: 'MINUTES') {
dir("${env.WORKSPACE}/buildy") {
mavenBuild("jdk17", "clean install -DskipTests", "maven3")
mavenBuild("jdk17", "javadoc:javadoc", "maven3")
}
}
}
}
}
}
post {
failure {
slackNotif()
}
unstable {
slackNotif()
}
fixed {
slackNotif()
}
}
}
def slackNotif() {
script {
try {
if ( env.BRANCH_NAME == 'jetty-10.0.x' || env.BRANCH_NAME == 'jetty-11.0.x' || env.BRANCH_NAME == 'jetty-12.0.x' ) {
//BUILD_USER = currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId()
// by ${BUILD_USER}
COLOR_MAP = ['SUCCESS': 'good', 'FAILURE': 'danger', 'UNSTABLE': 'danger', 'ABORTED': 'danger']
slackSend channel: '#jenkins',
color: COLOR_MAP[currentBuild.currentResult],
message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} - ${env.BUILD_URL}"
}
} catch (Exception e) {
e.printStackTrace()
echo "skip failure slack notification: " + e.getMessage()
}
}
}
@ -195,7 +101,7 @@ def mavenBuild(jdk, cmdline, mvnName) {
}
finally
{
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true, skipPublishingChecks: true
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true
}
}
}

View File

@ -1,5 +1,8 @@
jetty-12.0.0-SNAPSHOT
jetty-12.0.0.alpha0 - 22 August 2022
+ First alpha release of Jetty 12. A lot changes but very good changes!
jetty-11.0.11 - 21 June 2022
+ 8184 All suffix globs except first fail to match if path has `.` character
in prefix section

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.ee10.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.NanoTime;
@SuppressWarnings("unused")
public class WebSocketDocs
@ -256,7 +257,7 @@ public class WebSocketDocs
remote.sendBytes(bytes);
// Send a PING frame to the remote peer.
remote.sendPing(ByteBuffer.allocate(8).putLong(System.nanoTime()).flip());
remote.sendPing(ByteBuffer.allocate(8).putLong(NanoTime.now()).flip());
}
catch (IOException x)
{
@ -425,7 +426,7 @@ public class WebSocketDocs
public void onWebSocketConnect(Session session)
{
// Send to the remote peer the local nanoTime.
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(System.nanoTime()).flip();
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(NanoTime.now()).flip();
session.getRemote().sendPing(buffer, WriteCallback.NOOP);
}
@ -436,7 +437,7 @@ public class WebSocketDocs
long start = payload.getLong();
// Calculate the round-trip time.
long roundTrip = System.nanoTime() - start;
long roundTrip = NanoTime.since(start);
}
}
// end::pingPongListener[]

View File

@ -120,14 +120,14 @@ public class HTTPServerDocs
@Override
public void onRequestBegin(Request request)
{
times.put(request, System.nanoTime());
times.put(request, NanoTime.now());
}
@Override
public void onComplete(Request request)
{
long begin = times.remove(request);
long elapsed = System.nanoTime() - begin;
long elapsed = NanoTime.since(begin);
System.getLogger("timing").log(INFO, "Request {0} took {1} ns", request, elapsed);
}
}

View File

@ -204,27 +204,29 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-common</artifactId>
<artifactId>jetty-infinispan-common</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote-query</artifactId>
<artifactId>jetty-infinispan-remote</artifactId>
<classifier>config</classifier>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded</artifactId>
<version>10.0.8-SNAPSHOT</version>
<artifactId>jetty-infinispan-remote-query</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded-query</artifactId>
<artifactId>jetty-infinispan-embedded</artifactId>
<classifier>config</classifier>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan-embedded-query</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
@ -260,7 +262,7 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-keystore</artifactId>
<version>10.0.8-SNAPSHOT</version>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.memcached</groupId>
@ -275,7 +277,7 @@
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-alpn</artifactId>
<version>10.0.8-SNAPSHOT</version>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
@ -370,7 +372,7 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<version>10.0.8-SNAPSHOT</version>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -184,27 +184,27 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-common</artifactId>
<artifactId>jetty-infinispan-common</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote-query</artifactId>
<artifactId>jetty-infinispan-remote</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded</artifactId>
<version>10.0.8-SNAPSHOT</version>
<artifactId>jetty-infinispan-remote-query</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded-query</artifactId>
<artifactId>jetty-infinispan-embedded</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan-embedded-query</artifactId>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
@ -230,7 +230,7 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-keystore</artifactId>
<version>10.0.8-SNAPSHOT</version>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.memcached</groupId>
@ -296,7 +296,7 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<version>10.0.8-SNAPSHOT</version>
<version>12.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -277,7 +278,7 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
{
pending.decrementAndGet();
if (LOG.isDebugEnabled())
LOG.debug("Not creating connection as pool is full, pending: {}", pending);
LOG.debug("Not creating connection as pool {} is full, pending: {}", pool, pending);
return;
}
@ -533,15 +534,17 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
@Override
public String toString()
{
return String.format("%s@%x[c=%d/%d/%d,a=%d,i=%d,q=%d]",
return String.format("%s@%x[s=%s,c=%d/%d/%d,a=%d,i=%d,q=%d,p=%s]",
getClass().getSimpleName(),
hashCode(),
getState(),
getPendingConnectionCount(),
getConnectionCount(),
getMaxConnectionCount(),
getActiveConnectionCount(),
getIdleConnectionCount(),
destination.getQueuedRequestCount());
destination.getQueuedRequestCount(),
pool);
}
private class FutureConnection extends Promise.Completable<Connection>
@ -591,7 +594,7 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
private static class EntryHolder
{
private final Pool<Connection>.Entry entry;
private final long creationNanos = System.nanoTime();
private final long creationNanoTime = NanoTime.now();
private final AtomicInteger usage = new AtomicInteger();
private EntryHolder(Pool<Connection>.Entry entry)
@ -601,7 +604,7 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
private boolean isExpired(long timeoutNanos)
{
return System.nanoTime() - creationNanos >= timeoutNanos;
return NanoTime.since(creationNanoTime) >= timeoutNanos;
}
private boolean use(int maxUsage)

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.NanoTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -215,10 +216,10 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
// Adjust the timeout of the new request, taking into account the
// timeout of the previous request and the time already elapsed.
long timeoutAt = request.getTimeoutAt();
if (timeoutAt < Long.MAX_VALUE)
long timeoutNanoTime = request.getTimeoutNanoTime();
if (timeoutNanoTime < Long.MAX_VALUE)
{
long newTimeout = timeoutAt - System.nanoTime();
long newTimeout = NanoTime.until(timeoutNanoTime);
if (newTimeout > 0)
{
newRequest.timeout(newTimeout, TimeUnit.NANOSECONDS);

View File

@ -74,6 +74,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.Sweeper;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -142,12 +143,13 @@ public class HttpClient extends ContainerLifeCycle
private boolean tcpNoDelay = true;
private boolean strictEventOrdering = false;
private HttpField encodingField;
private boolean removeIdleDestinations = false;
private long destinationIdleTimeout;
private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
private HttpCompliance httpCompliance = HttpCompliance.RFC7230;
private String defaultRequestContentType = "application/octet-stream";
private boolean useInputDirectByteBuffers = true;
private boolean useOutputDirectByteBuffers = true;
private Sweeper destinationSweeper;
/**
* Creates a HttpClient instance that can perform HTTP/1.1 requests to non-TLS and TLS destinations.
@ -222,7 +224,14 @@ public class HttpClient extends ContainerLifeCycle
cookieStore = cookieManager.getCookieStore();
transport.setHttpClient(this);
super.doStart();
if (getDestinationIdleTimeout() > 0L)
{
destinationSweeper = new Sweeper(scheduler, 1000L);
destinationSweeper.start();
}
}
private CookieManager newCookieManager()
@ -233,6 +242,12 @@ public class HttpClient extends ContainerLifeCycle
@Override
protected void doStop() throws Exception
{
if (destinationSweeper != null)
{
destinationSweeper.stop();
destinationSweeper = null;
}
decoderFactories.clear();
handlers.clear();
@ -290,6 +305,11 @@ public class HttpClient extends ContainerLifeCycle
return cookieManager;
}
Sweeper getDestinationSweeper()
{
return destinationSweeper;
}
/**
* @return the authentication store associated with this instance
*/
@ -529,21 +549,28 @@ public class HttpClient extends ContainerLifeCycle
*/
public HttpDestination resolveDestination(Origin origin)
{
return destinations.computeIfAbsent(origin, o ->
return destinations.compute(origin, (k, v) ->
{
HttpDestination destination = getTransport().newHttpDestination(o);
// Start the destination before it's published to other threads.
addManaged(destination);
if (LOG.isDebugEnabled())
LOG.debug("Created {}", destination);
return destination;
if (v == null || v.stale())
{
HttpDestination newDestination = getTransport().newHttpDestination(k);
// Start the destination before it's published to other threads.
addManaged(newDestination);
if (LOG.isDebugEnabled())
LOG.debug("Created {}; existing: '{}'", newDestination, v);
return newDestination;
}
return v;
});
}
protected boolean removeDestination(HttpDestination destination)
{
boolean removed = destinations.remove(destination.getOrigin(), destination);
removeBean(destination);
return destinations.remove(destination.getOrigin(), destination);
if (LOG.isDebugEnabled())
LOG.debug("Removed {}; result: {}", destination, removed);
return removed;
}
/**
@ -925,28 +952,6 @@ public class HttpClient extends ContainerLifeCycle
this.maxRedirects = maxRedirects;
}
/**
* @return whether TCP_NODELAY is enabled
* @deprecated use {@link ClientConnector#isTCPNoDelay()} instead
*/
@ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
@Deprecated
public boolean isTCPNoDelay()
{
return tcpNoDelay;
}
/**
* @param tcpNoDelay whether TCP_NODELAY is enabled
* @see java.net.Socket#setTcpNoDelay(boolean)
* @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead
*/
@Deprecated
public void setTCPNoDelay(boolean tcpNoDelay)
{
this.tcpNoDelay = tcpNoDelay;
}
/**
* Gets the http compliance mode for parsing http responses.
* The default http compliance level is {@link HttpCompliance#RFC7230} which is the latest HTTP/1.1 specification
@ -1011,30 +1016,37 @@ public class HttpClient extends ContainerLifeCycle
}
/**
* @return whether destinations that have no connections should be removed
* @see #setRemoveIdleDestinations(boolean)
* The default value is 0
* @return the time in ms after which idle destinations are removed
* @see #setDestinationIdleTimeout(long)
*/
@ManagedAttribute("Whether idle destinations are removed")
public boolean isRemoveIdleDestinations()
@ManagedAttribute("The time in ms after which idle destinations are removed, disabled when zero or negative")
public long getDestinationIdleTimeout()
{
return removeIdleDestinations;
return destinationIdleTimeout;
}
/**
* Whether destinations that have no connections (nor active nor idle) should be removed.
* <p>
* Applications typically make request to a limited number of destinations so keeping
* destinations around is not a problem for the memory or the GC.
* However, for applications that hit millions of different destinations (e.g. a spider
* bot) it would be useful to be able to remove the old destinations that won't be visited
* anymore and leave space for new destinations.
* Whether destinations that have no connections (nor active nor idle) and no exchanges
* should be removed after the specified timeout.
* </p>
* <p>
* If the specified {@code destinationIdleTimeout} is 0 or negative, then the destinations
* are not removed.
* </p>
* <p>
* Avoids accumulating destinations when applications (e.g. a spider bot or web crawler)
* hit a lot of different destinations that won't be visited again.
* </p>
*
* @param removeIdleDestinations whether destinations that have no connections should be removed
* @see org.eclipse.jetty.client.DuplexConnectionPool
* @param destinationIdleTimeout the time in ms after which idle destinations are removed
*/
public void setRemoveIdleDestinations(boolean removeIdleDestinations)
public void setDestinationIdleTimeout(long destinationIdleTimeout)
{
this.removeIdleDestinations = removeIdleDestinations;
if (isStarted())
throw new IllegalStateException();
this.destinationIdleTimeout = destinationIdleTimeout;
}
/**
@ -1125,17 +1137,6 @@ public class HttpClient extends ContainerLifeCycle
return encodingField;
}
/**
* @param host the host to normalize
* @return the host itself
* @deprecated no replacement, do not use it
*/
@Deprecated
protected String normalizeHost(String host)
{
return host;
}
public static int normalizePort(String scheme, int port)
{
if (port > 0)

View File

@ -19,7 +19,6 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.api.Authentication;
@ -34,6 +33,7 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.HttpCookieStore;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
@ -48,13 +48,13 @@ public abstract class HttpConnection implements IConnection, Attachable
private final RequestTimeouts requestTimeouts;
private Object attachment;
private int idleTimeoutGuard;
private long idleTimeoutStamp;
private long idleTimeoutNanoTime;
protected HttpConnection(HttpDestination destination)
{
this.destination = destination;
this.requestTimeouts = new RequestTimeouts(destination.getHttpClient().getScheduler());
this.idleTimeoutStamp = System.nanoTime();
this.idleTimeoutNanoTime = NanoTime.now();
}
public HttpClient getHttpClient()
@ -121,7 +121,7 @@ public abstract class HttpConnection implements IConnection, Attachable
try (AutoLock l = lock.lock())
{
--idleTimeoutGuard;
idleTimeoutStamp = System.nanoTime();
idleTimeoutNanoTime = NanoTime.now();
}
return result;
@ -267,7 +267,7 @@ public abstract class HttpConnection implements IConnection, Attachable
{
if (idleTimeoutGuard == 0)
{
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTimeoutStamp);
long elapsed = NanoTime.millisSince(idleTimeoutNanoTime);
boolean idle = elapsed > idleTimeout / 2;
if (idle)
idleTimeoutGuard = -1;

View File

@ -34,20 +34,23 @@ import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.Sweeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ManagedObject
public class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable
public class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable, Sweeper.Sweepable
{
private static final Logger LOG = LoggerFactory.getLogger(HttpDestination.class);
@ -60,7 +63,10 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
private final ClientConnectionFactory connectionFactory;
private final HttpField hostField;
private final RequestTimeouts requestTimeouts;
private final AutoLock staleLock = new AutoLock();
private ConnectionPool connectionPool;
private boolean stale;
private long activeNanoTime;
public HttpDestination(HttpClient client, Origin origin, boolean intrinsicallySecure)
{
@ -104,23 +110,71 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
connectionPool.accept(connection);
}
public boolean stale()
{
try (AutoLock l = staleLock.lock())
{
boolean stale = this.stale;
if (!stale)
this.activeNanoTime = NanoTime.now();
if (LOG.isDebugEnabled())
LOG.debug("Stale check done with result {} on {}", stale, this);
return stale;
}
}
@Override
public boolean sweep()
{
if (LOG.isDebugEnabled())
LOG.debug("Sweep check in progress on {}", this);
boolean remove = false;
try (AutoLock l = staleLock.lock())
{
boolean stale = exchanges.isEmpty() && connectionPool.isEmpty();
if (!stale)
{
this.activeNanoTime = NanoTime.now();
}
else if (NanoTime.millisSince(activeNanoTime) >= getHttpClient().getDestinationIdleTimeout())
{
this.stale = true;
remove = true;
}
}
if (remove)
{
getHttpClient().removeDestination(this);
LifeCycle.stop(this);
}
if (LOG.isDebugEnabled())
LOG.debug("Sweep check done with result {} on {}", remove, this);
return remove;
}
@Override
protected void doStart() throws Exception
{
this.connectionPool = newConnectionPool(client);
addBean(connectionPool, true);
super.doStart();
Sweeper sweeper = client.getBean(Sweeper.class);
if (sweeper != null && connectionPool instanceof Sweeper.Sweepable)
sweeper.offer((Sweeper.Sweepable)connectionPool);
Sweeper connectionPoolSweeper = client.getBean(Sweeper.class);
if (connectionPoolSweeper != null && connectionPool instanceof Sweeper.Sweepable)
connectionPoolSweeper.offer((Sweeper.Sweepable)connectionPool);
Sweeper destinationSweeper = getHttpClient().getDestinationSweeper();
if (destinationSweeper != null)
destinationSweeper.offer(this);
}
@Override
protected void doStop() throws Exception
{
Sweeper sweeper = client.getBean(Sweeper.class);
if (sweeper != null && connectionPool instanceof Sweeper.Sweepable)
sweeper.remove((Sweeper.Sweepable)connectionPool);
Sweeper destinationSweeper = getHttpClient().getDestinationSweeper();
if (destinationSweeper != null)
destinationSweeper.remove(this);
Sweeper connectionPoolSweeper = client.getBean(Sweeper.class);
if (connectionPoolSweeper != null && connectionPool instanceof Sweeper.Sweepable)
connectionPoolSweeper.remove((Sweeper.Sweepable)connectionPool);
super.doStop();
removeBean(connectionPool);
}
@ -449,11 +503,7 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
{
boolean removed = connectionPool.remove(connection);
if (getHttpExchanges().isEmpty())
{
tryRemoveIdleDestination();
}
else if (removed)
if (removed)
{
// Process queued requests that may be waiting.
// We may create a connection that is not
@ -478,22 +528,6 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
{
exchange.getRequest().abort(cause);
}
if (exchanges.isEmpty())
tryRemoveIdleDestination();
}
private void tryRemoveIdleDestination()
{
if (getHttpClient().isRemoveIdleDestinations() && connectionPool.isEmpty())
{
// There is a race condition between this thread removing the destination
// and another thread queueing a request to this same destination.
// If this destination is removed, but the request queued, a new connection
// will be opened, the exchange will be executed and eventually the connection
// will idle timeout and be closed. Meanwhile a new destination will be created
// in HttpClient and will be used for other requests.
getHttpClient().removeDestination(this);
}
}
@Override
@ -507,16 +541,39 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
return getOrigin().asString();
}
@ManagedAttribute("For how long this destination has been idle in ms")
public long getIdle()
{
if (getHttpClient().getDestinationIdleTimeout() <= 0L)
return -1;
try (AutoLock l = staleLock.lock())
{
return NanoTime.millisSince(activeNanoTime);
}
}
@ManagedAttribute("Whether this destinations is stale")
public boolean isStale()
{
try (AutoLock l = staleLock.lock())
{
return this.stale;
}
}
@Override
public String toString()
{
return String.format("%s[%s]@%x%s,queue=%d,pool=%s",
return String.format("%s[%s]@%x%s,state=%s,queue=%d,pool=%s,stale=%b,idle=%d",
HttpDestination.class.getSimpleName(),
getOrigin(),
hashCode(),
proxy == null ? "" : "(via " + proxy + ")",
getState(),
getQueuedRequestCount(),
getConnectionPool());
getConnectionPool(),
isStale(),
getIdle());
}
/**

View File

@ -93,7 +93,7 @@ public class HttpExchange implements CyclicTimeouts.Expirable
@Override
public long getExpireNanoTime()
{
return request.getTimeoutAt();
return request.getTimeoutNanoTime();
}
/**

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.NanoTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -309,10 +310,10 @@ public class HttpRedirector
// Adjust the timeout of the new request, taking into account the
// timeout of the previous request and the time already elapsed.
long timeoutAt = httpRequest.getTimeoutAt();
if (timeoutAt < Long.MAX_VALUE)
long timeoutNanoTime = httpRequest.getTimeoutNanoTime();
if (timeoutNanoTime < Long.MAX_VALUE)
{
long newTimeout = timeoutAt - System.nanoTime();
long newTimeout = NanoTime.until(timeoutNanoTime);
if (newTimeout > 0)
{
redirect.timeout(newTimeout, TimeUnit.NANOSECONDS);

View File

@ -53,6 +53,7 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.NanoTime;
public class HttpRequest implements Request
{
@ -75,7 +76,7 @@ public class HttpRequest implements Request
private boolean versionExplicit;
private long idleTimeout = -1;
private long timeout;
private long timeoutAt = Long.MAX_VALUE;
private long timeoutNanoTime = Long.MAX_VALUE;
private Content content;
private boolean followRedirects;
private List<HttpCookie> cookies;
@ -799,16 +800,16 @@ public class HttpRequest implements Request
{
long timeout = getTimeout();
if (timeout > 0)
timeoutAt = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout);
timeoutNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout);
}
/**
* @return The nanoTime at which the timeout expires or {@link Long#MAX_VALUE} if there is no timeout.
* @see #timeout(long, TimeUnit)
*/
long getTimeoutAt()
long getTimeoutNanoTime()
{
return timeoutAt;
return timeoutNanoTime;
}
protected List<Response.ResponseListener> getResponseListeners()

View File

@ -22,6 +22,7 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.io.CyclicTimeout;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -67,7 +68,7 @@ public class TimeoutCompleteListener extends CyclicTimeout implements Response.C
{
if (requestTimeout.compareAndSet(null, request))
{
long delay = Math.max(0, timeoutAt - System.nanoTime());
long delay = Math.max(0, NanoTime.until(timeoutAt));
if (LOG.isDebugEnabled())
LOG.debug("Scheduling timeout in {} ms for {} on {}", TimeUnit.NANOSECONDS.toMillis(delay), request, this);
schedule(delay, TimeUnit.NANOSECONDS);

View File

@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
@ -115,7 +116,7 @@ public class ValidatingConnectionPool extends DuplexConnectionPool
private class Holder implements Runnable
{
private final long timestamp = System.nanoTime();
private final long creationNanoTime = NanoTime.now();
private final AtomicBoolean done = new AtomicBoolean();
private final Connection connection;
public Scheduler.Task task;
@ -156,7 +157,7 @@ public class ValidatingConnectionPool extends DuplexConnectionPool
{
return String.format("%s[validationLeft=%dms]",
connection,
timeout - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - timestamp)
timeout - NanoTime.millisSince(creationNanoTime)
);
}
}

View File

@ -43,6 +43,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -212,13 +213,12 @@ public class ConnectionPoolTest
private void run(CountDownLatch latch, int iterations, List<Throwable> failures)
{
long begin = System.nanoTime();
long begin = NanoTime.now();
for (int i = 0; i < iterations; ++i)
{
test(failures);
}
long end = System.nanoTime();
long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
long elapsed = NanoTime.millisSince(begin);
System.err.printf("%d requests in %d ms, %.3f req/s%n", iterations, elapsed, elapsed > 0 ? iterations * 1000D / elapsed : -1D);
latch.countDown();
}

View File

@ -19,12 +19,14 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.awaitility.Awaitility;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.NanoTime;
import org.hamcrest.Matchers;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@ -291,6 +293,9 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
public void testDestinationIsRemoved(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
client.stop();
client.setDestinationIdleTimeout(1000);
client.start();
String host = "localhost";
int port = connector.getLocalPort();
@ -305,7 +310,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
Destination destinationAfter = client.resolveDestination(request);
assertSame(destinationBefore, destinationAfter);
client.setRemoveIdleDestinations(true);
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> client.getDestinations().isEmpty());
request = client.newRequest(host, port)
.scheme(scenario.getScheme())
@ -323,21 +328,19 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
public void testDestinationIsRemovedAfterConnectionError(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
client.stop();
client.setDestinationIdleTimeout(1000);
client.start();
String host = "localhost";
int port = connector.getLocalPort();
client.setRemoveIdleDestinations(true);
assertTrue(client.getDestinations().isEmpty(), "Destinations of a fresh client must be empty");
server.stop();
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
assertThrows(Exception.class, request::send);
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(1);
while (!client.getDestinations().isEmpty() && System.nanoTime() < deadline)
{
Thread.sleep(10);
}
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> client.getDestinations().isEmpty());
assertTrue(client.getDestinations().isEmpty(), "Destination must be removed after connection error");
}
@ -348,8 +351,8 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
private Connection await(Supplier<Connection> supplier, long time, TimeUnit unit) throws InterruptedException
{
long start = System.nanoTime();
while (unit.toNanos(time) > System.nanoTime() - start)
long start = NanoTime.now();
while (NanoTime.since(start) < unit.toNanos(time))
{
Connection connection = supplier.get();
if (connection != null)

View File

@ -70,7 +70,6 @@ public class HttpClientProxyProtocolTest
clientThreads.setName("client");
client = new HttpClient();
client.setExecutor(clientThreads);
client.setRemoveIdleDestinations(false);
client.start();
}

View File

@ -78,6 +78,7 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.hamcrest.Matchers;
@ -87,6 +88,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
@ -119,9 +121,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
long start = System.nanoTime();
long start = NanoTime.now();
HttpConnectionOverHTTP connection = null;
while (connection == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
while (connection == null && NanoTime.secondsSince(start) < 5)
{
connection = (HttpConnectionOverHTTP)connectionPool.getIdleConnections().peek();
TimeUnit.MILLISECONDS.sleep(10);
@ -512,6 +514,41 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testRetryWithDestinationIdleTimeoutEnabled(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
client.stop();
client.setDestinationIdleTimeout(1000);
client.setIdleTimeout(1000);
client.setMaxConnectionsPerDestination(1);
client.start();
try (StacklessLogging ignored = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class))
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/one")
.send();
int idleTimeout = 100;
Thread.sleep(idleTimeout * 2);
// After serving a request over a connection that hasn't timed out, serving a second
// request with a shorter idle timeout will make the connection timeout immediately
// after being taken out of the pool. This triggers the retry mechanism.
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/two")
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.send();
}
// Wait for the sweeper to remove the idle HttpDestination.
await().atMost(5, TimeUnit.SECONDS).until(() -> client.getDestinations().isEmpty());
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testExchangeIsCompleteOnlyWhenBothRequestAndResponseAreComplete(Scenario scenario) throws Exception
@ -550,7 +587,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
.file(file)
.onRequestSuccess(request ->
{
requestTime.set(System.nanoTime());
requestTime.set(NanoTime.now());
latch.countDown();
})
.send(new Response.Listener.Adapter()
@ -558,22 +595,22 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@Override
public void onSuccess(Response response)
{
responseTime.set(System.nanoTime());
responseTime.set(NanoTime.now());
latch.countDown();
}
@Override
public void onComplete(Result result)
{
exchangeTime.set(System.nanoTime());
exchangeTime.set(NanoTime.now());
latch.countDown();
}
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertTrue(requestTime.get() <= exchangeTime.get());
assertTrue(responseTime.get() <= exchangeTime.get());
assertTrue(NanoTime.isBeforeOrSame(requestTime.get(), exchangeTime.get()));
assertTrue(NanoTime.isBeforeOrSame(responseTime.get(), exchangeTime.get()));
// Give some time to the server to consume the request content
// This is just to avoid exception traces in the test output

View File

@ -31,6 +31,7 @@ import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Test;
@ -73,12 +74,7 @@ public class HttpClientUploadDuringServerShutdownTest
chunk.release();
if (chunk.isLast())
break;
long now = System.nanoTime();
long sleep = TimeUnit.MICROSECONDS.toNanos(1);
while (System.nanoTime() < now + sleep)
{
Thread.onSpinWait();
}
NanoTime.spinWait(TimeUnit.MICROSECONDS.toNanos(1));
}
}
}

View File

@ -0,0 +1,21 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Demo Handler
[tags]
demo
handler
[depends]
server
[files]
basehome:modules/demo.d/demo-handler.xml|etc/demo-handler.xml
maven://org.eclipse.jetty.demos/jetty-demo-handler/${jetty.version}/jar|lib/jetty-demo-handler.jar
[xml]
etc/demo-handler.xml
[lib]
lib/jetty-demo-handler.jar

View File

@ -1,8 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="helloDemo" class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="handler">
<New class="org.eclipse.jetty.demo.HelloHandler" />
</Set>
</Configure>
<Configure id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
<Call name="addHandler">
<Arg>
<New id="helloHandler" class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="handler">
<New class="org.eclipse.jetty.demo.HelloHandler" />
</Set>
<Set name="contextPath">/demo-handler</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
@ -67,7 +68,7 @@ public class HttpStreamOverFCGI implements HttpStream
_generator = generator;
_httpChannel = httpChannel;
_id = id;
_nanoTime = System.nanoTime();
_nanoTime = NanoTime.now();
}
public HttpChannel getHttpChannel()

View File

@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -127,11 +128,13 @@ public class CachingContentFactory implements HttpContent.ContentFactory
// Scan the entire cache and generate an ordered list by last accessed time.
SortedSet<CachingHttpContent> sorted = new TreeSet<>((c1, c2) ->
{
if (c1._lastAccessed != c2._lastAccessed)
return Long.compare(c1._lastAccessed, c2._lastAccessed);
long delta = NanoTime.elapsed(c2._lastAccessed, c1._lastAccessed);
if (delta != 0)
return delta < 0 ? -1 : 1;
if (c1._contentLengthValue < c2._contentLengthValue)
return -1;
delta = c1._contentLengthValue - c2._contentLengthValue;
if (delta != 0)
return delta < 0 ? -1 : 1;
return c1._cacheKey.compareTo(c2._cacheKey);
});
@ -254,7 +257,7 @@ public class CachingContentFactory implements HttpContent.ContentFactory
_cacheKey = key;
_buffer = byteBuffer;
_lastModifiedValue = Files.getLastModifiedTime(httpContent.getResource().getPath());
_lastAccessed = System.nanoTime();
_lastAccessed = NanoTime.now();
}
long calculateSize()
@ -287,7 +290,7 @@ public class CachingContentFactory implements HttpContent.ContentFactory
FileTime lastModifiedTime = Files.getLastModifiedTime(_delegate.getResource().getPath());
if (lastModifiedTime.equals(_lastModifiedValue))
{
_lastAccessed = System.nanoTime();
_lastAccessed = NanoTime.now();
return true;
}
}

View File

@ -22,6 +22,7 @@ import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
@ -112,7 +113,7 @@ public class HttpCookie
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, Collections.singletonMap("SameSite", sameSite == null ? null : sameSite.getAttributeValue()));
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, Map<String, String> attributes)
{
_name = name;
@ -124,24 +125,24 @@ public class HttpCookie
_secure = secure;
_comment = comment;
_version = version;
_expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
_expiration = maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(maxAge);
_attributes = (attributes == null ? Collections.emptyMap() : attributes);
}
public HttpCookie(String name, String value, int version, Map<String, String> attributes)
{
_name = name;
_value = value;
_value = value;
_version = version;
_attributes = (attributes == null ? Collections.emptyMap() : new TreeMap<>(attributes));
//remove all of the well-known attributes, leaving only those pass-through ones
_domain = _attributes.remove("Domain");
_path = _attributes.remove("Path");
String tmp = _attributes.remove("Max-Age");
_maxAge = StringUtil.isBlank(tmp) ? -1L : Long.valueOf(tmp);
_expiration = _maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(_maxAge);
_expiration = _maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(_maxAge);
_httpOnly = Boolean.parseBoolean(_attributes.remove("HttpOnly"));
_secure = Boolean.parseBoolean(_attributes.remove("Secure"));
_comment = _attributes.remove("Comment");
@ -236,7 +237,7 @@ public class HttpCookie
*/
public boolean isExpired(long timeNanos)
{
return _expiration >= 0 && timeNanos >= _expiration;
return _expiration != -1 && NanoTime.isBefore(_expiration, timeNanos);
}
/**
@ -431,7 +432,7 @@ public class HttpCookie
buf.append("; Secure");
if (_httpOnly)
buf.append("; HttpOnly");
String sameSite = _attributes.get("SameSite");
if (sameSite != null)
{
@ -440,7 +441,7 @@ public class HttpCookie
}
//Add all other attributes
_attributes.entrySet().stream().filter(e -> !"SameSite".equals(e.getKey())).forEach(e ->
_attributes.entrySet().stream().filter(e -> !"SameSite".equals(e.getKey())).forEach(e ->
{
buf.append("; " + e.getKey() + "=");
buf.append(e.getValue());
@ -510,21 +511,21 @@ public class HttpCookie
throw new IllegalStateException(e);
}
}
/**
* Extract the bare minimum of info from a Set-Cookie header string.
*
*
* Ideally this method should not be necessary, however as java.net.HttpCookie
* does not yet support generic attributes, we have to use it in a minimal
* fashion. When it supports attributes, we could look at reverting to a
* constructor on o.e.j.h.HttpCookie to take the set-cookie header string.
*
*
* @param setCookieHeader the header as a string
* @return a map containing the name, value, domain, path. max-age of the set cookie header
*/
public static Map<String, String> extractBasics(String setCookieHeader)
{
//Parse the bare minimum
//Parse the bare minimum
List<java.net.HttpCookie> cookies = java.net.HttpCookie.parse(setCookieHeader);
if (cookies.size() != 1)
return Collections.emptyMap();
@ -537,10 +538,10 @@ public class HttpCookie
fields.put("max-age", Long.toString(cookie.getMaxAge()));
return fields;
}
/**
* Check if the Set-Cookie header represented as a string is for the name, domain and path given.
*
*
* @param setCookieHeader a Set-Cookie header
* @param name the cookie name to check
* @param domain the cookie domain to check
@ -549,18 +550,18 @@ public class HttpCookie
*/
public static boolean match(String setCookieHeader, String name, String domain, String path)
{
//Parse the bare minimum
//Parse the bare minimum
List<java.net.HttpCookie> cookies = java.net.HttpCookie.parse(setCookieHeader);
if (cookies.size() != 1)
return false;
java.net.HttpCookie cookie = cookies.get(0);
return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path);
}
/**
* Check if the HttpCookie is for the given name, domain and path.
*
*
* @param cookie the jetty HttpCookie to check
* @param name the cookie name to check
* @param domain the cookie domain to check
@ -573,10 +574,10 @@ public class HttpCookie
return false;
return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path);
}
/**
* Check if all old parameters match the new parameters.
*
*
* @param oldName
* @param oldDomain
* @param oldPath
@ -594,7 +595,7 @@ public class HttpCookie
}
else if (!oldName.equals(newName))
return false;
if (oldDomain == null)
{
if (newDomain != null)
@ -610,7 +611,7 @@ public class HttpCookie
}
else if (!oldPath.equals(newPath))
return false;
return true;
}
@ -685,7 +686,7 @@ public class HttpCookie
return _cookie;
}
}
/**
* Check that samesite is set on the cookie. If not, use a
* context default value, if one has been set.

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.http;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringTokenizer;
@ -363,12 +364,19 @@ public class HttpField
}
public String[] getValues()
{
List<String> values = getValueList();
if (values == null)
return null;
return values.toArray(String[]::new);
}
public List<String> getValueList()
{
if (_value == null)
return null;
QuotedCSV list = new QuotedCSV(false, _value);
return list.getValues().toArray(new String[list.size()]);
return list.getValues();
}
@Override

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -215,21 +216,21 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
protected void onSessionStalled(Session session)
{
sessionStall.set(System.nanoTime());
sessionStall.set(NanoTime.now());
if (LOG.isDebugEnabled())
LOG.debug("Session stalled {}", session);
}
protected void onStreamStalled(Stream stream)
{
streamsStalls.put(stream, System.nanoTime());
streamsStalls.put(stream, NanoTime.now());
if (LOG.isDebugEnabled())
LOG.debug("Stream stalled {}", stream);
}
protected void onSessionUnstalled(Session session)
{
long stallTime = System.nanoTime() - sessionStall.getAndSet(0);
long stallTime = NanoTime.since(sessionStall.getAndSet(0));
sessionStallTime.addAndGet(stallTime);
if (LOG.isDebugEnabled())
LOG.debug("Session unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), session);
@ -240,7 +241,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
Long time = streamsStalls.remove(stream);
if (time != null)
{
long stallTime = System.nanoTime() - time;
long stallTime = NanoTime.since(time);
streamsStallTime.addAndGet(stallTime);
if (LOG.isDebugEnabled())
LOG.debug("Stream unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), stream);
@ -253,7 +254,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
long pastStallTime = sessionStallTime.get();
long currentStallTime = sessionStall.get();
if (currentStallTime != 0)
currentStallTime = System.nanoTime() - currentStallTime;
currentStallTime = NanoTime.since(currentStallTime);
return TimeUnit.NANOSECONDS.toMillis(pastStallTime + currentStallTime);
}
@ -261,8 +262,8 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
public long getStreamsStallTime()
{
long pastStallTime = streamsStallTime.get();
long now = System.nanoTime();
long currentStallTime = streamsStalls.values().stream().reduce(0L, (result, time) -> now - time);
long now = NanoTime.now();
long currentStallTime = streamsStalls.values().stream().reduce(0L, (result, time) -> NanoTime.elapsed(time, now));
return TimeUnit.NANOSECONDS.toMillis(pastStallTime + currentStallTime);
}

View File

@ -19,6 +19,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.NanoTime;
/**
* <p>An implementation of {@link RateControl} that limits the number of
@ -65,13 +66,13 @@ public class WindowRateControl implements RateControl
@Override
public boolean onEvent(Object event)
{
long now = System.nanoTime();
long now = NanoTime.now();
while (true)
{
Long time = events.peek();
if (time == null)
break;
if (now < time)
if (NanoTime.isBefore(now, time))
break;
if (events.remove(time))
size.decrementAndGet();

View File

@ -29,7 +29,6 @@ import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@ -68,6 +67,7 @@ import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback;
import org.eclipse.jetty.util.MathUtils;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -1016,7 +1016,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements Session
private void notIdle()
{
streamsState.idleTime = System.nanoTime();
streamsState.idleNanoTime = NanoTime.now();
}
public void onFrame(Frame frame)
@ -1474,7 +1474,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements Session
private final Queue<Slot> slots = new ArrayDeque<>();
// Must be incremented with the lock held.
private final AtomicLong streamCount = new AtomicLong();
private long idleTime = System.nanoTime();
private long idleNanoTime = NanoTime.now();
private CloseState closed = CloseState.NOT_CLOSED;
private Runnable zeroStreamsAction;
private GoAwayFrame goAwayRecv;
@ -1814,7 +1814,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements Session
{
case NOT_CLOSED ->
{
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTime);
long elapsed = NanoTime.millisSince(idleNanoTime);
if (elapsed < endPoint.getIdleTimeout())
return false;
notify = true;

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
@ -65,7 +66,7 @@ public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dum
private final AtomicReference<CloseState> closeState = new AtomicReference<>(CloseState.NOT_CLOSED);
private final AtomicInteger sendWindow = new AtomicInteger();
private final AtomicInteger recvWindow = new AtomicInteger();
private final long timeStamp = System.nanoTime();
private final long creationNanoTime = NanoTime.now();
private final HTTP2Session session;
private final int streamId;
private final MetaData.Request request;
@ -268,7 +269,7 @@ public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dum
{
long idleTimeout = getIdleTimeout();
if (idleTimeout > 0)
expireNanoTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
expireNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
}
@Override
@ -852,7 +853,7 @@ public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dum
localReset,
remoteReset,
closeState,
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - timeStamp),
NanoTime.millisSince(creationNanoTime),
attachment);
}

View File

@ -24,6 +24,7 @@ import org.eclipse.jetty.http2.internal.generator.PingGenerator;
import org.eclipse.jetty.http2.internal.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@ -133,7 +134,7 @@ public class PingGenerateParseTest
parser.init(UnaryOperator.identity());
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
PingFrame ping = new PingFrame(System.nanoTime(), true);
PingFrame ping = new PingFrame(NanoTime.now(), true);
generator.generate(lease, ping);
for (ByteBuffer buffer : lease.getByteBuffers())

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
@ -38,7 +37,7 @@ public class HpackTest
{
static final HttpField ServerJetty = new PreEncodedHttpField(HttpHeader.SERVER, "jetty");
static final HttpField XPowerJetty = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, "jetty");
static final HttpField Date = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())));
static final HttpField Date = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(System.currentTimeMillis()));
@Test
public void encodeDecodeResponseTest() throws Exception

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
@ -51,7 +52,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
private final HTTP2ServerConnection _connection;
private final HttpChannel _httpChannel;
private final HTTP2Stream _stream;
private final long _nanoTimeStamp;
private final long _nanoTime;
private Content.Chunk _chunk;
private MetaData.Response _metaData;
private boolean committed;
@ -62,7 +63,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
_connection = connection;
_httpChannel = httpChannel;
_stream = stream;
_nanoTimeStamp = System.nanoTime();
_nanoTime = NanoTime.now();
}
@Override
@ -74,7 +75,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
@Override
public long getNanoTimeStamp()
{
return _nanoTimeStamp;
return _nanoTime;
}
public Runnable onRequest(HeadersFrame frame)

View File

@ -63,6 +63,7 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
@ -517,11 +518,10 @@ public class MaxConcurrentStreamsTest extends AbstractTest
// Wait until TCP congested.
assertTrue(clientEndPointLatch.await(5, TimeUnit.SECONDS));
AbstractEndPoint clientEndPoint = clientEndPointRef.get();
long start = System.nanoTime();
long start = NanoTime.now();
while (!clientEndPoint.getWriteFlusher().isPending())
{
long elapsed = System.nanoTime() - start;
assertThat(TimeUnit.NANOSECONDS.toSeconds(elapsed), Matchers.lessThan(15L));
assertThat(NanoTime.secondsSince(start), Matchers.lessThan(15L));
Thread.sleep(100);
}
// Wait for the selector to update the SelectionKey to OP_WRITE.

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.junit.jupiter.api.Test;
@ -119,14 +120,12 @@ public class SessionFailureTest extends AbstractTest
assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS));
long start = System.nanoTime();
long now = System.nanoTime();
long start = NanoTime.now();
while (((HTTP2Session)session).getEndPoint().isOpen())
{
assertThat(TimeUnit.NANOSECONDS.toSeconds(now - start), lessThanOrEqualTo(5L));
assertThat(NanoTime.secondsSince(start), lessThanOrEqualTo(5L));
Thread.sleep(10);
now = System.nanoTime();
}
}
}

View File

@ -37,6 +37,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
import org.hamcrest.Matchers;
@ -100,7 +101,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest
}, iterations * factor, TimeUnit.MILLISECONDS);
long successes = 0;
long begin = System.nanoTime();
long begin = NanoTime.now();
for (int i = 0; i < iterations; ++i)
{
boolean success = test(session, latch);
@ -109,10 +110,9 @@ public class SmallThreadPoolLoadTest extends AbstractTest
}
assertTrue(latch.await(iterations, TimeUnit.SECONDS));
long end = System.nanoTime();
assertThat(successes, Matchers.greaterThan(0L));
task.cancel();
long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
long elapsed = NanoTime.millisSince(begin);
logger.info("{} requests in {} ms, {}/{} success/failure, {} req/s",
iterations, elapsed,
successes, iterations - successes,

View File

@ -78,6 +78,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Disabled;
@ -1080,11 +1081,10 @@ public class StreamResetTest extends AbstractTest
private void waitUntilTCPCongested(WriteFlusher flusher) throws TimeoutException, InterruptedException
{
long start = System.nanoTime();
long start = NanoTime.now();
while (!flusher.isPending())
{
long elapsed = System.nanoTime() - start;
if (TimeUnit.NANOSECONDS.toSeconds(elapsed) > 15)
if (NanoTime.secondsSince(start) > 15)
throw new TimeoutException();
Thread.sleep(100);
}

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
@ -109,7 +110,7 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
{
long idleTimeout = getIdleTimeout();
if (idleTimeout > 0)
expireNanoTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
expireNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
}
boolean onIdleTimeout(TimeoutException timeout)
@ -308,7 +309,7 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
hashCode(),
getId(),
hasDemand(),
TimeUnit.NANOSECONDS.toMillis(expireNanoTime - System.nanoTime()),
NanoTime.millisSince(expireNanoTime),
getSession()
);
}

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,7 +46,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
private static final Logger LOG = LoggerFactory.getLogger(HttpStreamOverHTTP3.class);
private final AutoLock lock = new AutoLock();
private final long nanoTime = System.nanoTime();
private final long nanoTime = NanoTime.now();
private final ServerHTTP3StreamConnection connection;
private final HttpChannel httpChannel;
private final HTTP3StreamServer stream;

View File

@ -23,6 +23,7 @@ import java.util.function.Consumer;
import java.util.function.IntConsumer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -176,7 +177,7 @@ abstract class AbstractByteBufferPool implements ByteBufferPool
private final int _capacity;
private final int _maxSize;
private final AtomicInteger _size;
private final AtomicLong _lastUpdate = new AtomicLong(System.nanoTime());
private final AtomicLong _lastUpdate = new AtomicLong(NanoTime.now());
private final IntConsumer _memoryFunction;
@Deprecated
@ -223,7 +224,7 @@ abstract class AbstractByteBufferPool implements ByteBufferPool
void resetUpdateTime()
{
_lastUpdate.lazySet(System.nanoTime());
_lastUpdate.lazySet(NanoTime.now());
}
public void clear()

View File

@ -22,6 +22,7 @@ import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
@ -208,10 +209,10 @@ public class ArrayByteBufferPool extends AbstractByteBufferPool implements Dumpa
Bucket bucket = buckets[i];
if (bucket.isEmpty())
continue;
long lastUpdate = bucket.getLastUpdate();
if (lastUpdate < oldest)
long lastUpdateNanoTime = bucket.getLastUpdate();
if (oldest == Long.MAX_VALUE || NanoTime.isBefore(lastUpdateNanoTime, oldest))
{
oldest = lastUpdate;
oldest = lastUpdateNanoTime;
index = i;
}
}

View File

@ -21,6 +21,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -353,7 +354,7 @@ public class ArrayRetainableByteBufferPool implements RetainableByteBufferPool,
{
if (LOG.isDebugEnabled())
LOG.debug("evicting {} bytes from {} pools", excess, (direct ? "direct" : "heap"));
long now = System.nanoTime();
long now = NanoTime.now();
long totalClearedCapacity = 0L;
RetainedBucket[] buckets = direct ? _direct : _indirect;
@ -413,8 +414,8 @@ public class ArrayRetainableByteBufferPool implements RetainableByteBufferPool,
{
if (oldestEntry != null)
{
long entryAge = now - entry.getPooled().getLastUpdate();
if (entryAge > now - oldestEntry.getPooled().getLastUpdate())
long entryAge = NanoTime.elapsed(entry.getPooled().getLastUpdate(), now);
if (entryAge > NanoTime.elapsed(oldestEntry.getPooled().getLastUpdate(), now))
oldestEntry = entry;
}
else

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.io;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
@ -91,7 +92,7 @@ public abstract class CyclicTimeout implements Destroyable
*/
public boolean schedule(long delay, TimeUnit units)
{
long now = System.nanoTime();
long now = NanoTime.now();
long newTimeoutAt = now + units.toNanos(delay);
Wakeup newWakeup = null;
@ -103,7 +104,7 @@ public abstract class CyclicTimeout implements Destroyable
// Is the current wakeup good to use? ie before our timeout time?
Wakeup wakeup = timeout._wakeup;
if (wakeup == null || wakeup._at > newTimeoutAt)
if (wakeup == null || NanoTime.isBefore(newTimeoutAt, wakeup._at))
// No, we need an earlier wakeup.
wakeup = newWakeup = new Wakeup(newTimeoutAt, wakeup);
@ -114,7 +115,7 @@ public abstract class CyclicTimeout implements Destroyable
LOG.debug("Installed timeout in {} ms, {} wake up in {} ms",
units.toMillis(delay),
newWakeup != null ? "new" : "existing",
TimeUnit.NANOSECONDS.toMillis(wakeup._at - now));
NanoTime.millisElapsed(now, wakeup._at));
}
break;
}
@ -192,7 +193,7 @@ public abstract class CyclicTimeout implements Destroyable
return String.format("%s@%x:%dms,%s",
getClass().getSimpleName(),
hashCode(),
TimeUnit.NANOSECONDS.toMillis(_at - System.nanoTime()),
NanoTime.millisUntil(_at),
_wakeup);
}
}
@ -214,7 +215,7 @@ public abstract class CyclicTimeout implements Destroyable
private void schedule(long now)
{
_task.compareAndSet(null, _scheduler.schedule(this, _at - now, TimeUnit.NANOSECONDS));
_task.compareAndSet(null, _scheduler.schedule(this, NanoTime.elapsed(now, _at), TimeUnit.NANOSECONDS));
}
private void destroy()
@ -227,7 +228,7 @@ public abstract class CyclicTimeout implements Destroyable
@Override
public void run()
{
long now = System.nanoTime();
long now = NanoTime.now();
Wakeup newWakeup = null;
boolean hasExpired = false;
while (true)
@ -258,7 +259,7 @@ public abstract class CyclicTimeout implements Destroyable
wakeup = wakeup._next;
Timeout newTimeout;
if (timeout._at <= now)
if (NanoTime.isBeforeOrSame(timeout._at, now))
{
// We have timed out!
hasExpired = true;
@ -268,7 +269,7 @@ public abstract class CyclicTimeout implements Destroyable
{
// We have not timed out, but we are set to!
// Is the current wakeup good to use? ie before our timeout time?
if (wakeup == null || wakeup._at >= timeout._at)
if (wakeup == null || NanoTime.isBefore(timeout._at, wakeup._at))
// No, we need an earlier wakeup.
wakeup = newWakeup = new Wakeup(timeout._at, wakeup);
newTimeout = new Timeout(timeout._at, wakeup);
@ -299,7 +300,7 @@ public abstract class CyclicTimeout implements Destroyable
return String.format("%s@%x:%dms->%s",
getClass().getSimpleName(),
hashCode(),
_at == MAX_VALUE ? _at : TimeUnit.NANOSECONDS.toMillis(_at - System.nanoTime()),
_at == MAX_VALUE ? _at : NanoTime.millisUntil(_at),
_next);
}
}

View File

@ -17,6 +17,7 @@ import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
@ -75,7 +76,7 @@ public abstract class CyclicTimeouts<T extends CyclicTimeouts.Expirable> impleme
if (LOG.isDebugEnabled())
LOG.debug("Timeouts check for {}", this);
long now = System.nanoTime();
long now = NanoTime.now();
long earliest = Long.MAX_VALUE;
// Reset the earliest timeout so we can expire again.
// A concurrent call to schedule(long) may lose an
@ -95,12 +96,12 @@ public abstract class CyclicTimeouts<T extends CyclicTimeouts.Expirable> impleme
long expiresAt = expirable.getExpireNanoTime();
if (LOG.isDebugEnabled())
LOG.debug("Entity {} expires in {} ms for {}", expirable, TimeUnit.NANOSECONDS.toMillis(expiresAt - now), this);
LOG.debug("Entity {} expires in {} ms for {}", expirable, NanoTime.millisElapsed(now, expiresAt), this);
if (expiresAt == -1)
continue;
if (expiresAt <= now)
if (NanoTime.isBeforeOrSame(expiresAt, now))
{
boolean remove = onExpired(expirable);
if (LOG.isDebugEnabled())
@ -109,11 +110,12 @@ public abstract class CyclicTimeouts<T extends CyclicTimeouts.Expirable> impleme
iterator.remove();
continue;
}
earliest = Math.min(earliest, expiresAt);
earliest = Math.min(earliest, NanoTime.elapsed(now, expiresAt));
}
if (earliest < Long.MAX_VALUE)
schedule(earliest);
schedule(now + earliest);
}
/**
@ -133,12 +135,12 @@ public abstract class CyclicTimeouts<T extends CyclicTimeouts.Expirable> impleme
// Schedule a timeout for the earliest entity that may expire.
// When the timeout expires, scan the entities for the next
// earliest entity that may expire, and reschedule a new timeout.
long prevEarliest = earliestTimeout.getAndUpdate(t -> Math.min(t, expiresAt));
long prevEarliest = earliestTimeout.getAndUpdate(t -> NanoTime.isBefore(t, expiresAt) ? t : expiresAt);
long expires = expiresAt;
while (expires < prevEarliest)
while (NanoTime.isBefore(expires, prevEarliest))
{
// A new entity expires earlier than previous entities, schedule it.
long delay = Math.max(0, expires - System.nanoTime());
long delay = Math.max(0, NanoTime.until(expires));
if (LOG.isDebugEnabled())
LOG.debug("Scheduling timeout in {} ms for {}", TimeUnit.NANOSECONDS.toMillis(delay), this);
schedule(cyclicTimeout, delay, TimeUnit.NANOSECONDS);
@ -168,9 +170,9 @@ public abstract class CyclicTimeouts<T extends CyclicTimeouts.Expirable> impleme
{
/**
* <p>Returns the expiration time in nanoseconds.</p>
* <p>The value to return must be calculated taking into account {@link System#nanoTime()},
* <p>The value to return must be calculated taking into account the current nanoTime,
* for example:</p>
* {@code expireNanoTime = System.nanoTime() + timeoutNanos}
* {@code expireNanoTime = NanoTime.now() + timeoutNanos}
* <p>Returning {@link Long#MAX_VALUE} indicates that this entity does not expire.</p>
*
* @return the expiration time in nanoseconds, or {@link Long#MAX_VALUE} if this entity does not expire

View File

@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,7 +36,7 @@ public abstract class IdleTimeout
private final Scheduler _scheduler;
private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
private volatile long _idleTimeout;
private volatile long _idleTimestamp = System.nanoTime();
private volatile long _idleNanoTime = NanoTime.now();
/**
* @param scheduler A scheduler used to schedule checks for the idle timeout.
@ -55,7 +56,7 @@ public abstract class IdleTimeout
*/
public long getIdleFor()
{
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - _idleTimestamp);
return NanoTime.millisSince(_idleNanoTime);
}
/**
@ -100,7 +101,7 @@ public abstract class IdleTimeout
*/
public void notIdle()
{
_idleTimestamp = System.nanoTime();
_idleNanoTime = NanoTime.now();
}
private void idleCheck()
@ -147,8 +148,8 @@ public abstract class IdleTimeout
{
if (isOpen())
{
long idleTimestamp = _idleTimestamp;
long idleElapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTimestamp);
long idleNanoTime = _idleNanoTime;
long idleElapsed = NanoTime.millisSince(idleNanoTime);
long idleTimeout = getIdleTimeout();
long idleLeft = idleTimeout - idleElapsed;

View File

@ -13,6 +13,8 @@
package org.eclipse.jetty.io;
import org.eclipse.jetty.util.NanoTime;
/**
* Extension of the {@link ArrayByteBufferPool} whose bucket sizes increase exponentially instead of linearly.
* Each bucket will be double the size of the previous bucket, this decreases the amounts of buckets required
@ -113,10 +115,10 @@ public class LogarithmicArrayByteBufferPool extends ArrayByteBufferPool
Bucket bucket = buckets[i];
if (bucket.isEmpty())
continue;
long lastUpdate = bucket.getLastUpdate();
if (lastUpdate < oldest)
long lastUpdateNanoTime = bucket.getLastUpdate();
if (oldest == Long.MAX_VALUE || NanoTime.isBefore(lastUpdateNanoTime, oldest))
{
oldest = lastUpdate;
oldest = lastUpdateNanoTime;
index = i;
}
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
@ -205,10 +206,10 @@ public class MappedByteBufferPool extends AbstractByteBufferPool implements Dump
if (bucket.isEmpty())
continue;
long lastUpdate = bucket.getLastUpdate();
if (lastUpdate < oldest)
long lastUpdateNanoTime = bucket.getLastUpdate();
if (oldest == Long.MAX_VALUE || NanoTime.isBefore(lastUpdateNanoTime, oldest))
{
oldest = lastUpdate;
oldest = lastUpdateNanoTime;
index = entry.getKey();
}
}

View File

@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
/**
* <p>A pooled ByteBuffer which maintains a reference count that is
@ -35,7 +36,7 @@ public class RetainableByteBuffer extends Retainable.ReferenceCounter
{
private final ByteBuffer buffer;
private final Consumer<RetainableByteBuffer> releaser;
private final AtomicLong lastUpdate = new AtomicLong(System.nanoTime());
private final AtomicLong lastUpdate = new AtomicLong(NanoTime.now());
RetainableByteBuffer(ByteBuffer buffer, Consumer<RetainableByteBuffer> releaser)
{
@ -75,7 +76,7 @@ public class RetainableByteBuffer extends Retainable.ReferenceCounter
boolean released = super.release();
if (released)
{
lastUpdate.setOpaque(System.nanoTime());
lastUpdate.setOpaque(NanoTime.now());
releaser.accept(this);
}
return released;

View File

@ -21,6 +21,7 @@ import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.jupiter.api.AfterEach;
@ -283,7 +284,7 @@ public class ByteArrayEndPointTest
assertEquals("test", BufferUtil.toString(buffer));
// Wait for a read timeout.
long start = System.nanoTime();
long start = NanoTime.now();
fcb = new FutureCallback();
endp.fillInterested(fcb);
try
@ -295,7 +296,7 @@ public class ByteArrayEndPointTest
{
assertThat(t.getCause(), instanceOf(TimeoutException.class));
}
assertThat(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), greaterThan(halfIdleTimeout));
assertThat(NanoTime.millisSince(start), greaterThan(halfIdleTimeout));
assertThat("Endpoint open", endp.isOpen(), is(true));
}
}

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.io;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -26,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class CyclicTimeoutTest
{
private volatile boolean _expired;
private ScheduledExecutorScheduler _timer = new ScheduledExecutorScheduler();
private final ScheduledExecutorScheduler _timer = new ScheduledExecutorScheduler();
private CyclicTimeout _timeout;
@BeforeEach
@ -132,11 +133,11 @@ public class CyclicTimeoutTest
}
@Test
public void testBusy() throws Exception
public void testBusy()
{
long testUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(2000);
assertTrue(_timeout.schedule(500, TimeUnit.MILLISECONDS));
while (System.nanoTime() < testUntil)
long start = NanoTime.now();
while (NanoTime.secondsSince(start) < 2)
_timeout.schedule(500, TimeUnit.MILLISECONDS);
_timeout.cancel();
assertFalse(_expired);

View File

@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
@ -231,25 +232,25 @@ public class CyclicTimeoutsTest
return new ConstantExpirable(delay, unit);
}
private final long expireNanos;
private final long expireNanoTime;
private final String asString;
private ConstantExpirable()
{
this.expireNanos = Long.MAX_VALUE;
this.expireNanoTime = Long.MAX_VALUE;
this.asString = "noexp";
}
public ConstantExpirable(long delay, TimeUnit unit)
{
this.expireNanos = System.nanoTime() + unit.toNanos(delay);
this.expireNanoTime = NanoTime.now() + unit.toNanos(delay);
this.asString = String.valueOf(unit.toMillis(delay));
}
@Override
public long getExpireNanoTime()
{
return expireNanos;
return expireNanoTime;
}
@Override

View File

@ -13,9 +13,9 @@
package org.eclipse.jetty.io;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -126,8 +126,8 @@ public class IdleTimeoutTest
assertNull(_expired);
_timeout.setIdleTimeout(100);
long start = System.nanoTime();
while (_expired == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
long start = NanoTime.now();
while (_expired == null && NanoTime.secondsSince(start) < 5)
{
Thread.sleep(200);
}

View File

@ -43,6 +43,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
@ -99,8 +100,8 @@ public class SocketChannelEndPointTest
private CountDownLatch _lastEndPointLatch;
// Must be volatile or the test may fail spuriously
private AtomicInteger _blockAt = new AtomicInteger(0);
private AtomicInteger _writeCount = new AtomicInteger(1);
private final AtomicInteger _blockAt = new AtomicInteger(0);
private final AtomicInteger _writeCount = new AtomicInteger(1);
public void init(Scenario scenario) throws Exception
{
@ -152,10 +153,9 @@ public class SocketChannelEndPointTest
// wait for read timeout
client.setSoTimeout(500);
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
assertThrows(SocketTimeoutException.class, () -> client.getInputStream().read());
long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start;
assertThat("timeout duration", duration, greaterThanOrEqualTo(400L));
assertThat(NanoTime.millisSince(start), greaterThanOrEqualTo(400L));
// write then shutdown
client.getOutputStream().write("Goodbye Cruel TLS".getBytes(StandardCharsets.UTF_8));
@ -207,9 +207,9 @@ public class SocketChannelEndPointTest
}
// wait for read timeout
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
assertThrows(SocketTimeoutException.class, () -> client.getInputStream().read());
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start >= 400);
assertThat(NanoTime.millisSince(start), greaterThanOrEqualTo(400L));
// write then shutdown
client.getOutputStream().write("Goodbye Cruel TLS".getBytes(StandardCharsets.UTF_8));
@ -255,10 +255,9 @@ public class SocketChannelEndPointTest
_lastEndPoint.setIdleTimeout(10 * specifiedTimeout);
Thread.sleep((11 * specifiedTimeout) / 10);
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
assertThrows(SocketTimeoutException.class, () -> clientInputStream.read());
int elapsed = (int)(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start);
assertThat("Expected timeout", elapsed, greaterThanOrEqualTo(3 * specifiedTimeout / 4));
long start = NanoTime.now();
assertThrows(SocketTimeoutException.class, clientInputStream::read);
assertThat(NanoTime.millisSince(start), greaterThanOrEqualTo(3L * specifiedTimeout / 4));
// write remaining characters
clientOutputStream.write("90ABCDEF".getBytes(StandardCharsets.UTF_8));
@ -294,7 +293,7 @@ public class SocketChannelEndPointTest
BufferedOutputStream out = new BufferedOutputStream(client.getOutputStream());
final CountDownLatch latch = new CountDownLatch(writes);
final InputStream in = new BufferedInputStream(client.getInputStream());
final long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
final long start = NanoTime.now();
out.write(bytes);
out.write(count);
out.flush();
@ -326,7 +325,7 @@ public class SocketChannelEndPointTest
count1 = count1 * 10 + (b - '0');
b = in.read();
}
last = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
last = NanoTime.now();
//if (latch.getCount()%1000==0)
// System.out.println(writes-latch.getCount());
@ -336,12 +335,11 @@ public class SocketChannelEndPointTest
}
catch (Throwable e)
{
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long now = NanoTime.now();
System.err.println("count=" + count1);
System.err.println("latch=" + latch.getCount());
System.err.println("time=" + (now - start));
System.err.println("last=" + (now - last));
System.err.println("time=" + NanoTime.millisElapsed(start, now));
System.err.println("last=" + NanoTime.millisElapsed(last, now));
System.err.println("endp=" + _lastEndPoint);
System.err.println("conn=" + _lastEndPoint.getConnection());
@ -677,7 +675,6 @@ public class SocketChannelEndPointTest
// volatile int _blockAt = 0;
ByteBuffer _in = BufferUtil.allocate(32 * 1024);
ByteBuffer _out = BufferUtil.allocate(32 * 1024);
long _last = -1;
final CountDownLatch _latch;
public TestConnection(EndPoint endp, Executor executor, AtomicInteger blockAt, AtomicInteger writeCount)
@ -742,7 +739,6 @@ public class SocketChannelEndPointTest
EndPoint endPoint = getEndPoint();
try
{
_last = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
boolean progress = true;
while (progress)
{

View File

@ -36,5 +36,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,114 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.test.keystore;
import java.io.File;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.keystore.KeystoreGenerator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class KeystoreGeneratorTest
{
private Server _server;
private ServerConnector _connector;
private HttpClient _httpClient;
public KeystoreGeneratorTest()
{
}
@BeforeEach
public void before() throws Exception
{
// Generate a test keystore.
String password = "myKeystorePassword";
File targetTestingDir = MavenTestingUtils.getTargetTestingDir();
File myPassword = KeystoreGenerator.generateTestKeystore(targetTestingDir.getAbsolutePath(), password);
assertTrue(myPassword.exists());
// Configure the SslContextFactory and HttpConnectionFactory to use the keystore.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(myPassword.getAbsolutePath());
sslContextFactory.setKeyStorePassword(password);
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
HttpConfiguration httpsConfig = new HttpConfiguration();
SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
secureRequestCustomizer.setSniHostCheck(false);
httpsConfig.addCustomizer(secureRequestCustomizer);
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpsConfig);
// Start the server.
_server = new Server();
_connector = new ServerConnector(_server, sslConnectionFactory, httpConnectionFactory);
_server.addConnector(_connector);
_server.setHandler(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(200);
response.write(true, BufferUtil.toBuffer("success"), callback);
}
});
_server.start();
// Configure the client.
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
clientSslContextFactory.setTrustAll(true);
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(clientSslContextFactory);
_httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
_httpClient.start();
}
@AfterEach
public void after() throws Exception
{
_httpClient.stop();
_server.stop();
}
@Test
public void test() throws Exception
{
ContentResponse response = _httpClient.GET("https://localhost:" + _connector.getLocalPort());
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
assertThat(response.getContentAsString(), equalTo("success"));
}
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-core</artifactId>
<version>12.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-proxy</artifactId>
<packaging>jar</packaging>
<name>Jetty Core :: Proxy</name>
<properties>
<bundle-symbolic-name>${project.groupId}.proxy</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>jetty-http2-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-java-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>jetty-http2-client-transport</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,21 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
module org.eclipse.jetty.proxy
{
requires org.slf4j;
requires transitive org.eclipse.jetty.client;
requires transitive org.eclipse.jetty.server;
exports org.eclipse.jetty.proxy;
}

View File

@ -0,0 +1,717 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HttpCookieStore;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A {@link Handler} that can be used to implement a {@link Forward forward
* proxy ("proxy")} or a {@link Reverse reverse proxy ("gateway")} as defined by
* <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-2.3">RFC 7230</a>.</p>
* <p>This class uses {@link HttpClient} to send requests from the proxy to the server.</p>
* <p>The {@code HttpClient} instance is either
* {@link #setHttpClient(HttpClient) set explicitly}, or created implicitly.
* To customize the implicit {@code HttpClient} instance, applications can
* override {@link #newHttpClient()} and {@link #configureHttpClient(HttpClient)}.</p>
*
* @see Forward
* @see Reverse
*/
public abstract class ProxyHandler extends Handler.Processor
{
private static final Logger LOG = LoggerFactory.getLogger(ProxyHandler.class);
private static final String CLIENT_TO_PROXY_REQUEST_ATTRIBUTE = ProxyHandler.class.getName() + ".clientToProxyRequest";
private static final EnumSet<HttpHeader> HOP_HEADERS = EnumSet.of(
HttpHeader.CONNECTION,
HttpHeader.KEEP_ALIVE,
HttpHeader.PROXY_AUTHORIZATION,
HttpHeader.PROXY_AUTHENTICATE,
HttpHeader.PROXY_CONNECTION,
HttpHeader.TRANSFER_ENCODING,
HttpHeader.TE,
HttpHeader.TRAILER,
HttpHeader.UPGRADE
);
private HttpClient httpClient;
private String proxyToServerHost;
private String viaHost;
public HttpClient getHttpClient()
{
return httpClient;
}
public void setHttpClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
/**
* @return the proxy-to-server {@code Host} header value
*/
public String getProxyToServerHost()
{
return proxyToServerHost;
}
/**
* <p>Sets the value to use for the {@code Host} header in proxy-to-server requests.</p>
* <p>If {@code null}, the client-to-proxy value is used.</p>
*
* @param host the proxy-to-server {@code Host} header value
*/
public void setProxyToServerHost(String host)
{
this.proxyToServerHost = host;
}
/**
* @return the value to use for the {@code Via} header
*/
public String getViaHost()
{
return viaHost;
}
/**
* <p>Sets the value to use for the {@code Via} header in proxy-to-server requests.</p>
* <p>If {@code null}, the local host name is used.</p>
*
* @param viaHost the value to use for the {@code Via} header
*/
public void setViaHost(String viaHost)
{
this.viaHost = viaHost;
}
private static String viaHost()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException x)
{
return "localhost";
}
}
@Override
protected void doStart() throws Exception
{
if (httpClient == null)
setHttpClient(createHttpClient());
addBean(httpClient, true);
if (viaHost == null)
setViaHost(viaHost());
super.doStart();
}
private HttpClient createHttpClient()
{
HttpClient httpClient = newHttpClient();
configureHttpClient(httpClient);
LifeCycle.start(httpClient);
httpClient.getContentDecoderFactories().clear();
httpClient.getProtocolHandlers().clear();
return httpClient;
}
/**
* <p>Creates a new {@link HttpClient} instance, by default with a thread
* pool named {@code proxy-client} and with the
* {@link HttpClientTransportDynamic dynamic transport} configured only
* with HTTP/1.1.</p>
*
* @return a new {@code HttpClient} instance
*/
protected HttpClient newHttpClient()
{
ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
proxyClientThreads.setName("proxy-client");
clientConnector.setExecutor(proxyClientThreads);
return new HttpClient(new HttpClientTransportDynamic(clientConnector));
}
/**
* <p>Configures the {@link HttpClient} instance before it is started.</p>
*
* @param httpClient the {@code HttpClient} instance to configure
*/
protected void configureHttpClient(HttpClient httpClient)
{
httpClient.setFollowRedirects(false);
httpClient.setCookieStore(new HttpCookieStore.Empty());
}
protected static String requestId(Request clientToProxyRequest)
{
return String.valueOf(System.identityHashCode(clientToProxyRequest));
}
@Override
public void process(Request clientToProxyRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
if (LOG.isDebugEnabled())
LOG.debug("""
{} received C2P request
{}
{}""",
requestId(clientToProxyRequest),
clientToProxyRequest,
clientToProxyRequest.getHeaders());
HttpURI rewritten = rewriteHttpURI(clientToProxyRequest);
if (LOG.isDebugEnabled())
LOG.debug("{} URI rewrite {} => {}", requestId(clientToProxyRequest), clientToProxyRequest.getHttpURI(), rewritten);
var proxyToServerRequest = newProxyToServerRequest(clientToProxyRequest, rewritten);
copyRequestHeaders(clientToProxyRequest, proxyToServerRequest);
addProxyHeaders(clientToProxyRequest, proxyToServerRequest);
if (hasContent(clientToProxyRequest))
{
if (expects100Continue(clientToProxyRequest))
{
// TODO
}
else
{
var proxyToServerRequestContent = newProxyToServerRequestContent(clientToProxyRequest, proxyToClientResponse, proxyToServerRequest);
proxyToServerRequest.body(proxyToServerRequestContent);
}
}
sendProxyToServerRequest(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
}
/**
* <p>Rewrites the client-to-proxy request URI to the proxy-to-server request URI.</p>
*
* @param clientToProxyRequest the client-to-proxy request
* @return an {@code HttpURI} for the proxy-to-server request
*/
protected abstract HttpURI rewriteHttpURI(Request clientToProxyRequest);
protected org.eclipse.jetty.client.api.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
{
return getHttpClient().newRequest(newHttpURI.toURI())
.method(clientToProxyRequest.getMethod())
.attribute(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE, clientToProxyRequest);
}
protected void copyRequestHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
{
Set<String> headersToRemove = findConnectionHeaders(clientToProxyRequest);
for (HttpField clientToProxyRequestField : clientToProxyRequest.getHeaders())
{
HttpHeader clientToProxyRequestHeader = clientToProxyRequestField.getHeader();
if (HttpHeader.HOST == clientToProxyRequestHeader)
{
String host = getProxyToServerHost();
if (host != null)
{
proxyToServerRequest.headers(headers -> headers.put(HttpHeader.HOST, host));
continue;
}
}
if (HOP_HEADERS.contains(clientToProxyRequestHeader))
continue;
if (headersToRemove != null && headersToRemove.contains(clientToProxyRequestField.getLowerCaseName()))
continue;
proxyToServerRequest.headers(headers -> headers.add(clientToProxyRequestField));
}
}
private Set<String> findConnectionHeaders(Request clientToProxyRequest)
{
// Any header listed by the Connection header must be removed:
// http://tools.ietf.org/html/rfc7230#section-6.1.
Set<String> hopHeaders = null;
List<String> connectionHeaders = clientToProxyRequest.getHeaders().getValuesList(HttpHeader.CONNECTION);
for (String value : connectionHeaders)
{
String[] values = value.split(",");
for (String name : values)
{
name = name.trim().toLowerCase(Locale.ENGLISH);
if (hopHeaders == null)
hopHeaders = new HashSet<>();
hopHeaders.add(name);
}
}
return hopHeaders;
}
protected void addProxyHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
{
addViaHeader(clientToProxyRequest, proxyToServerRequest);
addForwardedHeader(clientToProxyRequest, proxyToServerRequest);
}
protected void addViaHeader(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
{
String protocol = clientToProxyRequest.getConnectionMetaData().getProtocol();
String[] parts = protocol.split("/", 2);
// Retain only the version if the protocol is HTTP.
String protocolPart = parts.length == 2 && "HTTP".equalsIgnoreCase(parts[0]) ? parts[1] : protocol;
String viaHeaderValue = protocolPart + " " + getViaHost();
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.VIA, (header, viaFields) ->
{
if (viaFields == null || viaFields.isEmpty())
return new HttpField(header, viaHeaderValue);
String separator = ", ";
String newValue = viaFields.stream()
.flatMap(field -> Stream.of(field.getValues()))
.filter(value -> !StringUtil.isBlank(value))
.collect(Collectors.joining(separator));
if (newValue.length() > 0)
newValue += separator;
newValue += viaHeaderValue;
return new HttpField(HttpHeader.VIA, newValue);
}));
}
protected void addForwardedHeader(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
{
String byAttr = Request.getLocalAddr(clientToProxyRequest);
String forAttr = Request.getRemoteAddr(clientToProxyRequest);
String hostAttr = clientToProxyRequest.getHeaders().get(HttpHeader.HOST);
String scheme = clientToProxyRequest.getHttpURI().getScheme();
// Even if the request came through a secure channel, look at the original scheme if present.
// For example, a client with a forward proxy may want to communicate in clear-text with the
// server (so the scheme is http), but securely with the forward proxy (so isSecure() is true).
String protoAttr = scheme == null ? (clientToProxyRequest.isSecure() ? "https" : "http") : scheme;
String forwardedValue = "by=%s;for=%s;host=%s;proto=%s".formatted(
QuotedStringTokenizer.quote(byAttr),
QuotedStringTokenizer.quote(forAttr),
QuotedStringTokenizer.quote(hostAttr),
protoAttr
);
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.FORWARDED, (header, fields) ->
{
String newValue;
if (fields == null || fields.isEmpty())
{
newValue = forwardedValue;
}
else
{
String separator = ", ";
newValue = fields.stream()
.flatMap(field -> field.getValueList().stream())
.collect(Collectors.joining(separator));
newValue += separator + forwardedValue;
}
return new HttpField(HttpHeader.FORWARDED, newValue);
}));
}
private boolean hasContent(Request clientToProxyRequest)
{
if (clientToProxyRequest.getLength() > 0)
return true;
HttpFields headers = clientToProxyRequest.getHeaders();
return headers.get(HttpHeader.CONTENT_TYPE) != null ||
headers.get(HttpHeader.TRANSFER_ENCODING) != null;
}
private boolean expects100Continue(Request clientToProxyRequest)
{
return HttpHeaderValue.CONTINUE.is(clientToProxyRequest.getHeaders().get(HttpHeader.EXPECT));
}
protected org.eclipse.jetty.client.api.Request.Content newProxyToServerRequestContent(Request clientToProxyRequest, Response proxyToClientResponse, org.eclipse.jetty.client.api.Request proxyToServerRequest)
{
return new ProxyRequestContent(clientToProxyRequest);
}
protected void sendProxyToServerRequest(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} sending P2S request
{}
{}""",
requestId(clientToProxyRequest),
proxyToServerRequest,
proxyToServerRequest.getHeaders());
}
proxyToServerRequest.send(newServerToProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback));
}
protected org.eclipse.jetty.client.api.Response.CompleteListener newServerToProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
return new ProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
}
protected HttpField filterServerToProxyResponseField(HttpField serverToProxyResponseField)
{
return serverToProxyResponseField;
}
protected void onServerToProxyResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
{
int status = HttpStatus.BAD_GATEWAY_502;
if (failure instanceof TimeoutException)
status = HttpStatus.GATEWAY_TIMEOUT_504;
Callback callback = new ProxyToClientResponseFailureCallback(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
Response.writeError(clientToProxyRequest, proxyToClientResponse, callback, status);
}
protected void onProxyToClientResponseComplete(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
{
proxyToClientCallback.succeeded();
}
protected void onProxyToClientResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
{
// There is no point trying to write an error,
// we already know we cannot write to the client.
proxyToClientCallback.failed(failure);
}
/**
* <p>A {@code ProxyHandler} that can be used to implement a forward proxy server.</p>
* <p>Forward proxies are configured in client applications that use
* {@link HttpClient} in this way:</p>
* <pre>{@code
* httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
* }</pre>
*
* @see org.eclipse.jetty.client.ProxyConfiguration
* @see org.eclipse.jetty.client.HttpProxy
* @see Reverse
*/
public static class Forward extends ProxyHandler
{
/**
* {@inheritDoc}
* <p>Applications that use this class should return the client-to-proxy
* request URI, since clients will send the absolute URI of the server.</p>
*
* @param clientToProxyRequest the client-to-proxy request
* @return the client-to-proxy request URI
*/
@Override
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
{
return clientToProxyRequest.getHttpURI();
}
}
/**
* <p>A {@code ProxyHandler} that can be used to implement a reverse proxy.</p>
* <p>A reverse proxy must rewrite the client-to-proxy request URI into the
* proxy-to-server request URI.
* This can be done by providing a rewrite function to the constructor,
* and/or override {@link #rewriteHttpURI(Request)}.</p>
*
* @see Forward
*/
public static class Reverse extends ProxyHandler
{
private final Function<Request, HttpURI> httpURIRewriter;
public Reverse(Function<Request, HttpURI> httpURIRewriter)
{
this.httpURIRewriter = httpURIRewriter;
}
public Function<Request, HttpURI> getHttpURIRewriter()
{
return httpURIRewriter;
}
/**
* {@inheritDoc}
* <p>Applications that use this class must provide a rewrite function
* to the constructor.</p>
* <p>The rewrite function rewrites the client-to-proxy request URI,
* for example {@code http://example.com/path}, to the proxy-to-server
* request URI, for example {@code http://backend1:8080/path}.</p>
*
* @param clientToProxyRequest the client-to-proxy request
* @return the proxy-to-server request URI.
*/
@Override
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
{
return getHttpURIRewriter().apply(clientToProxyRequest);
}
}
protected static class ProxyRequestContent implements org.eclipse.jetty.client.api.Request.Content
{
private final Request clientToProxyRequest;
public ProxyRequestContent(Request clientToProxyRequest)
{
this.clientToProxyRequest = clientToProxyRequest;
}
@Override
public long getLength()
{
return clientToProxyRequest.getLength();
}
@Override
public Content.Chunk read()
{
Content.Chunk chunk = clientToProxyRequest.read();
if (LOG.isDebugEnabled())
LOG.debug("{} read C2P content {}", requestId(clientToProxyRequest), chunk);
return chunk;
}
@Override
public void demand(Runnable demandCallback)
{
clientToProxyRequest.demand(demandCallback);
}
@Override
public void fail(Throwable failure)
{
clientToProxyRequest.fail(failure);
}
@Override
public String getContentType()
{
return clientToProxyRequest.getHeaders().get(HttpHeader.CONTENT_TYPE);
}
@Override
public boolean rewind()
{
// TODO: can this be delegated to the clientToProxyRequest?
return false;
}
}
protected class ProxyResponseListener extends Callback.Completable implements org.eclipse.jetty.client.api.Response.Listener
{
private final Request clientToProxyRequest;
private final org.eclipse.jetty.client.api.Request proxyToServerRequest;
private final Response proxyToClientResponse;
private final Callback proxyToClientCallback;
public ProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
this.clientToProxyRequest = clientToProxyRequest;
this.proxyToServerRequest = proxyToServerRequest;
this.proxyToClientResponse = proxyToClientResponse;
this.proxyToClientCallback = proxyToClientCallback;
}
@Override
public void onBegin(org.eclipse.jetty.client.api.Response serverToProxyResponse)
{
proxyToClientResponse.setStatus(serverToProxyResponse.getStatus());
}
@Override
public void onHeaders(org.eclipse.jetty.client.api.Response serverToProxyResponse)
{
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} received S2P response
{}
{}""",
requestId(clientToProxyRequest),
serverToProxyResponse,
serverToProxyResponse.getHeaders());
}
for (HttpField serverToProxyResponseField : serverToProxyResponse.getHeaders())
{
if (HOP_HEADERS.contains(serverToProxyResponseField.getHeader()))
continue;
HttpField newField = filterServerToProxyResponseField(serverToProxyResponseField);
if (newField == null)
continue;
proxyToClientResponse.getHeaders().add(newField);
}
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} sending P2C response
{}
{}""",
requestId(clientToProxyRequest),
proxyToClientResponse,
proxyToClientResponse.getHeaders());
}
}
@Override
public void onContent(org.eclipse.jetty.client.api.Response serverToProxyResponse, ByteBuffer serverToProxyContent, Callback serverToProxyContentCallback)
{
if (LOG.isDebugEnabled())
LOG.debug("{} received S2P content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
Callback callback = new Callback()
{
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} succeeded to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
serverToProxyContentCallback.succeeded();
}
@Override
public void failed(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("{} failed to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent), failure);
serverToProxyContentCallback.failed(failure);
// Cannot write towards the client, abort towards the server.
serverToProxyResponse.abort(failure);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
};
proxyToClientResponse.write(false, serverToProxyContent, callback);
}
@Override
public void onSuccess(org.eclipse.jetty.client.api.Response serverToProxyResponse)
{
proxyToClientResponse.write(true, BufferUtil.EMPTY_BUFFER, this);
}
@Override
public void onComplete(Result result)
{
if (result.isSucceeded())
{
// Wait for the last write to complete.
whenComplete((r, failure) ->
{
if (failure == null)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, failure);
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, failure);
}
});
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("{} S2P failure {}", requestId(clientToProxyRequest), result.getResponse(), result.getFailure());
onServerToProxyResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, result.getFailure());
}
}
}
private class ProxyToClientResponseFailureCallback implements Callback
{
private final Request clientToProxyRequest;
private final org.eclipse.jetty.client.api.Request proxyToServerRequest;
private final org.eclipse.jetty.client.api.Response serverToProxyResponse;
private final Response proxyToClientResponse;
private final Callback proxyToClientCallback;
private ProxyToClientResponseFailureCallback(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
{
this.clientToProxyRequest = clientToProxyRequest;
this.proxyToServerRequest = proxyToServerRequest;
this.serverToProxyResponse = serverToProxyResponse;
this.proxyToClientResponse = proxyToClientResponse;
this.proxyToClientCallback = proxyToClientCallback;
}
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
}
@Override
public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, x);
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback, x);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
}
}

View File

@ -0,0 +1,591 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.client.AbstractConnectionPool;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.HTTP2Cipher;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Connection;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ConnectHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ForwardProxyWithDynamicTransportTest
{
private static final Logger LOG = LoggerFactory.getLogger(ForwardProxyWithDynamicTransportTest.class);
private Server server;
private ServerConnector serverConnector;
private ServerConnector serverTLSConnector;
private Server proxy;
private ServerConnector proxyConnector;
private ServerConnector proxyTLSConnector;
private HTTP2Client http2Client;
private HttpClient client;
private void start(Handler handler) throws Exception
{
startServer(handler);
startProxy(new ConnectHandler());
startClient();
}
private void startServer(Handler handler) throws Exception
{
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setUseCipherSuitesOrder(true);
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
HttpConfiguration httpConfig = new HttpConfiguration();
HttpConnectionFactory h1c = new HttpConnectionFactory(httpConfig);
HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
serverConnector = new ServerConnector(server, 1, 1, h1c, h2c);
server.addConnector(serverConnector);
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory h1 = new HttpConnectionFactory(httpsConfig);
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
serverTLSConnector = new ServerConnector(server, 1, 1, ssl, alpn, h2, h1, h2c);
server.addConnector(serverTLSConnector);
server.setHandler(handler);
server.start();
LOG.info("Started server on :{} and :{}", serverConnector.getLocalPort(), serverTLSConnector.getLocalPort());
}
private void startProxy(ConnectHandler connectHandler) throws Exception
{
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setUseCipherSuitesOrder(true);
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
QueuedThreadPool proxyThreads = new QueuedThreadPool();
proxyThreads.setName("proxy");
proxy = new Server(proxyThreads);
HttpConfiguration httpConfig = new HttpConfiguration();
ConnectionFactory h1c = new HttpConnectionFactory(httpConfig);
ConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
proxyConnector = new ServerConnector(proxy, 1, 1, h1c, h2c);
proxy.addConnector(proxyConnector);
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory h1 = new HttpConnectionFactory(httpsConfig);
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
proxyTLSConnector = new ServerConnector(proxy, 1, 1, ssl, alpn, h2, h1, h2c);
proxy.addConnector(proxyTLSConnector);
proxy.setHandler(connectHandler);
connectHandler.setHandler(new ProxyHandler.Forward()
{
@Override
protected HttpClient newHttpClient()
{
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
proxyClientThreads.setName("proxy-client");
ClientConnector proxyClientConnector = new ClientConnector();
proxyClientConnector.setSelectors(1);
proxyClientConnector.setExecutor(proxyClientThreads);
proxyClientConnector.setSslContextFactory(new SslContextFactory.Client(true));
HTTP2Client proxyHTTP2Client = new HTTP2Client(proxyClientConnector);
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(proxyHTTP2Client);
return new HttpClient(new HttpClientTransportDynamic(proxyClientConnector, h1, http2));
}
});
proxy.start();
LOG.info("Started proxy on :{} and :{}", proxyConnector.getLocalPort(), proxyTLSConnector.getLocalPort());
}
private void startClient() throws Exception
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSelectors(1);
clientConnector.setExecutor(clientThreads);
clientConnector.setSslContextFactory(new SslContextFactory.Client(true));
http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2));
client.start();
}
@AfterEach
public void dispose()
{
LifeCycle.stop(client);
LifeCycle.stop(proxy);
LifeCycle.stop(server);
}
public static java.util.stream.Stream<Arguments> proxyMatrix()
{
var h1 = List.of("http/1.1");
var h2c = List.of("h2c");
var h2 = List.of("h2");
return java.util.stream.Stream.of(
// HTTP/1.1 Proxy with HTTP/1.1 Server.
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_1_1, false),
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_1_1, true),
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_1_1, false),
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_1_1, true),
// HTTP/1.1 Proxy with HTTP/2 Server.
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_2, false),
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_2, true),
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_2, false),
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_2, true)
// HTTP/2 Proxy with HTTP/1.1 Server.
// TODO: re-enable when HTTP/2 tunnel support is implemented
// Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, false),
// Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, true),
// Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_1_1, false),
// Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_1_1, true),
// Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_1_1, false),
// Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_1_1, true),
// HTTP/2 Proxy with HTTP/2 Server.
// TODO: re-enable when HTTP/2 tunnel support is implemented
// Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_2, false),
// Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_2, true),
// Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_2, false),
// Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_2, true),
// Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_2, false),
// Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_2, true)
);
}
@ParameterizedTest(name = "proxyProtocol={0}, proxySecure={1}, serverProtocol={2}, serverSecure={3}")
@MethodSource("proxyMatrix")
public void testProxy(Origin.Protocol proxyProtocol, boolean proxySecure, HttpVersion serverProtocol, boolean serverSecure) throws Exception
{
int status = HttpStatus.NO_CONTENT_204;
start(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
response.setStatus(status);
callback.succeeded();
}
});
int proxyPort = proxySecure ? proxyTLSConnector.getLocalPort() : proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, proxySecure, proxyProtocol);
client.getProxyConfiguration().getProxies().add(proxy);
String scheme = serverSecure ? "https" : "http";
int serverPort = serverSecure ? serverTLSConnector.getLocalPort() : serverConnector.getLocalPort();
ContentResponse response1 = client.newRequest("localhost", serverPort)
.scheme(scheme)
.version(serverProtocol)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(status, response1.getStatus());
// Make a second request to be sure it went through the same connection.
ContentResponse response2 = client.newRequest("localhost", serverPort)
.scheme(scheme)
.version(serverProtocol)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(status, response2.getStatus());
List<Destination> destinations = client.getDestinations().stream()
.filter(d -> d.getPort() == serverPort)
.toList();
assertEquals(1, destinations.size());
HttpDestination destination = (HttpDestination)destinations.get(0);
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
assertEquals(1, connectionPool.getConnectionCount());
}
@Test
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
public void testHTTP2TunnelClosedByClient() throws Exception
{
start(new EmptyServerHandler());
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(proxy);
long idleTimeout = 1000;
http2Client.setStreamIdleTimeout(idleTimeout);
String serverScheme = "http";
int serverPort = serverConnector.getLocalPort();
ContentResponse response = client.newRequest("localhost", serverPort)
.scheme(serverScheme)
.version(HttpVersion.HTTP_1_1)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
// Client will close the HTTP2StreamEndPoint.
Thread.sleep(2 * idleTimeout);
List<Destination> destinations = client.getDestinations().stream()
.filter(d -> d.getPort() == serverPort)
.toList();
assertEquals(1, destinations.size());
HttpDestination destination = (HttpDestination)destinations.get(0);
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
assertEquals(0, connectionPool.getConnectionCount());
List<HTTP2Connection> serverConnections = proxyConnector.getConnectedEndPoints().stream()
.map(EndPoint::getConnection)
.map(HTTP2Connection.class::cast)
.toList();
assertEquals(1, serverConnections.size());
assertTrue(serverConnections.get(0).getSession().getStreams().isEmpty());
}
@Test
public void testProxyDown() throws Exception
{
start(new EmptyServerHandler());
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
proxy.stop();
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.version(HttpVersion.HTTP_1_1)
.timeout(5, TimeUnit.SECONDS)
.send(result ->
{
assertTrue(result.isFailed());
assertThat(result.getFailure(), Matchers.instanceOf(ConnectException.class));
latch.countDown();
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
public void testHTTP2TunnelHardClosedByProxy() throws Exception
{
startServer(new EmptyServerHandler());
CountDownLatch closeLatch = new CountDownLatch(1);
startProxy(new ConnectHandler()
{
@Override
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
request.getConnectionMetaData().getConnection().getEndPoint().close();
closeLatch.countDown();
}
});
startClient();
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.version(HttpVersion.HTTP_1_1)
.timeout(5, TimeUnit.SECONDS)
.send(result ->
{
assertTrue(result.isFailed());
assertThat(result.getFailure(), Matchers.instanceOf(ClosedChannelException.class));
latch.countDown();
});
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
assertTrue(latch.await(5, TimeUnit.SECONDS));
List<Destination> destinations = client.getDestinations().stream()
.filter(d -> d.getPort() == proxyPort)
.toList();
assertEquals(1, destinations.size());
HttpDestination destination = (HttpDestination)destinations.get(0);
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
assertEquals(0, connectionPool.getConnectionCount());
}
@Test
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
public void testHTTP2TunnelResetByClient() throws Exception
{
startServer(new EmptyServerHandler());
CountDownLatch closeLatch = new CountDownLatch(2);
startProxy(new ConnectHandler()
{
@Override
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context)
{
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context)
{
@Override
protected void close(Throwable failure)
{
super.close(failure);
closeLatch.countDown();
}
};
}
@Override
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
{
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext)
{
@Override
protected void close(Throwable failure)
{
super.close(failure);
closeLatch.countDown();
}
};
}
});
startClient();
FuturePromise<Session> sessionPromise = new FuturePromise<>();
http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener() {}, sessionPromise);
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
String serverAddress = "localhost:" + serverConnector.getLocalPort();
MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(serverAddress), null, HttpFields.EMPTY, null);
HeadersFrame frame = new HeadersFrame(connect, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch tunnelLatch = new CountDownLatch(1);
CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(frame, streamPromise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
if (response.getStatus() == HttpStatus.OK_200)
tunnelLatch.countDown();
stream.demand();
}
@Override
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
String response = BufferUtil.toString(data.frame().getData(), StandardCharsets.UTF_8);
data.release();
if (response.startsWith("HTTP/1.1 200"))
responseLatch.countDown();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
assertTrue(tunnelLatch.await(5, TimeUnit.SECONDS));
// Tunnel is established, send a HTTP/1.1 request.
String h1 = "GET / HTTP/1.1\r\n" +
"Host: " + serverAddress + "\r\n" +
"\r\n";
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(h1.getBytes(StandardCharsets.UTF_8)), false), Callback.NOOP);
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
// Now reset the stream, tunnel must be closed.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
public void testHTTP2TunnelProxyStreamTimeout() throws Exception
{
startServer(new EmptyServerHandler());
CountDownLatch closeLatch = new CountDownLatch(2);
startProxy(new ConnectHandler()
{
@Override
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context)
{
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context)
{
@Override
protected void close(Throwable failure)
{
super.close(failure);
closeLatch.countDown();
}
};
}
@Override
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
{
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext)
{
@Override
protected void close(Throwable failure)
{
super.close(failure);
closeLatch.countDown();
}
};
}
});
startClient();
long streamIdleTimeout = 1000;
ConnectionFactory h2c = proxyConnector.getConnectionFactory("h2c");
((HTTP2CServerConnectionFactory)h2c).setStreamIdleTimeout(streamIdleTimeout);
FuturePromise<Session> sessionPromise = new FuturePromise<>();
http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener() {}, sessionPromise);
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
String serverAddress = "localhost:" + serverConnector.getLocalPort();
MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(serverAddress), null, HttpFields.EMPTY, null);
HeadersFrame frame = new HeadersFrame(connect, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch tunnelLatch = new CountDownLatch(1);
CountDownLatch responseLatch = new CountDownLatch(1);
CountDownLatch resetLatch = new CountDownLatch(1);
session.newStream(frame, streamPromise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
if (response.getStatus() == HttpStatus.OK_200)
tunnelLatch.countDown();
stream.demand();
}
@Override
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
String response = BufferUtil.toString(data.frame().getData(), StandardCharsets.UTF_8);
data.release();
if (response.startsWith("HTTP/1.1 200"))
responseLatch.countDown();
}
@Override
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
assertTrue(tunnelLatch.await(5, TimeUnit.SECONDS));
// Tunnel is established, send a HTTP/1.1 request.
String h1 = "GET / HTTP/1.1\r\n" +
"Host: " + serverAddress + "\r\n" +
"\r\n";
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(h1.getBytes(StandardCharsets.UTF_8)), false), Callback.NOOP);
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
// Wait until the proxy stream idle times out.
Thread.sleep(2 * streamIdleTimeout);
// Client should see a RST_STREAM.
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
// Tunnel must be closed.
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
private static class EmptyServerHandler extends Handler.Processor
{
@Override
public void process(Request request, Response response, Callback callback)
{
callback.succeeded();
}
}
}

View File

@ -0,0 +1,151 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.util.List;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReverseProxyTest
{
private Server server;
private ServerConnector serverConnector;
private Server proxy;
private ServerConnector proxyConnector;
private HttpClient client;
private void startServer(Handler handler) throws Exception
{
QueuedThreadPool serverPool = new QueuedThreadPool();
serverPool.setName("server");
server = new Server(serverPool);
HttpConfiguration httpConfig = new HttpConfiguration();
serverConnector = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig));
server.addConnector(serverConnector);
server.setHandler(handler);
server.start();
}
private void startProxy(Handler handler) throws Exception
{
QueuedThreadPool proxyPool = new QueuedThreadPool();
proxyPool.setName("proxy");
proxy = new Server(proxyPool);
HttpConfiguration configuration = new HttpConfiguration();
configuration.setSendDateHeader(false);
configuration.setSendServerVersion(false);
proxyConnector = new ServerConnector(proxy, 1, 1, new HttpConnectionFactory(configuration), new HTTP2CServerConnectionFactory(configuration));
proxy.addConnector(proxyConnector);
proxy.setHandler(handler);
proxy.start();
}
private void startClient() throws Exception
{
ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
clientConnector.setExecutor(clientThreads);
HTTP2Client http2Client = new HTTP2Client(clientConnector);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
client.start();
}
@AfterEach
public void dispose()
{
LifeCycle.stop(client);
LifeCycle.stop(proxy);
LifeCycle.stop(server);
}
private static List<HttpVersion> httpVersions()
{
return List.of(HttpVersion.HTTP_1_1, HttpVersion.HTTP_2);
}
@ParameterizedTest
@MethodSource("httpVersions")
public void testSimple(HttpVersion httpVersion) throws Exception
{
String clientContent = "hello";
String serverContent = "world";
startServer(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
String requestContent = Content.Source.asString(request);
assertEquals(clientContent, requestContent);
Content.Sink.write(response, true, serverContent, callback);
}
});
startProxy(new ProxyHandler.Reverse(clientToProxyRequest ->
HttpURI.build(clientToProxyRequest.getHttpURI()).port(serverConnector.getLocalPort()))
{
@Override
protected HttpClient newHttpClient()
{
ClientConnector proxyClientConnector = new ClientConnector();
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
proxyClientThreads.setName("proxy-client");
proxyClientConnector.setExecutor(proxyClientThreads);
HTTP2Client proxyHTTP2Client = new HTTP2Client(proxyClientConnector);
return new HttpClient(new HttpClientTransportDynamic(proxyClientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(proxyHTTP2Client)));
}
@Override
protected org.eclipse.jetty.client.api.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
{
return super.newProxyToServerRequest(clientToProxyRequest, newHttpURI)
.version(httpVersion);
}
});
startClient();
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
.version(httpVersion)
.body(new StringRequestContent(clientContent))
.send();
assertEquals(serverContent, response.getContentAsString());
}
}

View File

@ -0,0 +1,3 @@
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG
#org.eclipse.jetty.server.handler.LEVEL=DEBUG

Binary file not shown.

View File

@ -31,6 +31,7 @@ import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
/**
@ -120,6 +121,6 @@ public class ServerQuicSession extends QuicSession implements CyclicTimeouts.Exp
{
long idleTimeout = getIdleTimeout();
if (idleTimeout > 0)
expireNanoTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
expireNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(idleTimeout);
}
}

View File

@ -2,21 +2,23 @@
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
<Call id="ResourceFactory" class="org.eclipse.jetty.util.resource.ResourceFactory" name="of">
<Arg><Ref refid="Contexts" /></Arg>
<Call id="baseResource" name="newResource">
<Arg>
<Call name="resolvePath" class="org.eclipse.jetty.xml.XmlConfiguration">
<Arg><Property name="jetty.base"/></Arg>
<Arg><Property name="jetty.wellknown.dir" default=".well-known"/></Arg>
</Call>
</Arg>
</Call>
</Call>
<New id="WellKnownHandler" class="org.eclipse.jetty.server.handler.ContextHandler">
<Call id="ResourceFactory" class="org.eclipse.jetty.util.resource.ResourceFactory" name="of">
<Arg><Ref refid="WellKnownHandler" /></Arg>
<Call id="baseResource" name="newResource">
<Arg>
<Call name="resolvePath" class="org.eclipse.jetty.xml.XmlConfiguration">
<Arg><Property name="jetty.base"/></Arg>
<Arg><Property name="jetty.wellknown.dir" default=".well-known"/></Arg>
</Call>
</Arg>
</Call>
</Call>
<Set name="contextPath">/.well-known</Set>
<Set name="baseResource">
<Ref refid="baseResource"/>
</Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.ResourceHandler">
<Set name="baseResource"><Ref refid="baseResource"/></Set>

View File

@ -27,8 +27,6 @@ import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
@ -47,12 +45,35 @@ import org.slf4j.LoggerFactory;
*/
public class SecureRequestCustomizer implements HttpConfiguration.Customizer
{
private static final Logger LOG = LoggerFactory.getLogger(SecureRequestCustomizer.class);
public static final String X509_CERT = "org.eclipse.jetty.server.x509_cert";
public static final String CERTIFICATES = "org.eclipse.jetty.server.certificates";
private String _sslSessionAttribute = "org.eclipse.jetty.servlet.request.ssl_session"; // TODO better name?
private String _sslSessionDataAttribute = _sslSessionAttribute + "_data"; // TODO better name?
/**
* <p>The request attribute name to use to obtain the peer certificate
* chain as an array of {@link X509Certificate} objects.</p>
*/
public static final String PEER_CERTIFICATES_ATTRIBUTE = "org.eclipse.jetty.server.certificates";
/**
* <p>The request attribute name to use to obtain the local certificate
* as an {@link X509} object.</p>
*/
public static final String X509_ATTRIBUTE = "org.eclipse.jetty.server.x509";
/**
* <p>The default value of the request attribute name to use to obtain
* the {@link SSLSession} object.</p>
*
* @see #setSslSessionAttribute(String)
*/
public static final String DEFAULT_SSL_SESSION_ATTRIBUTE = "org.eclipse.jetty.server.sslSession";
/**
* <p>The default value of the request attribute name to use to obtain
* the {@link SslSessionData} object.</p>
*
* @see #getSslSessionDataAttribute()
*/
public static final String DEFAULT_SSL_SESSION_DATA_ATTRIBUTE = newSslSessionDataAttribute(DEFAULT_SSL_SESSION_ATTRIBUTE);
private static final Logger LOG = LoggerFactory.getLogger(SecureRequestCustomizer.class);
private String _sslSessionAttribute = DEFAULT_SSL_SESSION_ATTRIBUTE;
private String _sslSessionDataAttribute = DEFAULT_SSL_SESSION_DATA_ATTRIBUTE;
private boolean _sniRequired;
private boolean _sniHostCheck;
private long _stsMaxAge;
@ -144,21 +165,21 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Set the Strict-Transport-Security max age.
* Sets the Strict-Transport-Security max age in seconds.
*
* @param stsMaxAgeSeconds The max age in seconds for a Strict-Transport-Security response header. If set less than zero then no header is sent.
* @param stsMaxAgeSeconds the max age in seconds for the Strict-Transport-Security response header.
* If less than zero then no Strict-Transport-Security response header is set.
*/
public void setStsMaxAge(long stsMaxAgeSeconds)
{
_stsMaxAge = stsMaxAgeSeconds;
formatSTS();
setStsMaxAge(stsMaxAgeSeconds, TimeUnit.SECONDS);
}
/**
* Convenience method to call {@link #setStsMaxAge(long)}
* Sets the Strict-Transport-Security max age in the given time unit.
*
* @param period The period in units
* @param units The {@link TimeUnit} of the period
* @param period The max age value
* @param units The {@link TimeUnit} of the max age
*/
public void setStsMaxAge(long period, TimeUnit units)
{
@ -167,7 +188,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* @return true if a include subdomain property is sent with any Strict-Transport-Security header
* @return whether the {@code includeSubdomains} attribute is sent with the Strict-Transport-Security response header
*/
public boolean isStsIncludeSubDomains()
{
@ -175,7 +196,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* @param stsIncludeSubDomains If true, a include subdomain property is sent with any Strict-Transport-Security header
* @param stsIncludeSubDomains whether the {@code includeSubdomains} attribute is sent with the Strict-Transport-Security response header
*/
public void setStsIncludeSubDomains(boolean stsIncludeSubDomains)
{
@ -185,49 +206,41 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
private void formatSTS()
{
if (_stsMaxAge < 0)
long stsMaxAge = getStsMaxAge();
if (stsMaxAge < 0)
_stsField = null;
else
_stsField = new PreEncodedHttpField(HttpHeader.STRICT_TRANSPORT_SECURITY, String.format("max-age=%d%s", _stsMaxAge, _stsIncludeSubDomains ? "; includeSubDomains" : ""));
_stsField = new PreEncodedHttpField(HttpHeader.STRICT_TRANSPORT_SECURITY, String.format("max-age=%d%s", stsMaxAge, isStsIncludeSubDomains() ? "; includeSubDomains" : ""));
}
@Override
public Request customize(Request request, HttpFields.Mutable responseHeaders)
{
EndPoint endp = request.getConnectionMetaData().getConnection().getEndPoint();
HttpURI uri = request.getHttpURI();
SSLEngine sslEngine;
if (endp instanceof DecryptedEndPoint)
EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
if (endPoint instanceof DecryptedEndPoint sslEndPoint)
{
DecryptedEndPoint sslEndp = (DecryptedEndPoint)endp;
SslConnection sslConnection = sslEndp.getSslConnection();
uri = (HttpScheme.HTTPS.is(uri.getScheme()))
? uri : HttpURI.build(uri).scheme(HttpScheme.HTTPS);
sslEngine = sslConnection.getSSLEngine();
SslConnection sslConnection = sslEndPoint.getSslConnection();
SSLEngine sslEngine = sslConnection.getSSLEngine();
request = newSecureRequest(request, sslEngine);
}
else if (endp instanceof ProxyConnectionFactory.ProxyEndPoint)
else if (endPoint instanceof ProxyConnectionFactory.ProxyEndPoint proxyEndPoint)
{
ProxyConnectionFactory.ProxyEndPoint proxy = (ProxyConnectionFactory.ProxyEndPoint)endp;
if (proxy.getAttribute(ProxyConnectionFactory.TLS_VERSION) == null)
return request;
uri = (HttpScheme.HTTPS.is(uri.getScheme()))
? uri : HttpURI.build(uri).scheme(HttpScheme.HTTPS);
sslEngine = null;
}
else
{
return request;
if (proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION) != null)
request = newSecureRequest(request, null);
}
if (_stsField != null)
responseHeaders.add(_stsField);
return newSecureRequest(request, uri, sslEngine);
return request;
}
protected SecureRequest newSecureRequest(Request request, HttpURI uri, SSLEngine sslEngine)
protected Request newSecureRequest(Request request, SSLEngine sslEngine)
{
return new SecureRequest(request, uri, sslEngine);
if (sslEngine != null)
return new SecureRequestWithTLSData(request, sslEngine);
else
return new SecureRequest(request);
}
private X509Certificate[] getCertChain(Connector connector, SSLSession sslSession)
@ -249,7 +262,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
{
Objects.requireNonNull(attribute);
_sslSessionAttribute = attribute;
_sslSessionDataAttribute = attribute + "_data";
_sslSessionDataAttribute = newSslSessionDataAttribute(attribute);
}
public String getSslSessionAttribute()
@ -258,14 +271,16 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Get data belonging to the {@link SSLSession}.
*
* @return the SslSessionData
* @return {@link #getSslSessionAttribute()} {@code + "Data"}
*/
public static SslSessionData getSslSessionData(SSLSession session)
public String getSslSessionDataAttribute()
{
String key = SslSessionData.class.getName();
return (SslSessionData)session.getValue(key);
return _sslSessionDataAttribute;
}
private static String newSslSessionDataAttribute(String sslSessionAttribute)
{
return sslSessionAttribute + "Data";
}
protected void checkSni(Request request, SSLSession session)
@ -291,14 +306,14 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
private X509 getX509(SSLSession session)
{
X509 x509 = (X509)session.getValue(X509_CERT);
X509 x509 = (X509)session.getValue(X509_ATTRIBUTE);
if (x509 == null)
{
Certificate[] certificates = session.getLocalCertificates();
if (certificates == null || certificates.length == 0 || !(certificates[0] instanceof X509Certificate))
return null;
x509 = new X509(null, (X509Certificate)certificates[0]);
session.putValue(X509_CERT, x509);
session.putValue(X509_ATTRIBUTE, x509);
}
return x509;
}
@ -309,57 +324,11 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
return String.format("%s@%x", this.getClass().getSimpleName(), hashCode());
}
protected class SecureRequest extends Request.Wrapper
protected static class SecureRequest extends Request.Wrapper
{
private final HttpURI _uri;
private final SSLSession _sslSession;
private final SslSessionData _sslSessionData;
private SecureRequest(Request request, HttpURI uri, SSLEngine sslEngine)
public SecureRequest(Request wrapped)
{
super(request);
_uri = uri.asImmutable();
if (sslEngine == null)
{
_sslSession = null;
_sslSessionData = null;
}
else
{
_sslSession = sslEngine.getSession();
checkSni(request, _sslSession);
String key = SslSessionData.class.getName();
SslSessionData sslSessionData = (SslSessionData)_sslSession.getValue(key);
if (sslSessionData == null)
{
try
{
String cipherSuite = _sslSession.getCipherSuite();
int keySize = SslContextFactory.deduceKeyLength(cipherSuite);
X509Certificate[] certs = getCertChain(getConnectionMetaData().getConnector(), _sslSession);
byte[] bytes = _sslSession.getId();
String idStr = StringUtil.toHexString(bytes);
sslSessionData = new SslSessionData(keySize, certs, idStr);
_sslSession.putValue(key, sslSessionData);
}
catch (Exception e)
{
LOG.warn("Unable to get secure details ", e);
}
}
_sslSessionData = sslSessionData;
}
}
@Override
public HttpURI getHttpURI()
{
return _uri;
super(wrapped);
}
@Override
@ -367,6 +336,43 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
{
return true;
}
}
protected class SecureRequestWithTLSData extends SecureRequest
{
private final SSLSession _sslSession;
private final SslSessionData _sslSessionData;
public SecureRequestWithTLSData(Request request, SSLEngine sslEngine)
{
super(request);
_sslSession = sslEngine.getSession();
checkSni(request, _sslSession);
String key = SslSessionData.class.getName();
SslSessionData sslSessionData = (SslSessionData)_sslSession.getValue(key);
if (sslSessionData == null)
{
try
{
String cipherSuite = _sslSession.getCipherSuite();
int keySize = SslContextFactory.deduceKeyLength(cipherSuite);
X509Certificate[] certs = getCertChain(getConnectionMetaData().getConnector(), _sslSession);
byte[] bytes = _sslSession.getId();
String idStr = StringUtil.toHexString(bytes);
sslSessionData = new SslSessionData(idStr, keySize, certs);
_sslSession.putValue(key, sslSessionData);
}
catch (Exception e)
{
LOG.warn("Unable to get secure details ", e);
}
}
_sslSessionData = sslSessionData;
}
@Override
public Object getAttribute(String name)
@ -376,14 +382,14 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
{
if (name.equals(sessionAttribute))
return _sslSession;
if (name.equals(_sslSessionDataAttribute))
if (name.equals(getSslSessionDataAttribute()))
return _sslSessionData;
}
return switch (name)
{
case X509_CERT -> getX509(_sslSession);
case CERTIFICATES -> _sslSessionData._certs;
case X509_ATTRIBUTE -> getX509(_sslSession);
case PEER_CERTIFICATES_ATTRIBUTE -> _sslSessionData != null ? _sslSessionData.peerCertificates() : null;
default -> super.getAttribute(name);
};
}
@ -405,32 +411,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
/**
* Simple bundle of data that is cached in the SSLSession.
*/
public static class SslSessionData
public record SslSessionData(String sessionId, int keySize, X509Certificate[] peerCertificates)
{
private final Integer _keySize;
private final X509Certificate[] _certs;
private final String _idStr;
private SslSessionData(Integer keySize, X509Certificate[] certs, String idStr)
{
this._keySize = keySize;
this._certs = certs;
this._idStr = idStr;
}
public Integer getKeySize()
{
return _keySize;
}
public X509Certificate[] getX509Certificates()
{
return _certs;
}
public String getId()
{
return _idStr;
}
}
}

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Uptime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.Name;
@ -495,7 +496,7 @@ public class Server extends Handler.Wrapper implements Attributes
if (getStopTimeout() > 0)
{
long end = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(getStopTimeout());
long end = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(getStopTimeout());
try
{
Graceful.shutdown(this).get(getStopTimeout(), TimeUnit.MILLISECONDS);
@ -506,7 +507,7 @@ public class Server extends Handler.Wrapper implements Attributes
}
QueuedThreadPool qtp = getBean(QueuedThreadPool.class);
if (qtp != null)
qtp.setStopTimeout(Math.max(1000L, TimeUnit.NANOSECONDS.toMillis(end - System.nanoTime())));
qtp.setStopTimeout(Math.max(1000L, NanoTime.millisUntil(end)));
}
// Now stop the connectors (this will close existing connections)
@ -747,7 +748,7 @@ public class Server extends Handler.Wrapper implements Attributes
{
runnable.run();
}
@Override
public void run(Runnable runnable, Request request)
{

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.server;
package org.eclipse.jetty.server.handler;
import java.io.Closeable;
import java.io.IOException;
@ -39,6 +39,11 @@ import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IteratingCallback;

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.statistic.CounterStatistic;
@ -53,7 +54,7 @@ public class StatisticsHandler extends Handler.Wrapper
@Override
public Request.Processor handle(Request request) throws Exception
{
long beginTimeStamp = System.nanoTime();
long beginNanoTime = NanoTime.now();
_handleStats.increment();
StatisticsRequest statisticsRequest = new StatisticsRequest(request);
@ -69,7 +70,7 @@ public class StatisticsHandler extends Handler.Wrapper
finally
{
_handleStats.decrement();
_handleTimeStats.record(System.nanoTime() - beginTimeStamp);
_handleTimeStats.record(NanoTime.since(beginNanoTime));
}
}
@ -252,7 +253,7 @@ public class StatisticsHandler extends Handler.Wrapper
{
private final LongAdder _bytesRead = new LongAdder();
private final LongAdder _bytesWritten = new LongAdder();
private long _processStartTimeStamp;
private long _processStartNanoTime;
private StatisticsRequest(Request request)
{
@ -283,13 +284,13 @@ public class StatisticsHandler extends Handler.Wrapper
private long spentTimeNs()
{
return System.nanoTime() - _processStartTimeStamp;
return NanoTime.since(_processStartNanoTime);
}
@Override
public void process(Request ignored, Response response, Callback callback) throws Exception
{
_processStartTimeStamp = System.nanoTime();
_processStartNanoTime = NanoTime.now();
_processStats.increment();
_requestStats.increment();
@ -342,7 +343,7 @@ public class StatisticsHandler extends Handler.Wrapper
public void succeeded()
{
_requestStats.decrement();
_requestTimeStats.record(System.nanoTime() - getNanoTimeStamp());
_requestTimeStats.record(NanoTime.since(getNanoTimeStamp()));
super.succeeded();
}
@ -350,7 +351,7 @@ public class StatisticsHandler extends Handler.Wrapper
public void failed(Throwable x)
{
_requestStats.decrement();
_requestTimeStats.record(System.nanoTime() - getNanoTimeStamp());
_requestTimeStats.record(NanoTime.since(getNanoTimeStamp()));
super.failed(x);
}
});
@ -367,7 +368,7 @@ public class StatisticsHandler extends Handler.Wrapper
finally
{
_processStats.decrement();
_processTimeStats.record(System.nanoTime() - _processStartTimeStamp);
_processTimeStats.record(NanoTime.since(_processStartNanoTime));
}
}
}

View File

@ -68,6 +68,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
@ -1156,7 +1157,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
protected class HttpStreamOverHTTP1 implements HttpStream
{
private final long _nanoTimestamp = System.nanoTime();
private final long _nanoTime = NanoTime.now();
private final long _id;
private final String _method;
private final HttpURI.Mutable _uri;
@ -1385,7 +1386,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
@Override
public long getNanoTimeStamp()
{
return _nanoTimestamp;
return _nanoTime;
}
@Override

View File

@ -37,6 +37,7 @@ import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
@ -62,10 +63,10 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
{
protected static final Logger LOG = LoggerFactory.getLogger(ConnectorTimeoutTest.class);
protected static final int MAX_IDLE_TIME = 2000;
private int sleepTime = MAX_IDLE_TIME + MAX_IDLE_TIME / 5;
private int minimumTestRuntime = MAX_IDLE_TIME - MAX_IDLE_TIME / 5;
private int maximumTestRuntime = MAX_IDLE_TIME * 10;
protected static final long MAX_IDLE_TIME = 2000;
private final long sleepTime = MAX_IDLE_TIME + MAX_IDLE_TIME / 5;
private final long minimumTestRuntime = MAX_IDLE_TIME - MAX_IDLE_TIME / 5;
private final long maximumTestRuntime = MAX_IDLE_TIME * 10;
@BeforeEach
@Override
@ -92,7 +93,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
assertTimeoutPreemptively(ofSeconds(10), () ->
{
@ -109,8 +110,9 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertEquals(-1, is.read());
});
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start > minimumTestRuntime);
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start < maximumTestRuntime);
long elapsedMs = NanoTime.millisSince(start);
assertTrue(elapsedMs > minimumTestRuntime);
assertTrue(elapsedMs < maximumTestRuntime);
}
@Test
@ -125,7 +127,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
assertTimeoutPreemptively(ofSeconds(10), () ->
{
@ -146,8 +148,9 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertEquals(-1, is.read());
});
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start > minimumTestRuntime);
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start < maximumTestRuntime);
long elapsedMs = NanoTime.millisSince(start);
assertTrue(elapsedMs > minimumTestRuntime);
assertTrue(elapsedMs < maximumTestRuntime);
}
@Test
@ -281,7 +284,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
InputStream is = client.getInputStream();
assertFalse(client.isClosed());
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
OutputStream os = client.getOutputStream();
os.write(("GET / HTTP/1.1\r\n" +
@ -319,7 +322,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
{
e.printStackTrace();
}
long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start;
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(500L));
assertTimeoutPreemptively(ofSeconds(10), () ->
@ -344,7 +347,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
OutputStream os = client.getOutputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
os.write(("GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"Transfer-Encoding: chunked\r\n" +
@ -377,7 +380,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
os.flush();
}
long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start;
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(500L));
// read the response
@ -422,7 +425,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
if (i % 1028 == 0)
{
Thread.sleep(20);
// System.err.println("read "+TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
}
line = is.readLine();
assertThat(line, notNullValue());
@ -460,7 +462,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
line = is.readLine();
}
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class, AbstractConnection.class))
{
for (int i = 0; i < (128 * 1024); i++)
@ -468,16 +470,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
if (i % 1028 == 0)
{
Thread.sleep(20);
// System.err.println("read "+TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
}
line = is.readLine();
if (line == null)
break;
}
}
long end = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long duration = end - start;
assertThat(duration, lessThan(20L * 128L));
assertThat(NanoTime.millisSince(start), lessThan(20L * 128L));
}
@Test
@ -490,7 +489,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
os.write("GET ".getBytes("utf-8"));
os.flush();
@ -508,14 +507,14 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
LOG.warn(e.getMessage());
}
});
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start < maximumTestRuntime);
assertTrue(NanoTime.millisSince(start) < maximumTestRuntime);
}
@Test
public void testMaxIdleNothingSent() throws Exception
{
startServer(new EchoHandler());
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
InputStream is = client.getInputStream();
@ -539,7 +538,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
LOG.warn("Unable to read stream", e);
}
});
assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start < maximumTestRuntime);
assertTrue(NanoTime.millisSince(start) < maximumTestRuntime);
}
@Test
@ -552,7 +551,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
os.write((
"GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
@ -576,7 +575,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
e.printStackTrace();
}
});
int duration = (int)(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start);
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThanOrEqualTo(MAX_IDLE_TIME));
assertThat(duration, lessThan(maximumTestRuntime));
}
@ -591,7 +590,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
os.write((
"GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
@ -616,7 +615,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
e.printStackTrace();
}
});
int duration = (int)(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start);
long duration = NanoTime.millisSince(start);
assertThat(duration + 100, greaterThanOrEqualTo(MAX_IDLE_TIME));
assertThat(duration - 100, lessThan(maximumTestRuntime));
}

View File

@ -18,7 +18,6 @@ import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.Connection;
@ -29,6 +28,7 @@ import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
@ -77,7 +77,7 @@ public class ExtendedServerTest extends HttpServerTestBase
@Override
public Runnable onSelected()
{
_lastSelected = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
_lastSelected = NanoTime.now();
return super.onSelected();
}
@ -119,7 +119,7 @@ public class ExtendedServerTest extends HttpServerTestBase
{
OutputStream os = client.getOutputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
os.write("GET / HTTP/1.0\r\n".getBytes(StandardCharsets.ISO_8859_1));
os.flush();
Thread.sleep(200);
@ -127,7 +127,7 @@ public class ExtendedServerTest extends HttpServerTestBase
// Read the response.
String response = readResponse(client);
long end = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long end = NanoTime.now();
assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
assertThat(response, Matchers.containsString("DispatchedAt="));
@ -136,8 +136,8 @@ public class ExtendedServerTest extends HttpServerTestBase
s = s.substring(0, s.indexOf('\n'));
long dispatched = Long.parseLong(s);
assertThat(dispatched, Matchers.greaterThanOrEqualTo(start));
assertThat(dispatched, Matchers.lessThanOrEqualTo(end));
assertThat(NanoTime.elapsed(start, dispatched), Matchers.greaterThanOrEqualTo(0L));
assertThat(NanoTime.elapsed(dispatched, end), Matchers.greaterThanOrEqualTo(0L));
}
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
import org.hamcrest.Matchers;
@ -45,6 +46,7 @@ import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Disabled // TODO
@ -148,6 +150,7 @@ public class GracefulStopTest
assertTrue(handler.latch.await(5, TimeUnit.SECONDS));
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
@ -163,6 +166,7 @@ public class GracefulStopTest
assertTrue(handler.latch.await(5, TimeUnit.SECONDS));
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
@ -171,21 +175,21 @@ public class GracefulStopTest
Future<Integer> backgroundUnavailable(Socket client, byte[] post, ContextHandler context, TestHandler handler) throws Exception
{
FuturePromise<Integer> future = new FuturePromise<>();
long start = System.nanoTime();
long start = NanoTime.now();
new Thread(() ->
{
try
{
while (context.isAvailable())
{
assertThat(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), lessThan(5000L));
assertThat(NanoTime.millisSince(start), lessThan(5000L));
Thread.sleep(100);
}
client.getOutputStream().write(concat(post, BODY_67890));
client.getOutputStream().flush();
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
future.succeeded(response.getStatus());
}
catch (Exception e)
@ -199,19 +203,16 @@ public class GracefulStopTest
void assertQuickStop() throws Exception
{
long start = System.nanoTime();
long start = NanoTime.now();
server.stop();
long stop = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(stop - start);
assertThat(duration, lessThan(2000L));
assertThat(NanoTime.millisSince(start), lessThan(2000L));
}
void assertGracefulStop(LifeCycle lifecycle) throws Exception
{
long start = System.nanoTime();
long start = NanoTime.now();
lifecycle.stop();
long stop = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(stop - start);
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(50L));
assertThat(duration, lessThan(5000L));
}
@ -219,6 +220,7 @@ public class GracefulStopTest
void assertResponse(Socket client, boolean close) throws Exception
{
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
if (close)
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
@ -239,11 +241,9 @@ public class GracefulStopTest
void assertQuickClose(Socket client) throws Exception
{
long start = System.nanoTime();
long start = NanoTime.now();
assertThat(client.getInputStream().read(), is(-1));
long stop = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(stop - start);
assertThat(duration, lessThan(2000L));
assertThat(NanoTime.millisSince(start), lessThan(2000L));
}
void assertHandled(TestHandler handler, boolean error)
@ -257,14 +257,13 @@ public class GracefulStopTest
void backgroundComplete(Socket client, TestHandler handler) throws Exception
{
long start = System.nanoTime();
long start = NanoTime.now();
new Thread(() ->
{
try
{
handler.latch.await();
long now = System.nanoTime();
Thread.sleep(100 - TimeUnit.NANOSECONDS.toMillis(now - start));
Thread.sleep(100 - NanoTime.millisSince(start));
client.getOutputStream().write(BODY_67890);
}
catch (Exception e)

View File

@ -15,12 +15,10 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.util.Callback;
@ -30,7 +28,6 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -287,15 +284,14 @@ public class HttpChannelEventTest
@Override
public void onRequestBegin(Request request)
{
request.setAttribute(attribute, System.nanoTime());
request.setAttribute(attribute, NanoTime.now());
}
@Override
public void onComplete(Request request)
{
long endTime = System.nanoTime();
long beginTime = (Long)request.getAttribute(attribute);
elapsed.set(endTime - beginTime);
elapsed.set(NanoTime.since(beginTime));
latch.countDown();
}
});

View File

@ -49,6 +49,7 @@ import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -933,9 +934,9 @@ public class HttpConnectionTest
"5;\r\n" +
"12345\r\n";
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
String response = _connector.getResponse(requests, 2000, TimeUnit.MILLISECONDS);
assertThat(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start, lessThanOrEqualTo(2000L));
assertThat(NanoTime.millisSince(start), lessThanOrEqualTo(2000L));
offset = checkContains(response, offset, "HTTP/1.1 200");
offset = checkContains(response, offset, "pathInContext=/R1");

View File

@ -29,12 +29,13 @@ import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
public class MockHttpStream implements HttpStream
{
private static final Throwable SUCCEEDED = new Throwable();
private static final Content.Chunk DEMAND = Content.Chunk.from(BufferUtil.EMPTY_BUFFER, false);
private final long nano = System.nanoTime();
private final long _nanoTime = NanoTime.now();
private final AtomicReference<Content.Chunk> _content = new AtomicReference<>();
private final AtomicReference<Throwable> _complete = new AtomicReference<>();
private final CountDownLatch _completed = new CountDownLatch(1);
@ -128,7 +129,7 @@ public class MockHttpStream implements HttpStream
@Override
public long getNanoTimeStamp()
{
return nano;
return _nanoTime;
}
@Override

View File

@ -24,6 +24,7 @@ import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.HelloHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -424,7 +425,7 @@ public class NotAcceptingTest
public static <T> void waitFor(Supplier<T> value, Matcher<T> matcher, long wait, TimeUnit units)
{
long start = System.nanoTime();
long start = NanoTime.now();
while (true)
{
@ -435,7 +436,7 @@ public class NotAcceptingTest
}
catch (Throwable e)
{
if ((System.nanoTime() - start) > units.toNanos(wait))
if (NanoTime.since(start) > units.toNanos(wait))
throw e;
}

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -77,7 +78,7 @@ public class ServerConnectorAcceptTest
{
assertTrue(awaitBarrier(barrier));
long start = System.nanoTime();
long start = NanoTime.now();
for (int i = 0; i < iterations; ++i)
{
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
@ -100,12 +101,11 @@ public class ServerConnectorAcceptTest
latch.countDown();
}
}
long elapsed = System.nanoTime() - start;
System.err.printf("%d acceptors, %d threads, %d requests each, time = %d ms%n",
acceptors,
threads,
iterations,
TimeUnit.NANOSECONDS.toMillis(elapsed));
NanoTime.millisSince(start));
}
finally
{

View File

@ -116,13 +116,12 @@ public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
{
try (Socket socket = new Socket((String)null, _connector.getLocalPort()))
{
socket.setSoTimeout(10 * MAX_IDLE_TIME);
socket.setSoTimeout((int)(10 * MAX_IDLE_TIME));
socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
InputStream inputStream = socket.getInputStream();
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long start = NanoTime.now();
String response = IO.toString(inputStream);
long timeElapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start;
assertThat(timeElapsed, greaterThanOrEqualTo(MAX_IDLE_TIME - 100L));
assertThat(NanoTime.millisSince(start), greaterThanOrEqualTo(MAX_IDLE_TIME - 100L));
return response;
}
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.LifeCycle;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
@ -155,12 +156,12 @@ public class StopTest
@Override
public void close()
{
long start = System.nanoTime();
long start = NanoTime.now();
new Thread(() ->
{
try
{
Thread.sleep(closeWait - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
Thread.sleep(closeWait - NanoTime.millisSince(start));
}
catch (Throwable e)
{
@ -205,7 +206,7 @@ public class StopTest
break;
}
long start = System.nanoTime();
long start = NanoTime.now();
try
{
server.stop();
@ -215,10 +216,9 @@ public class StopTest
{
assertTrue(stopTimeout > 0 && stopTimeout < closeWait);
}
long stop = System.nanoTime();
// Check stop time was correct
assertThat(TimeUnit.NANOSECONDS.toMillis(stop - start), stopTimeMatcher);
assertThat(NanoTime.millisSince(start), stopTimeMatcher);
// Connection closed
while (true)
@ -226,7 +226,7 @@ public class StopTest
int r = client.getInputStream().read();
if (r == -1)
break;
assertThat(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start), lessThan(10L));
assertThat(NanoTime.millisSince(start), lessThan(10L));
}
// onClose Thread interrupted or completed
@ -346,10 +346,10 @@ public class StopTest
}
}).start();
long start = System.nanoTime();
long start = NanoTime.now();
while (!connector.isShutdown())
{
assertThat(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start), lessThan(10L));
assertThat(NanoTime.secondsSince(start), lessThan(10L));
Thread.sleep(10);
}

View File

@ -371,8 +371,8 @@ public class SSLEngineTest
{
// System.err.println("HANDLE "+request.getRequestURI());
SecureRequestCustomizer.SslSessionData sslData = (SecureRequestCustomizer.SslSessionData)
request.getAttribute("org.eclipse.jetty.servlet.request.ssl_session_data");
String sslId = sslData.getId();
request.getAttribute(SecureRequestCustomizer.DEFAULT_SSL_SESSION_DATA_ATTRIBUTE);
String sslId = sslData.sessionId();
assertNotNull(sslId);
Fields fields = Request.extractQueryParameters(request);

View File

@ -117,7 +117,6 @@ public class SSLSelectChannelConnectorLoadTest
tasks[i] = threadPool.submit(workers[i]);
}
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
while (true)
{
Thread.sleep(1000);
@ -126,13 +125,9 @@ public class SSLSelectChannelConnectorLoadTest
{
done &= task.isDone();
}
//System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
if (done)
break;
}
long end = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
//System.err.println();
//System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
for (Worker worker : workers)
{
@ -169,7 +164,6 @@ public class SSLSelectChannelConnectorLoadTest
tasks[i] = threadPool.submit(workers[i]);
}
long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
while (true)
{
Thread.sleep(1000);
@ -178,13 +172,9 @@ public class SSLSelectChannelConnectorLoadTest
{
done &= task.isDone();
}
// System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
if (done)
break;
}
long end = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
// System.err.println();
// System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
threadPool.shutdown();

View File

@ -217,15 +217,15 @@ public class ServerConnectorSslServerTest extends HttpServerTestBase
StringBuilder out = new StringBuilder();
SSLSession session = (SSLSession)request.getAttribute("SSL_SESSION");
SecureRequestCustomizer.SslSessionData data = (SecureRequestCustomizer.SslSessionData)request.getAttribute("SSL_SESSION_data");
SecureRequestCustomizer.SslSessionData data = (SecureRequestCustomizer.SslSessionData)request.getAttribute("SSL_SESSIONData");
out.append("Hello world").append('\n');
out.append("scheme='").append(request.getHttpURI().getScheme()).append("'").append('\n');
out.append("isSecure='").append(request.isSecure()).append("'").append('\n');
out.append("X509Certificate='").append(data == null ? "" : data.getX509Certificates()).append("'").append('\n');
out.append("X509Certificate='").append(data == null ? "" : data.peerCertificates()).append("'").append('\n');
out.append("cipher_suite='").append(session == null ? "" : session.getCipherSuite()).append("'").append('\n');
out.append("key_size='").append(data == null ? "" : data.getKeySize()).append("'").append('\n');
out.append("ssl_session_id='").append(data == null ? "" : data.getId()).append("'").append('\n');
out.append("key_size='").append(data == null ? "" : data.keySize()).append("'").append('\n');
out.append("ssl_session_id='").append(data == null ? "" : data.sessionId()).append("'").append('\n');
out.append("ssl_session='").append(session).append("'").append('\n');
Content.Sink.write(response, true, out.toString(), callback);
}

View File

@ -268,7 +268,7 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
String longType = _dbAdaptor.getLongType();
String stringType = _dbAdaptor.getStringType();
return "create table " + _tableName + " (" + _idColumn + " " + stringType + "(120), " +
return "create table " + getSchemaTableName() + " (" + _idColumn + " " + stringType + "(120), " +
_contextPathColumn + " " + stringType + "(60), " + _virtualHostColumn + " " + stringType + "(60), " + _lastNodeColumn + " " + stringType + "(60), " + _accessTimeColumn + " " + longType + ", " +
_lastAccessTimeColumn + " " + longType + ", " + _createTimeColumn + " " + longType + ", " + _cookieTimeColumn + " " + longType + ", " +
_lastSavedTimeColumn + " " + longType + ", " + _expiryTimeColumn + " " + longType + ", " + _maxIntervalColumn + " " + longType + ", " +

View File

@ -14,8 +14,8 @@
package org.eclipse.jetty.util.ajax;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -40,7 +40,7 @@ public class JSONPojoConvertorFactoryTest
jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
Foo foo = new Foo();
foo.setName("Foo @ " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
foo.setName("Foo @ " + NanoTime.now());
foo.setInt1(1);
foo.setInt2(2);
foo.setLong1(1000001L);
@ -83,7 +83,7 @@ public class JSONPojoConvertorFactoryTest
jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
Foo foo = new Foo();
foo.setName("Foo @ " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
foo.setName("Foo @ " + NanoTime.now());
foo.setInt1(1);
foo.setInt2(2);
foo.setLong1(1000001L);

View File

@ -13,8 +13,7 @@
package org.eclipse.jetty.util.ajax;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -36,7 +35,7 @@ public class JSONPojoConvertorTest
json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class));
Foo foo = new Foo();
foo.setName("Foo @ " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
foo.setName("Foo @ " + NanoTime.now());
foo.setInt1(1);
foo.setInt2(2);
foo.setLong1(1000001L);
@ -76,7 +75,7 @@ public class JSONPojoConvertorTest
json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class, new String[]{"boolean2"}));
Foo foo = new Foo();
foo.setName("Foo @ " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
foo.setName("Foo @ " + NanoTime.now());
foo.setInt1(1);
foo.setInt2(2);
foo.setLong1(1000001L);

View File

@ -1071,16 +1071,18 @@ public class BufferUtil
public static ByteBuffer toMappedBuffer(Resource resource) throws IOException
{
if (!resource.isMemoryMappable())
Path path = resource.getPath();
if (path == null || !path.toUri().getScheme().endsWith("file"))
return null;
return toMappedBuffer(resource.getPath());
return toMappedBuffer(path);
}
public static ByteBuffer toMappedBuffer(Resource resource, long pos, long len) throws IOException
{
if (!resource.isMemoryMappable())
Path path = resource.getPath();
if (path == null || !path.toUri().getScheme().endsWith("file"))
return null;
return toMappedBuffer(resource.getPath(), pos, len);
return toMappedBuffer(path, pos, len);
}
public static String toSummaryString(ByteBuffer buffer)

View File

@ -0,0 +1,180 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.util.concurrent.TimeUnit;
/**
* <p>Utility class with methods that deal with {@link System#nanoTime()}.</p>
*/
public class NanoTime
{
/**
* @return the current nanoTime via {@link System#nanoTime()}
*/
public static long now()
{
return System.nanoTime();
}
/**
* <p>Calculates the nanoseconds elapsed between two nanoTimes.</p>
*
* @param beginNanoTime the begin nanoTime
* @param endNanoTime the end nanoTime
* @return the nanoseconds elapsed
*/
public static long elapsed(long beginNanoTime, long endNanoTime)
{
return endNanoTime - beginNanoTime;
}
/**
* <p>Calculates the nanoseconds elapsed since a begin nanoTime and the current nanoTime.</p>
*
* @param beginNanoTime the begin nanoTime
* @return the nanoseconds elapsed since the given begin nanoTime and the current nanoTime
*/
public static long since(long beginNanoTime)
{
return elapsed(beginNanoTime, now());
}
/**
* <p>Calculates the nanoseconds remaining from the current nanoTime until an end nanoTime.</p>
*
* @param endNanoTime the end nanoTime
* @return the nanoseconds remaining from the current nanoTime until the given end nanoTime
*/
public static long until(long endNanoTime)
{
return elapsed(now(), endNanoTime);
}
/**
* <p>Calculates the milliseconds elapsed between two nanoTimes.</p>
*
* @param beginNanoTime the begin nanoTime
* @param endNanoTime the end nanoTime
* @return the milliseconds elapsed
*/
public static long millisElapsed(long beginNanoTime, long endNanoTime)
{
return TimeUnit.NANOSECONDS.toMillis(elapsed(beginNanoTime, endNanoTime));
}
/**
* <p>Calculates the milliseconds elapsed between a begin nanoTime and the current nanoTime.</p>
*
* @param beginNanoTime the begin nanoTime
* @return the milliseconds elapsed between the given begin nanoTime and the current nanoTime
*/
public static long millisSince(long beginNanoTime)
{
return millisElapsed(beginNanoTime, now());
}
/**
* <p>Calculates the milliseconds remaining between the current nanoTime and an end nanoTime.</p>
*
* @param endNanoTime the end nanoTime
* @return the milliseconds remaining between the current nanoTime and the given end nanoTime
*/
public static long millisUntil(long endNanoTime)
{
return millisElapsed(now(), endNanoTime);
}
/**
* <p>Calculates the seconds elapsed between two nanoTimes.</p>
*
* @param beginNanoTime the begin nanoTime
* @param endNanoTime the end nanoTime
* @return the seconds elapsed
*/
public static long secondsElapsed(long beginNanoTime, long endNanoTime)
{
return TimeUnit.NANOSECONDS.toSeconds(elapsed(beginNanoTime, endNanoTime));
}
/**
* <p>Calculates the seconds elapsed between a begin nanoTime and the current nanoTime.</p>
*
* @param beginNanoTime the begin nanoTime
* @return the seconds elapsed between the given begin nanoTime and the current nanoTime
*/
public static long secondsSince(long beginNanoTime)
{
return secondsElapsed(beginNanoTime, now());
}
/**
* <p>Calculates the seconds remaining between the current nanoTime and an end nanoTime.</p>
*
* @param endNanoTime the end nanoTime
* @return the seconds remaining between the current nanoTime and the given end nanoTime
*/
public static long secondsUntil(long endNanoTime)
{
return secondsElapsed(now(), endNanoTime);
}
/**
* <p>Returns whether the first nanoTime is strictly before the second nanoTime.</p>
* <p>Reads as: {@code "is nanoTime1 strictly before nanoTime2?"}.</p>
* <p>Avoids the common mistake of comparing the 2 nanoTimes with the
* less-than {@code <} operator, which cannot be used when comparing nanoTimes,
* as specified in {@link System#nanoTime()}.</p>
*
* @param nanoTime1 the first nanoTime
* @param nanoTime2 the second nanoTime
* @return whether the first nanoTime is strictly before the second nanoTime
* @see #isBeforeOrSame(long, long)
*/
public static boolean isBefore(long nanoTime1, long nanoTime2)
{
return nanoTime1 - nanoTime2 < 0;
}
/**
* <p>Returns whether the first nanoTime is before or the same as the second nanoTime.</p>
*
* @param nanoTime1 the first nanoTime
* @param nanoTime2 the second nanoTime
* @return whether the first nanoTime is before or the same as the second nanoTime
* @see #isBefore(long, long)
*/
public static boolean isBeforeOrSame(long nanoTime1, long nanoTime2)
{
return nanoTime1 - nanoTime2 <= 0;
}
/**
* <p>Spin waits for the specified number of nanoseconds.</p>
*
* @param nanos the amount of nanoseconds to spin wait
*/
public static void spinWait(long nanos)
{
long start = now();
while (since(start) < nanos)
{
Thread.onSpinWait();
}
}
private NanoTime()
{
}
}

View File

@ -114,7 +114,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
public void setPauseUntil(long time)
{
if (time > pauseUntil)
if (NanoTime.isBefore(pauseUntil, time))
pauseUntil = time;
}
@ -122,7 +122,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
{
if (pauseUntil == 0)
return false;
if (pauseUntil > now)
if (NanoTime.isBefore(now, pauseUntil))
{
if (LOG.isDebugEnabled())
LOG.debug("PAUSED {}", this);
@ -494,32 +494,28 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
}
}
public static enum DirAction
public enum DirAction
{
IGNORE, WATCH, ENTER;
IGNORE, WATCH, ENTER
}
/**
* Listener for path change events
*/
public static interface Listener extends EventListener
public interface Listener extends EventListener
{
void onPathWatchEvent(PathWatchEvent event);
}
/**
* EventListListener
*
* Listener that reports accumulated events in one shot
*/
public static interface EventListListener extends EventListener
public interface EventListListener extends EventListener
{
void onPathWatchEvents(List<PathWatchEvent> events);
}
/**
* PathWatchEvent
*
* Represents a file event. Reported to registered listeners.
*/
public class PathWatchEvent
@ -536,7 +532,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
this.path = path;
this.type = type;
this.config = config;
checked = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
checked = NanoTime.now();
check();
}
@ -565,7 +561,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
this.type = PathWatchEventType.UNKNOWN;
}
this.config = config;
checked = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
checked = NanoTime.now();
check();
}
@ -599,7 +595,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
check();
if (lastModified == modified && lastLength == length)
return (now - checked) >= quietTime;
return NanoTime.elapsed(checked, now) >= quietTime;
checked = now;
return false;
@ -607,7 +603,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
public long toQuietCheck(long now, long quietTime)
{
long check = quietTime - (now - checked);
long check = quietTime - NanoTime.elapsed(checked, now);
if (check <= 0)
return quietTime;
return check;
@ -615,10 +611,10 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
public void modified()
{
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long now = NanoTime.now();
checked = now;
check();
config.setPauseUntil(now + getUpdateQuietTimeMillis());
config.setPauseUntil(now + getUpdateQuietTimeNanos());
}
@Override
@ -683,13 +679,11 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
}
/**
* PathWatchEventType
*
* Type of an event
*/
public static enum PathWatchEventType
public enum PathWatchEventType
{
ADDED, DELETED, MODIFIED, UNKNOWN;
ADDED, DELETED, MODIFIED, UNKNOWN
}
private static final boolean IS_WINDOWS;
@ -853,7 +847,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
this.watchService = FileSystems.getDefault().newWatchService();
//ensure setting of quiet time is appropriate now we have a watcher
setUpdateQuietTime(getUpdateQuietTimeMillis(), TimeUnit.MILLISECONDS);
setUpdateQuietTime(getUpdateQuietTimeNanos(), TimeUnit.NANOSECONDS);
// Register all watched paths, walking dir hierarchies as needed, possibly generating
// fake add events if notifyExistingOnStart is true
@ -922,14 +916,9 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
return listeners.iterator();
}
/**
* Change the quiet time.
*
* @return the quiet time in millis
*/
public long getUpdateQuietTimeMillis()
long getUpdateQuietTimeNanos()
{
return TimeUnit.MILLISECONDS.convert(updateQuietTimeDuration, updateQuietTimeUnit);
return TimeUnit.NANOSECONDS.convert(updateQuietTimeDuration, updateQuietTimeUnit);
}
private void registerTree(Path dir, Config config, boolean notify) throws IOException
@ -1039,8 +1028,6 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
* single MODIFY event. Both the accumulation of events and coalescing of MODIFY
* events reduce the number and frequency of event reporting for "noisy" files (ie
* those that are undergoing rapid change).
*
* @see java.lang.Runnable#run()
*/
@Override
public void run()
@ -1051,7 +1038,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
LOG.debug("Starting java.nio file watching with {}", watchService);
}
long waitTime = getUpdateQuietTimeMillis();
long waitTime = getUpdateQuietTimeNanos();
WatchService watch = watchService;
@ -1063,7 +1050,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
try
{
// Reset all keys before watching
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long now = NanoTime.now();
for (Map.Entry<WatchKey, Config> e : keys.entrySet())
{
WatchKey k = e.getKey();
@ -1081,7 +1068,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("Waiting for poll({})", waitTime);
key = waitTime < 0 ? watch.take() : waitTime > 0 ? watch.poll(waitTime, updateQuietTimeUnit) : watch.poll();
key = waitTime < 0 ? watch.take() : waitTime > 0 ? watch.poll(waitTime, TimeUnit.NANOSECONDS) : watch.poll();
// handle all active keys
while (key != null)
@ -1218,7 +1205,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("processPending> {}", pending.values());
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long now = NanoTime.now();
long wait = Long.MAX_VALUE;
// pending map is maintained in LRU order
@ -1230,7 +1217,8 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
continue;
// if the path is quiet move to events
if (event.isQuiet(now, getUpdateQuietTimeMillis()))
long quietTime = getUpdateQuietTimeNanos();
if (event.isQuiet(now, quietTime))
{
if (LOG.isDebugEnabled())
LOG.debug("isQuiet {}", event);
@ -1239,11 +1227,11 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable
}
else
{
long msToCheck = event.toQuietCheck(now, getUpdateQuietTimeMillis());
long nsToCheck = event.toQuietCheck(now, quietTime);
if (LOG.isDebugEnabled())
LOG.debug("pending {} {}", event, msToCheck);
if (msToCheck < wait)
wait = msToCheck;
LOG.debug("pending {} {}", event, nsToCheck);
if (nsToCheck < wait)
wait = nsToCheck;
}
}
if (LOG.isDebugEnabled())

View File

@ -129,7 +129,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
public Pool(StrategyType strategyType, int maxEntries, boolean cache, ToIntFunction<T> maxMultiplex)
{
this.maxEntries = maxEntries;
this.strategyType = strategyType;
this.strategyType = Objects.requireNonNull(strategyType);
this.cache = cache ? new ThreadLocal<>() : null;
this.nextIndex = strategyType == StrategyType.ROUND_ROBIN ? new AtomicInteger() : null;
this.maxMultiplex = Objects.requireNonNull(maxMultiplex);
@ -205,14 +205,25 @@ public class Pool<T> implements AutoCloseable, Dumpable
try (AutoLock ignored = lock.lock())
{
if (closed)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("{} is closed, returning null reserved entry", this);
return null;
}
// If we have no space
if (maxEntries > 0 && entries.size() >= maxEntries)
int entriesSize = entries.size();
if (maxEntries > 0 && entriesSize >= maxEntries)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("{} has no space: {} >= {}, returning null reserved entry", this, entriesSize, maxEntries);
return null;
}
Entry entry = new Entry();
entries.add(entry);
if (LOGGER.isDebugEnabled())
LOGGER.debug("{} returning new reserved entry {}", this, entry);
return entry;
}
}
@ -368,6 +379,9 @@ public class Pool<T> implements AutoCloseable, Dumpable
@Override
public void close()
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Closing {}", this);
List<Entry> copy;
try (AutoLock ignored = lock.lock())
{

View File

@ -161,11 +161,10 @@ public interface SocketAddressResolver
try
{
long start = System.nanoTime();
long start = NanoTime.now();
InetAddress[] addresses = InetAddress.getAllByName(host);
long elapsed = System.nanoTime() - start;
if (LOG.isDebugEnabled())
LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
LOG.debug("Resolved {} in {} ms", host, NanoTime.millisSince(start));
List<InetSocketAddress> result = new ArrayList<>(addresses.length);
for (InetAddress address : addresses)

View File

@ -23,6 +23,7 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.URIUtil;
@ -235,25 +236,6 @@ public class PathResource extends Resource
return Files.exists(alias != null ? alias : path);
}
@Override
public boolean isSame(Resource resource)
{
try
{
if (resource instanceof PathResource)
{
Path path = resource.getPath();
return Files.isSameFile(getPath(), path);
}
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.debug("ignored", e);
}
return false;
}
@Override
public boolean equals(Object obj)
{
@ -270,18 +252,7 @@ public class PathResource extends Resource
return false;
}
PathResource other = (PathResource)obj;
if (path == null)
{
if (other.path != null)
{
return false;
}
}
else if (!path.equals(other.path))
{
return false;
}
return true;
return Objects.equals(path, other.path);
}
/**
@ -298,12 +269,6 @@ public class PathResource extends Resource
return path.toAbsolutePath().toString();
}
@Override
public boolean isMemoryMappable()
{
return "file".equalsIgnoreCase(uri.getScheme());
}
@Override
public URI getURI()
{
@ -313,10 +278,7 @@ public class PathResource extends Resource
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((path == null) ? 0 : path.hashCode());
return result;
return Objects.hashCode(path);
}
@Override

View File

@ -32,6 +32,7 @@ import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jetty.util.IO;
@ -47,7 +48,7 @@ import org.slf4j.LoggerFactory;
* Supports real filesystems, and also <a href="https://docs.oracle.com/en/java/javase/17/docs/api/jdk.zipfs/module-summary.html">ZipFS</a>.
* </p>
*/
public abstract class Resource
public abstract class Resource implements Iterable<Resource>
{
private static final Logger LOG = LoggerFactory.getLogger(Resource.class);
private static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
@ -143,16 +144,18 @@ public abstract class Resource
public abstract boolean isContainedIn(Resource r);
/**
* Return true if the passed Resource represents the same resource as the Resource.
* For many resource types, this is equivalent to {@link #equals(Object)}, however
* for resources types that support aliasing, this maybe some other check (e.g. {@link java.nio.file.Files#isSameFile(Path, Path)}).
* Return an Iterator of all Resource's referenced in this Resource.
*
* @param resource The resource to check
* @return true if the passed resource represents the same resource.
* <p>
* This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator.
* </p>
*
* @return the iterator of Resources.
*/
public boolean isSame(Resource resource)
@Override
public Iterator<Resource> iterator()
{
return equals(resource);
return List.of(this).iterator();
}
/**
@ -255,16 +258,6 @@ public abstract class Resource
return Files.newByteChannel(getPath(), StandardOpenOption.READ);
}
/**
* Checks if the resource supports being loaded as a memory-mapped ByteBuffer.
*
* @return true if the resource supports memory-mapped ByteBuffer, false otherwise.
*/
public boolean isMemoryMappable()
{
return false;
}
/**
* list of resource names contained in the given resource.
* Ordering is unspecified, so callers may wish to sort the return value to ensure deterministic behavior.

View File

@ -23,7 +23,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.URIUtil;
@ -257,6 +259,12 @@ public class ResourceCollection extends Resource
return -1;
}
@Override
public Iterator<Resource> iterator()
{
return _resources.iterator();
}
/**
* @return The list of resource names(merged) contained in the collection of resources.
*/
@ -286,6 +294,23 @@ public class ResourceCollection extends Resource
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ResourceCollection other = (ResourceCollection)o;
return Objects.equals(_resources, other._resources);
}
@Override
public int hashCode()
{
return Objects.hash(_resources);
}
/**
* @return the list of resources
*/

View File

@ -13,17 +13,18 @@
package org.eclipse.jetty.util.statistic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import org.eclipse.jetty.util.NanoTime;
/**
* Counts the rate that {@link Long}s are added to this from the time of creation or the last call to {@link #reset()}.
*/
public class RateCounter
{
private final LongAdder _total = new LongAdder();
private final AtomicLong _timeStamp = new AtomicLong(System.nanoTime());
private final AtomicLong _nanoTime = new AtomicLong(NanoTime.now());
public void add(long l)
{
@ -32,13 +33,13 @@ public class RateCounter
public long getRate()
{
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - _timeStamp.get());
long elapsed = NanoTime.millisSince(_nanoTime.get());
return elapsed == 0 ? 0 : _total.sum() * 1000 / elapsed;
}
public void reset()
{
_timeStamp.getAndSet(System.nanoTime());
_nanoTime.set(NanoTime.now());
_total.reset();
}
}

View File

@ -18,6 +18,7 @@ import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.AutoLock;
/**
@ -69,14 +70,13 @@ public class RateStatistic
private void update()
{
update(System.nanoTime());
update(NanoTime.now());
}
private void update(long now)
{
long expire = now - _nanoPeriod;
Long head = _samples.peekFirst();
while (head != null && head < expire)
while (head != null && NanoTime.elapsed(head, now) > _nanoPeriod)
{
_samples.removeFirst();
head = _samples.peekFirst();
@ -104,7 +104,7 @@ public class RateStatistic
*/
public int record()
{
long now = System.nanoTime();
long now = NanoTime.now();
try (AutoLock l = _lock.lock())
{
_count++;
@ -151,7 +151,7 @@ public class RateStatistic
Long head = _samples.peekFirst();
if (head == null)
return -1;
return units.convert(System.nanoTime() - head, TimeUnit.NANOSECONDS);
return units.convert(NanoTime.since(head), TimeUnit.NANOSECONDS);
}
}
@ -173,11 +173,11 @@ public class RateStatistic
public String dump(TimeUnit units)
{
long now = System.nanoTime();
long now = NanoTime.now();
try (AutoLock l = _lock.lock())
{
String samples = _samples.stream()
.mapToLong(t -> units.convert(now - t, TimeUnit.NANOSECONDS))
.mapToLong(t -> units.convert(NanoTime.elapsed(t, now), TimeUnit.NANOSECONDS))
.mapToObj(Long::toString)
.collect(Collectors.joining(System.lineSeparator()));
return String.format("%s%n%s", toString(now), samples);
@ -187,7 +187,7 @@ public class RateStatistic
@Override
public String toString()
{
return toString(System.nanoTime());
return toString(NanoTime.now());
}
private String toString(long nanoTime)

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.util.thread;
import java.util.concurrent.BlockingQueue;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -56,24 +57,24 @@ public class MonitoredQueuedThreadPool extends QueuedThreadPool
public void execute(final Runnable job)
{
queueStats.increment();
long begin = System.nanoTime();
long begin = NanoTime.now();
super.execute(new Runnable()
{
@Override
public void run()
{
long queueLatency = System.nanoTime() - begin;
long queueLatency = NanoTime.since(begin);
queueStats.decrement();
threadStats.increment();
queueLatencyStats.record(queueLatency);
long start = System.nanoTime();
long start = NanoTime.now();
try
{
job.run();
}
finally
{
long taskLatency = System.nanoTime() - start;
long taskLatency = NanoTime.since(start);
threadStats.decrement();
taskLatencyStats.record(taskLatency);
}

View File

@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.VirtualThreads;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -216,7 +217,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
}
addBean(_tryExecutor);
_lastShrink.set(System.nanoTime());
_lastShrink.set(NanoTime.now());
super.doStart();
// The threads count set to MIN_VALUE is used to signal to Runners that the pool is stopped.
@ -249,7 +250,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
break;
// try to let jobs complete naturally for half our stop time
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
joinThreads(NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
// If we still have threads running, get a bit more aggressive
@ -264,7 +265,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
}
// wait again for the other half of our stop time
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
joinThreads(NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
Thread.yield();
@ -323,7 +324,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
if (thread == Thread.currentThread())
continue;
long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
long canWait = NanoTime.millisUntil(stopByNanos);
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canWait);
if (canWait <= 0)
@ -826,7 +827,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
if (LOG.isDebugEnabled())
LOG.debug("Starting {}", thread);
_threads.add(thread);
_lastShrink.set(System.nanoTime());
_lastShrink.set(NanoTime.now());
thread.start();
started = true;
}
@ -1050,8 +1051,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactor
if (idleTimeout > 0 && getThreads() > _minThreads)
{
long last = _lastShrink.get();
long now = System.nanoTime();
if ((now - last) > TimeUnit.MILLISECONDS.toNanos(idleTimeout) && _lastShrink.compareAndSet(last, now))
long now = NanoTime.now();
if (NanoTime.millisElapsed(last, now) > idleTimeout && _lastShrink.compareAndSet(last, now))
{
if (LOG.isDebugEnabled())
LOG.debug("shrinking {}", QueuedThreadPool.this);

Some files were not shown because too many files have changed in this diff Show More