Merged branch 'jetty-10.0.x' into 'jetty-11.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2022-10-05 11:51:37 +02:00
commit 76b6f9cfe7
No known key found for this signature in database
GPG Key ID: 1677D141BCF3584D
15 changed files with 301 additions and 34 deletions

View File

@ -106,6 +106,7 @@ public abstract class HttpConnection implements IConnection, Attachable
SendFailure result;
if (channel.associate(exchange))
{
request.sent();
requestTimeouts.schedule(channel);
channel.send();
result = null;

View File

@ -311,6 +311,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{
if (enqueue(exchanges, exchange))
{
request.sent();
requestTimeouts.schedule(exchange);
if (!client.isRunning() && exchanges.remove(exchange))
{

View File

@ -19,6 +19,7 @@ import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
@ -195,10 +196,14 @@ public class HttpProxy extends ProxyConfiguration.Proxy
String target = destination.getOrigin().getAddress().asString();
Origin.Address proxyAddress = destination.getConnectAddress();
HttpClient httpClient = destination.getHttpClient();
long connectTimeout = httpClient.getConnectTimeout();
Request connect = new TunnelRequest(httpClient, proxyAddress)
.method(HttpMethod.CONNECT)
.path(target)
.headers(headers -> headers.put(HttpHeader.HOST, target));
.headers(headers -> headers.put(HttpHeader.HOST, target))
// Use the connect timeout as a total timeout,
// since this request is to "connect" to the server.
.timeout(connectTimeout, TimeUnit.MILLISECONDS);
ProxyConfiguration.Proxy proxy = destination.getProxy();
if (proxy.isSecure())
connect.scheme(HttpScheme.HTTPS.asString());
@ -234,7 +239,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
private void tunnelFailed(EndPoint endPoint, Throwable failure)
{
endPoint.close();
endPoint.close(failure);
promise.failed(failure);
}

View File

@ -816,15 +816,17 @@ public class HttpRequest implements Request
{
if (listener != null)
responseListeners.add(listener);
sent();
sender.accept(this, responseListeners);
}
void sent()
{
long timeout = getTimeout();
if (timeout > 0)
timeoutNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout);
if (timeoutNanoTime == Long.MAX_VALUE)
{
long timeout = getTimeout();
if (timeout > 0)
timeoutNanoTime = NanoTime.now() + TimeUnit.MILLISECONDS.toNanos(timeout);
}
}
/**

View File

@ -19,6 +19,7 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -140,12 +141,21 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
@Override
public void failed(Throwable x)
{
close();
if (LOG.isDebugEnabled())
LOG.debug("SOCKS4 failure", x);
getEndPoint().close(x);
@SuppressWarnings("unchecked")
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
promise.failed(x);
}
@Override
public boolean onIdleExpired()
{
failed(new TimeoutException("Idle timeout expired"));
return false;
}
@Override
public void onFillable()
{

View File

@ -131,7 +131,10 @@ public class HttpChannelOverHTTP extends HttpChannel
{
if (LOG.isDebugEnabled())
LOG.debug("Closing, reason: {} - {}", closeReason, connection);
connection.close();
if (result.isFailed())
connection.close(result.getFailure());
else
connection.close();
}
else
{

View File

@ -201,6 +201,16 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
return receiver.onUpgradeFrom();
}
void onResponseHeaders(HttpExchange exchange)
{
HttpRequest request = exchange.getRequest();
if (request instanceof HttpProxy.TunnelRequest)
{
// Restore idle timeout
getEndPoint().setIdleTimeout(idleTimeout);
}
}
public void release()
{
// Restore idle timeout
@ -208,6 +218,11 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
getHttpDestination().release(this);
}
public void remove()
{
getHttpDestination().remove(this);
}
@Override
public void close()
{
@ -241,14 +256,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
{
if (!closed.get())
return false;
if (sweeps.incrementAndGet() < 4)
return false;
return true;
}
public void remove()
{
getHttpDestination().remove(this);
return sweeps.incrementAndGet() > 3;
}
@Override
@ -300,9 +308,8 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
if (request instanceof HttpProxy.TunnelRequest)
{
long connectTimeout = getHttpClient().getConnectTimeout();
request.timeout(connectTimeout, TimeUnit.MILLISECONDS)
.idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS);
// Override the idle timeout in case it is shorter than the connect timeout.
request.idleTimeout(2 * getHttpClient().getConnectTimeout(), TimeUnit.MILLISECONDS);
}
HttpConversation conversation = request.getConversation();

View File

@ -327,6 +327,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
// Store the EndPoint is case of upgrades, tunnels, etc.
exchange.getRequest().getConversation().setAttribute(EndPoint.class.getName(), getHttpConnection().getEndPoint());
getHttpConnection().onResponseHeaders(exchange);
return !responseHeaders(exchange);
}

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
@ -21,11 +22,15 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -34,19 +39,22 @@ 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.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class Socks4ProxyTest
{
private ServerSocketChannel server;
private ServerSocketChannel proxy;
private HttpClient client;
@BeforeEach
public void prepare() throws Exception
{
server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 0));
proxy = ServerSocketChannel.open();
proxy.bind(new InetSocketAddress("localhost", 0));
ClientConnector connector = new ClientConnector();
QueuedThreadPool clientThreads = new QueuedThreadPool();
@ -62,13 +70,13 @@ public class Socks4ProxyTest
public void dispose() throws Exception
{
client.stop();
server.close();
proxy.close();
}
@Test
public void testSocks4Proxy() throws Exception
{
int proxyPort = server.socket().getLocalPort();
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
CountDownLatch latch = new CountDownLatch(1);
@ -91,7 +99,7 @@ public class Socks4ProxyTest
latch.countDown();
});
try (SocketChannel channel = server.accept())
try (SocketChannel channel = proxy.accept())
{
int socks4MessageLength = 9;
ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
@ -130,7 +138,7 @@ public class Socks4ProxyTest
@Test
public void testSocks4ProxyWithSplitResponse() throws Exception
{
int proxyPort = server.socket().getLocalPort();
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
CountDownLatch latch = new CountDownLatch(1);
@ -150,7 +158,7 @@ public class Socks4ProxyTest
result.getFailure().printStackTrace();
});
try (SocketChannel channel = server.accept())
try (SocketChannel channel = proxy.accept())
{
int socks4MessageLength = 9;
ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
@ -189,7 +197,7 @@ public class Socks4ProxyTest
public void testSocks4ProxyWithTLSServer() throws Exception
{
String proxyHost = "localhost";
int proxyPort = server.socket().getLocalPort();
int proxyPort = proxy.socket().getLocalPort();
String serverHost = "127.0.0.13"; // Server host different from proxy host.
int serverPort = proxyPort + 1; // Any port will do.
@ -221,7 +229,7 @@ public class Socks4ProxyTest
result.getFailure().printStackTrace();
});
try (SocketChannel channel = server.accept())
try (SocketChannel channel = proxy.accept())
{
int socks4MessageLength = 9;
ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
@ -269,4 +277,69 @@ public class Socks4ProxyTest
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@Test
public void testRequestTimeoutWhenSocksProxyDoesNotRespond() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
long timeout = 1000;
Request request = client.newRequest("localhost", proxyPort + 1)
.timeout(timeout, TimeUnit.MILLISECONDS);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
try (SocketChannel ignored = proxy.accept())
{
// Accept the connection, but do not reply and don't close.
ExecutionException x = assertThrows(ExecutionException.class, () -> listener.get(2 * timeout, TimeUnit.MILLISECONDS));
assertThat(x.getCause(), instanceOf(TimeoutException.class));
}
}
@Test
public void testIdleTimeoutWhenSocksProxyDoesNotRespond() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
long idleTimeout = 1000;
client.setIdleTimeout(idleTimeout);
Request request = client.newRequest("localhost", proxyPort + 1);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
try (SocketChannel ignored = proxy.accept())
{
// Accept the connection, but do not reply and don't close.
ExecutionException x = assertThrows(ExecutionException.class, () -> listener.get(2 * idleTimeout, TimeUnit.MILLISECONDS));
assertThat(x.getCause(), instanceOf(TimeoutException.class));
}
}
@Test
public void testSocksProxyClosesConnectionImmediately() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
Request request = client.newRequest("localhost", proxyPort + 1);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
try (SocketChannel channel = proxy.accept())
{
// Immediately close the connection.
channel.close();
ExecutionException x = assertThrows(ExecutionException.class, () -> listener.get(5, TimeUnit.SECONDS));
assertThat(x.getCause(), instanceOf(IOException.class));
}
}
}

View File

@ -80,6 +80,9 @@ public abstract class IdleTimeout
long old = _idleTimeout;
_idleTimeout = idleTimeout;
if (LOG.isDebugEnabled())
LOG.debug("Setting idle timeout {} -> {} on {}", old, idleTimeout, this);
// Do we have an old timeout
if (old > 0)
{

View File

@ -166,7 +166,8 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
public void connect(SelectableChannel channel, Object attachment)
{
ManagedSelector set = chooseSelector();
set.submit(set.new Connect(channel, attachment));
if (set != null)
set.submit(set.new Connect(channel, attachment));
}
/**

View File

@ -24,6 +24,7 @@ import java.security.Principal;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import javax.net.ssl.KeyManager;
@ -40,6 +41,7 @@ import org.eclipse.jetty.client.Origin;
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.Result;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.FutureResponseListener;
@ -61,7 +63,6 @@ import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Promise;
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.Assumptions;
import org.junit.jupiter.api.Disabled;
@ -71,6 +72,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -413,7 +415,7 @@ public class ForwardProxyTLSServerTest
.timeout(5, TimeUnit.SECONDS)
.send();
});
assertThat(x.getCause(), Matchers.instanceOf(ConnectException.class));
assertThat(x.getCause(), instanceOf(ConnectException.class));
httpClient.stop();
}
@ -829,6 +831,135 @@ public class ForwardProxyTLSServerTest
}
}
@ParameterizedTest
@MethodSource("proxyTLS")
public void testServerLongProcessing(SslContextFactory.Server proxyTLS) throws Exception
{
long timeout = 500;
startTLSServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
sleep(3 * timeout);
baseRequest.setHandled(true);
}
});
startProxy(proxyTLS);
HttpClient httpClient = newHttpClient();
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.setConnectTimeout(timeout);
httpClient.setIdleTimeout(4 * timeout);
httpClient.start();
try
{
// The idle timeout is larger than the server processing time, request should succeed.
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
@ParameterizedTest
@MethodSource("proxyTLS")
public void testServerLongProcessingWithRequestIdleTimeout(SslContextFactory.Server proxyTLS) throws Exception
{
long timeout = 500;
startTLSServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
sleep(3 * timeout);
baseRequest.setHandled(true);
}
});
startProxy(proxyTLS);
HttpClient httpClient = newHttpClient();
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.setConnectTimeout(timeout);
// Short idle timeout for HttpClient.
httpClient.setIdleTimeout(timeout);
httpClient.start();
try
{
// The idle timeout is larger than the server processing time, request should succeed.
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
// Long idle timeout for the request, should override that of the client.
.idleTimeout(4 * timeout, TimeUnit.MILLISECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
@ParameterizedTest
@MethodSource("proxyTLS")
public void testProxyLongProcessing(SslContextFactory.Server proxyTLS) throws Exception
{
long timeout = 500;
startTLSServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
}
});
startProxy(proxyTLS, new ConnectHandler()
{
@Override
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
{
sleep(3 * timeout);
super.handleConnect(baseRequest, request, response, serverAddress);
}
});
HttpClient httpClient = newHttpClient();
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.setConnectTimeout(timeout);
httpClient.setIdleTimeout(10 * timeout);
httpClient.start();
try
{
// Connecting to the server through the proxy involves a CONNECT + 200
// so if the proxy delays the response, the client request interprets
// it as a "connect" timeout (rather than an idle timeout).
AtomicReference<Result> resultRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send(result ->
{
resultRef.set(result);
latch.countDown();
});
assertTrue(latch.await(2 * timeout, TimeUnit.MILLISECONDS));
Result result = resultRef.get();
assertTrue(result.isFailed());
assertThat(result.getFailure(), instanceOf(TimeoutException.class));
}
finally
{
httpClient.stop();
}
}
@Test
@Tag("external")
@Disabled

View File

@ -1,4 +1,3 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG

View File

@ -817,7 +817,7 @@ public class StartArgs
{
for (Prop p : properties)
{
cmd.addRawArg(CommandLineBuilder.quote(p.key) + "=" + CommandLineBuilder.quote(p.value));
cmd.addRawArg(CommandLineBuilder.quote(p.key) + "=" + CommandLineBuilder.quote(properties.expand(p.value)));
}
}
else if (properties.size() > 0)

View File

@ -25,6 +25,7 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -64,6 +65,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -1229,4 +1231,32 @@ public class DistributionTests extends AbstractJettyHomeTest
openIdProvider.stop();
}
}
@Test
public void testDryRunProperties() throws Exception
{
Path jettyBase = newTestJettyBaseDirectory();
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.jettyBase(jettyBase)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {"--add-to-start=server,logging-jetty"};
try (JettyHomeTester.Run run1 = distribution.start(args1))
{
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
String[] args2 = {"--dry-run"};
try (JettyHomeTester.Run run2 = distribution.start(args2))
{
run2.awaitFor(5, TimeUnit.SECONDS);
Queue<String> logs = run2.getLogs();
assertThat(logs.size(), equalTo(1));
assertThat(logs.poll(), not(containsString("${jetty.home.uri}")));
}
}
}
}