diff options
7 files changed, 336 insertions, 127 deletions
diff --git a/VERSION.txt b/VERSION.txt index 7fbb50ef74..e55b713d0d 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -24,6 +24,16 @@ jetty-9.3.2.v20150730 - 30 July 2015 before query when using prefix. + 473832 SslConnection flips back buffers on handshake exception +jetty-9.2.13.v20150730 - 30 July 2015 + + 472859 ConcatServlet may expose protected resources. + + 473006 Encode addPath in URLResource + + 473243 Delay resource close for async default content + + 473266 Better handling of MultiException + + 473322 GatherWrite limit handling + + 473624 ProxyServlet.Transparent / TransparentDelegate add trailing slash + before query when using prefix. + + 473832 SslConnection flips back buffers on handshake exception + jetty-9.3.1.v20150714 - 14 July 2015 + 441020 Support HEADERS followed by CONTINUATION+. + 460671 Rationalize property names (fix for jetty.sh) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java index 5795cfb03a..cc6040ec7f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java @@ -88,15 +88,24 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp if (bindAddress != null) channel.bind(bindAddress); configure(client, channel); - channel.configureBlocking(false); context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost()); context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort()); - if (channel.connect(address)) + if (client.isConnectBlocking()) + { + channel.socket().connect(address, (int)client.getConnectTimeout()); + channel.configureBlocking(false); selectorManager.accept(channel, context); + } else - selectorManager.connect(channel, context); + { + channel.configureBlocking(false); + if (channel.connect(address)) + selectorManager.accept(channel, context); + else + selectorManager.connect(channel, context); + } } // Must catch all exceptions, since some like // UnresolvedAddressException are not IOExceptions. diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 3b6da5bee4..650f1103ab 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -22,8 +22,10 @@ import java.io.IOException; import java.net.CookieManager; import java.net.CookiePolicy; import java.net.CookieStore; +import java.net.Socket; import java.net.SocketAddress; import java.net.URI; +import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collection; @@ -137,6 +139,7 @@ public class HttpClient extends ContainerLifeCycle private volatile boolean strictEventOrdering = false; private volatile HttpField encodingField; private volatile boolean removeIdleDestinations = false; + private volatile boolean connectBlocking = false; /** * Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only @@ -208,7 +211,9 @@ public class HttpClient extends ContainerLifeCycle transport.setHttpClient(this); addBean(transport); - resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); + if (resolver == null) + resolver = new SocketAddressResolver.Async(executor, scheduler, getAddressResolutionTimeout()); + addBean(resolver); handlers.put(new ContinueProtocolHandler()); handlers.put(new RedirectProtocolHandler(this)); @@ -604,7 +609,8 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the timeout, in milliseconds, for the DNS resolution of host addresses + * @return the timeout, in milliseconds, for the default {@link SocketAddressResolver} created at startup + * @see #getSocketAddressResolver() */ public long getAddressResolutionTimeout() { @@ -612,7 +618,13 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses + * <p>Sets the socket address resolution timeout used by the default {@link SocketAddressResolver} + * created by this {@link HttpClient} at startup.</p> + * <p>For more fine tuned configuration of socket address resolution, see + * {@link #setSocketAddressResolver(SocketAddressResolver)}.</p> + * + * @param addressResolutionTimeout the timeout, in milliseconds, for the default {@link SocketAddressResolver} created at startup + * @see #setSocketAddressResolver(SocketAddressResolver) */ public void setAddressResolutionTimeout(long addressResolutionTimeout) { @@ -723,6 +735,22 @@ public class HttpClient extends ContainerLifeCycle } /** + * @return the {@link SocketAddressResolver} of this {@link HttpClient} + */ + public SocketAddressResolver getSocketAddressResolver() + { + return resolver; + } + + /** + * @param resolver the {@link SocketAddressResolver} of this {@link HttpClient} + */ + public void setSocketAddressResolver(SocketAddressResolver resolver) + { + this.resolver = resolver; + } + + /** * @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s */ public int getMaxConnectionsPerDestination() @@ -935,6 +963,29 @@ public class HttpClient extends ContainerLifeCycle } /** + * @return whether {@code connect()} operations are performed in blocking mode + */ + public boolean isConnectBlocking() + { + return connectBlocking; + } + + /** + * <p>Whether {@code connect()} operations are performed in blocking mode.</p> + * <p>If {@code connect()} are performed in blocking mode, then {@link Socket#connect(SocketAddress, int)} + * will be used to connect to servers.</p> + * <p>Otherwise, {@link SocketChannel#connect(SocketAddress)} will be used in non-blocking mode, + * therefore registering for {@link SelectionKey#OP_CONNECT} and finishing the connect operation + * when the NIO system emits that event.</p> + * + * @param connectBlocking whether {@code connect()} operations are performed in blocking mode + */ + public void setConnectBlocking(boolean connectBlocking) + { + this.connectBlocking = connectBlocking; + } + + /** * @return the forward proxy configuration */ public ProxyConfiguration getProxyConfiguration() diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java index 1c6e7fe488..e36432aa9c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java @@ -56,6 +56,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.LeakDetector; +import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -84,13 +85,13 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest server.removeConnector(connector); LeakTrackingByteBufferPool serverBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged()); connector = new ServerConnector(server, connector.getExecutor(), connector.getScheduler(), - serverBufferPool , 1, Math.min(1, cores / 2), + serverBufferPool , 1, Math.min(1, cores / 2), AbstractConnectionFactory.getFactories(sslContextFactory, new HttpConnectionFactory())); server.addConnector(connector); server.start(); client.stop(); - + HttpClient newClient = new HttpClient(new HttpClientTransportOverHTTP() { @Override @@ -114,6 +115,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest } }, sslContextFactory); newClient.setExecutor(client.getExecutor()); + newClient.setSocketAddressResolver(new SocketAddressResolver.Sync()); client = newClient; LeakTrackingByteBufferPool clientBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged()); client.setByteBufferPool(clientBufferPool); @@ -144,11 +146,11 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), is(0L)); assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), is(0L)); assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), is(0L)); - + assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), is(0L)); assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), is(0L)); assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), is(0L)); - + assertThat("Connection Leaks", connectionLeaks.get(), is(0L)); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index e5ec74710b..813a0b43ae 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -173,6 +173,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); + client.setConnectBlocking(true); ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); Assert.assertNotNull(response); @@ -1135,16 +1136,16 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }; - + client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .send(listener); - + Response response = ex.exchange(null); Assert.assertEquals(200, response.getStatus()); Assert.assertArrayEquals(content, listener.getContent()); - + } @Test @@ -1381,7 +1382,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } - + @Test public void testCompleteNotInvokedUntilContentConsumed() throws Exception { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java index 7a43c95ede..e41ee143d3 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLEngine; import javax.servlet.ServletException; @@ -299,6 +300,127 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest } } + @Slow + @Test + public void testBlockingConnectTimeoutFailsRequest() throws Exception + { + testConnectTimeoutFailsRequest(true); + } + + @Slow + @Test + public void testNonBlockingConnectTimeoutFailsRequest() throws Exception + { + testConnectTimeoutFailsRequest(false); + } + + private void testConnectTimeoutFailsRequest(boolean blocking) throws Exception + { + String host = "10.255.255.1"; + int port = 80; + int connectTimeout = 1000; + assumeConnectTimeout(host, port, connectTimeout); + + start(new EmptyServerHandler()); + client.stop(); + client.setConnectTimeout(connectTimeout); + client.setConnectBlocking(blocking); + client.start(); + + final CountDownLatch latch = new CountDownLatch(1); + Request request = client.newRequest(host, port); + request.scheme(scheme) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + if (result.isFailed()) + latch.countDown(); + } + }); + + Assert.assertTrue(latch.await(2 * connectTimeout, TimeUnit.MILLISECONDS)); + Assert.assertNotNull(request.getAbortCause()); + } + + @Slow + @Test + public void testConnectTimeoutIsCancelledByShorterRequestTimeout() throws Exception + { + String host = "10.255.255.1"; + int port = 80; + int connectTimeout = 2000; + assumeConnectTimeout(host, port, connectTimeout); + + start(new EmptyServerHandler()); + client.stop(); + client.setConnectTimeout(connectTimeout); + client.start(); + + final AtomicInteger completes = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(2); + Request request = client.newRequest(host, port); + request.scheme(scheme) + .timeout(connectTimeout / 2, TimeUnit.MILLISECONDS) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + completes.incrementAndGet(); + latch.countDown(); + } + }); + + Assert.assertFalse(latch.await(2 * connectTimeout, TimeUnit.MILLISECONDS)); + Assert.assertEquals(1, completes.get()); + Assert.assertNotNull(request.getAbortCause()); + } + + @Test + public void retryAfterConnectTimeout() throws Exception + { + final String host = "10.255.255.1"; + final int port = 80; + int connectTimeout = 1000; + assumeConnectTimeout(host, port, connectTimeout); + + start(new EmptyServerHandler()); + client.stop(); + client.setConnectTimeout(connectTimeout); + client.start(); + + final CountDownLatch latch = new CountDownLatch(1); + Request request = client.newRequest(host, port); + request.scheme(scheme) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + if (result.isFailed()) + { + // Retry + client.newRequest(host, port) + .scheme(scheme) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + if (result.isFailed()) + latch.countDown(); + } + }); + } + } + }); + + Assert.assertTrue(latch.await(333 * connectTimeout, TimeUnit.MILLISECONDS)); + Assert.assertNotNull(request.getAbortCause()); + } + @Test public void testVeryShortTimeout() throws Exception { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java index 068025526b..8a487167fe 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java @@ -31,69 +31,10 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; /** - * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise}, - * in order to avoid blocking on DNS lookup. - * <p> - * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of - * the host name, and this may block for several seconds. - * This class creates the {@link InetSocketAddress} in a separate thread and provides the result - * through a {@link Promise}, with the possibility to specify a timeout for the operation. - * <p> - * Example usage: - * <pre> - * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler); - * resolver.resolve("www.google.com", 80, new Promise<SocketAddress>() - * { - * public void succeeded(SocketAddress result) - * { - * // The address was resolved - * } - * - * public void failed(Throwable failure) - * { - * // The address resolution failed - * } - * }); - * </pre> + * <p>Creates {@link SocketAddress} instances, returning them through a {@link Promise}.</p> */ -public class SocketAddressResolver +public interface SocketAddressResolver { - private static final Logger LOG = Log.getLogger(SocketAddressResolver.class); - - private final Executor executor; - private final Scheduler scheduler; - private final long timeout; - - /** - * Creates a new instance with the given executor (to perform DNS resolution in a separate thread), - * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds. - * - * @param executor the thread pool to use to perform DNS resolution in pooled threads - * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long - * @param timeout the timeout, in milliseconds, for the DNS resolution to complete - */ - public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout) - { - this.executor = executor; - this.scheduler = scheduler; - this.timeout = timeout; - } - - public Executor getExecutor() - { - return executor; - } - - public Scheduler getScheduler() - { - return scheduler; - } - - public long getTimeout() - { - return timeout; - } - /** * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise} * with the default timeout. @@ -101,76 +42,149 @@ public class SocketAddressResolver * @param host the host to resolve * @param port the port of the resulting socket address * @param promise the callback invoked when the resolution succeeds or fails - * @see #resolve(String, int, long, Promise) */ - public void resolve(String host, int port, Promise<SocketAddress> promise) + public void resolve(String host, int port, Promise<SocketAddress> promise); + + /** + * <p>Creates {@link SocketAddress} instances synchronously in the caller thread.</p> + */ + public static class Sync implements SocketAddressResolver { - resolve(host, port, timeout, promise); + @Override + public void resolve(String host, int port, Promise<SocketAddress> promise) + { + try + { + InetSocketAddress result = new InetSocketAddress(host, port); + if (result.isUnresolved()) + promise.failed(new UnresolvedAddressException()); + else + promise.succeeded(result); + } + catch (Throwable x) + { + promise.failed(x); + } + } } /** - * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise} - * with the given timeout. + * <p>Creates {@link SocketAddress} instances asynchronously in a different thread.</p> + * <p>{@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS + * resolution of the host name, and this may block for several seconds. + * This class creates the {@link InetSocketAddress} in a separate thread and provides the result + * through a {@link Promise}, with the possibility to specify a timeout for the operation.</p> + * <p>Example usage:</p> + * <pre> + * SocketAddressResolver resolver = new SocketAddressResolver.Async(executor, scheduler, timeout); + * resolver.resolve("www.google.com", 80, new Promise<SocketAddress>() + * { + * public void succeeded(SocketAddress result) + * { + * // The address was resolved + * } * - * @param host the host to resolve - * @param port the port of the resulting socket address - * @param timeout the timeout, in milliseconds, for the DNS resolution to complete - * @param promise the callback invoked when the resolution succeeds or fails + * public void failed(Throwable failure) + * { + * // The address resolution failed + * } + * }); + * </pre> */ - protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise) + public static class Async implements SocketAddressResolver { - executor.execute(new Runnable() + private static final Logger LOG = Log.getLogger(SocketAddressResolver.class); + + private final Executor executor; + private final Scheduler scheduler; + private final long timeout; + + /** + * Creates a new instance with the given executor (to perform DNS resolution in a separate thread), + * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds. + * + * @param executor the thread pool to use to perform DNS resolution in pooled threads + * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long + * @param timeout the timeout, in milliseconds, for the DNS resolution to complete + */ + public Async(Executor executor, Scheduler scheduler, long timeout) + { + this.executor = executor; + this.scheduler = scheduler; + this.timeout = timeout; + } + + public Executor getExecutor() + { + return executor; + } + + public Scheduler getScheduler() + { + return scheduler; + } + + public long getTimeout() { - @Override - public void run() + return timeout; + } + + @Override + public void resolve(final String host, final int port, final Promise<SocketAddress> promise) + { + executor.execute(new Runnable() { - Scheduler.Task task = null; - final AtomicBoolean complete = new AtomicBoolean(); - if (timeout > 0) + @Override + public void run() { - final Thread thread = Thread.currentThread(); - task = scheduler.schedule(new Runnable() + Scheduler.Task task = null; + final AtomicBoolean complete = new AtomicBoolean(); + if (timeout > 0) { - @Override - public void run() + final Thread thread = Thread.currentThread(); + task = scheduler.schedule(new Runnable() { - if (complete.compareAndSet(false, true)) + @Override + public void run() { - promise.failed(new TimeoutException()); - thread.interrupt(); + if (complete.compareAndSet(false, true)) + { + promise.failed(new TimeoutException()); + thread.interrupt(); + } } - } - }, timeout, TimeUnit.MILLISECONDS); - } + }, timeout, TimeUnit.MILLISECONDS); + } - try - { - long start = System.nanoTime(); - InetSocketAddress result = new InetSocketAddress(host, port); - long elapsed = System.nanoTime() - start; - if (LOG.isDebugEnabled()) - LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed)); - if (complete.compareAndSet(false, true)) + try { - if (result.isUnresolved()) - promise.failed(new UnresolvedAddressException()); - else - promise.succeeded(result); + long start = System.nanoTime(); + InetSocketAddress result = new InetSocketAddress(host, port); + long elapsed = System.nanoTime() - start; + if (LOG.isDebugEnabled()) + LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed)); + if (complete.compareAndSet(false, true)) + { + if (result.isUnresolved()) + promise.failed(new UnresolvedAddressException()); + else + promise.succeeded(result); + } + } + catch (Throwable x) + { + if (complete.compareAndSet(false, true)) + promise.failed(x); + } + finally + { + if (task != null) + task.cancel(); + // Reset the interrupted status before releasing the thread to the pool + Thread.interrupted(); } } - catch (Throwable x) - { - if (complete.compareAndSet(false, true)) - promise.failed(x); - } - finally - { - if (task != null) - task.cancel(); - // Reset the interrupted status before releasing the thread to the pool - Thread.interrupted(); - } - } - }); + }); + } } } |