diff --git a/VERSION.txt b/VERSION.txt index 0b0e807228c..5093255ca7f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -171,6 +171,16 @@ jetty-9.4.7.v20170914 - 14 September 2017 + 475546 ClosedChannelException when connection to HTTPS over HTTP proxy with CONNECT +jetty-9.2.23.v20171218 - 18 December 2017 + + 1556 Remove a timing channel in Password matching + + 1685 Update ALPN support for Java 8u141 + + 1702 Update ALPN support for Java 8u144 + + 1914 HttpClient fails to parse Content-Type response header with RFC 2045 + charset="utf-8" syntax + + 2065 Backport #347 to Jetty 9.2.x + + 475546 ClosedChannelException when connecting to HTTPS over HTTP proxy with + CONNECT + jetty-9.3.22.v20171030 - 30 October 2017 + 1213 Upgrade to ASM Version 6.0_ALPHA for JDK9 + 1692 Annotation scanning should ignore `module-info.class` files diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 84fef88763f..d0b4a3d12a3 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -156,11 +156,6 @@ cdi-core 9.4.9-SNAPSHOT - - org.eclipse.jetty.cdi - cdi-full-servlet - 9.4.9-SNAPSHOT - org.eclipse.jetty.cdi cdi-servlet @@ -206,6 +201,13 @@ org.eclipse.jetty jetty-home 9.4.9-SNAPSHOT + zip + + + org.eclipse.jetty + jetty-home + 9.4.9-SNAPSHOT + tar.gz org.eclipse.jetty diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index 3fe68cc340d..5531f289740 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -27,6 +27,7 @@ import java.util.regex.Pattern; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -86,7 +87,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler { HttpRequest request = (HttpRequest)result.getRequest(); ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding()); - if (result.isFailed()) + if (result.getResponseFailure() != null) { if (LOG.isDebugEnabled()) LOG.debug("Authentication challenge failed {}", result.getFailure()); @@ -98,7 +99,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler HttpConversation conversation = request.getConversation(); if (conversation.getAttribute(authenticationAttribute) != null) { - // We have already tried to authenticate, but we failed again + // We have already tried to authenticate, but we failed again. if (LOG.isDebugEnabled()) LOG.debug("Bad credentials for {}", request); forwardSuccessComplete(request, response); @@ -111,7 +112,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler { if (LOG.isDebugEnabled()) LOG.debug("Authentication challenge without {} header", header); - forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response)); + forwardFailureComplete(request, result.getRequestFailure(), response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response)); return; } @@ -138,9 +139,18 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler return; } + ContentProvider requestContent = request.getContent(); + if (requestContent != null && !requestContent.isReproducible()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Request content not reproducible for {}", request); + forwardSuccessComplete(request, response); + return; + } + try { - final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation); + Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation); if (LOG.isDebugEnabled()) LOG.debug("Authentication result {}", authnResult); if (authnResult == null) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index a2d978ef0d7..40ce6c768bd 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -95,6 +95,11 @@ public abstract class HttpReceiver return channel.getHttpDestination(); } + public boolean isFailed() + { + return responseState.get() == ResponseState.FAILURE; + } + /** * Method to be invoked when the response status code is available. *

diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index bdae1570819..22a50d88ee8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -86,6 +86,11 @@ public abstract class HttpSender implements AsyncContentProvider.Listener return channel.getHttpExchange(); } + public boolean isFailed() + { + return requestState.get() == RequestState.FAILURE; + } + @Override public void onContent() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java index 03754f72c9b..6419b11e8f4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java @@ -22,6 +22,7 @@ import java.io.Closeable; import java.nio.ByteBuffer; import java.util.Iterator; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.client.util.PathContentProvider; @@ -48,6 +49,22 @@ public interface ContentProvider extends Iterable */ long getLength(); + /** + *

Whether this ContentProvider can produce exactly the same content more + * than once.

+ *

Implementations should return {@code true} only if the content can be + * produced more than once, which means that invocations to {@link #iterator()} + * must return a new, independent, iterator instance over the content.

+ *

The {@link HttpClient} implementation may use this method in particular + * cases where it detects that it is safe to retry a request that failed.

+ * + * @return whether the content can be produced more than once + */ + default boolean isReproducible() + { + return false; + } + /** * An extension of {@link ContentProvider} that provides a content type string * to be used as a {@code Content-Type} HTTP header in requests. diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java index 68386758da9..2bc5233f8be 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java @@ -57,6 +57,12 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider return length; } + @Override + public boolean isReproducible() + { + return true; + } + @Override public Iterator iterator() { @@ -85,12 +91,6 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider throw new NoSuchElementException(); } } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } }; } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java index d2c080aa700..39f5fd5ad51 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java @@ -53,6 +53,12 @@ public class BytesContentProvider extends AbstractTypedContentProvider return length; } + @Override + public boolean isReproducible() + { + return true; + } + @Override public Iterator iterator() { @@ -78,12 +84,6 @@ public class BytesContentProvider extends AbstractTypedContentProvider throw new NoSuchElementException(); } } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } }; } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java index d1051d94136..00147a556df 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java @@ -85,6 +85,12 @@ public class PathContentProvider extends AbstractTypedContentProvider return fileSize; } + @Override + public boolean isReproducible() + { + return true; + } + public ByteBufferPool getByteBufferPool() { return bufferPool; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java index b591cd7481f..4eea728f923 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.After; @@ -96,6 +97,7 @@ public abstract class AbstractHttpClientServerTest clientThreads.setName("client"); client = new HttpClient(transport, sslContextFactory); client.setExecutor(clientThreads); + client.setSocketAddressResolver(new SocketAddressResolver.Sync()); client.start(); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 6e1a5ca074d..14d23267228 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -22,11 +22,14 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntFunction; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -34,6 +37,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -41,6 +45,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.DigestAuthentication; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; @@ -220,7 +225,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { baseRequest.setHandled(true); if (requests.incrementAndGet() == 1) - response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),request.getRequestURI(),null)); + response.sendRedirect(URIUtil.newURI(scheme, request.getServerName(), request.getServerPort(), request.getRequestURI(), null)); } }); @@ -259,7 +264,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { baseRequest.setHandled(true); if (request.getRequestURI().endsWith("/redirect")) - response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),"/secure",null)); + response.sendRedirect(URIUtil.newURI(scheme, request.getServerName(), request.getServerPort(), "/secure", null)); } }); @@ -424,6 +429,40 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest Assert.assertEquals(1, requests.get()); } + @Test + public void test_NonReproducibleContent() throws Exception + { + startBasic(new EmptyServerHandler()); + + AuthenticationStore authenticationStore = client.getAuthenticationStore(); + URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort()); + BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic"); + authenticationStore.addAuthentication(authentication); + + CountDownLatch resultLatch = new CountDownLatch(1); + byte[] data = new byte[]{'h', 'e', 'l', 'l', 'o'}; + DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data)) + { + @Override + public boolean isReproducible() + { + return false; + } + }; + Request request = client.newRequest(uri) + .path("/secure") + .content(content); + request.send(result -> + { + if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401) + resultLatch.countDown(); + }); + + content.close(); + + Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); + } + @Test public void test_RequestFailsAfterResponse() throws Exception { @@ -434,32 +473,111 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic"); authenticationStore.addAuthentication(authentication); - CountDownLatch successLatch = new CountDownLatch(1); + AtomicBoolean fail = new AtomicBoolean(true); + GeneratingContentProvider content = new GeneratingContentProvider(index -> + { + switch (index) + { + case 0: + return ByteBuffer.wrap(new byte[]{'h', 'e', 'l', 'l', 'o'}); + case 1: + return ByteBuffer.wrap(new byte[]{'w', 'o', 'r', 'l', 'd'}); + case 2: + if (fail.compareAndSet(true, false)) + { + // Wait for the 401 response to arrive + // to the authentication protocol handler. + sleep(1000); + // Trigger request failure. + throw new RuntimeException(); + } + else + { + return null; + } + default: + throw new IllegalStateException(); + } + }); CountDownLatch resultLatch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); - Request request = client.newRequest("localhost", connector.getLocalPort()) + client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .path("/secure") .content(content) - .onResponseSuccess(response -> successLatch.countDown()); - request.send(result -> - { - if (result.isFailed() && result.getResponseFailure() == null) - resultLatch.countDown(); - }); + .send(result -> + { + if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) + resultLatch.countDown(); + }); - // Send some content to make sure the request is dispatched on the server. - content.offer(ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8))); - - // Wait for the response to arrive to - // the authentication protocol handler. - Thread.sleep(1000); - - // Trigger request failure. - request.abort(new Exception()); - - // Verify that the response was successful, it's the request that failed. - Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } + + private void sleep(long time) + { + try + { + Thread.sleep(time); + } + catch (InterruptedException x) + { + throw new RuntimeException(x); + } + } + + private static class GeneratingContentProvider implements ContentProvider + { + private static final ByteBuffer DONE = ByteBuffer.allocate(0); + + private final IntFunction generator; + + private GeneratingContentProvider(IntFunction generator) + { + this.generator = generator; + } + + @Override + public long getLength() + { + return -1; + } + + @Override + public boolean isReproducible() + { + return true; + } + + @Override + public Iterator iterator() + { + return new Iterator() + { + private int index; + public ByteBuffer current; + + @Override + public boolean hasNext() + { + if (current == null) + { + current = generator.apply(index++); + if (current == null) + current = DONE; + } + return current != DONE; + } + + @Override + public ByteBuffer next() + { + ByteBuffer result = current; + current = null; + if (result == null) + throw new NoSuchElementException(); + return result; + } + }; + } + } } 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 639d9af7bac..851118c3c95 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 @@ -263,10 +263,10 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest return new SslConnection(byteBufferPool, executor, endPoint, engine) { @Override - protected boolean onReadTimeout() + protected boolean onReadTimeout(Throwable timeout) { sslIdle.set(true); - return super.onReadTimeout(); + return super.onReadTimeout(timeout); } }; } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java index 735d08b4299..db32212ee55 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java @@ -126,12 +126,12 @@ public class SslBytesServerTest extends SslBytesTest } @Override - protected boolean onReadTimeout() + protected boolean onReadTimeout(Throwable timeout) { final Runnable idleHook = SslBytesServerTest.this.idleHook; if (idleHook != null) idleHook.run(); - return super.onReadTimeout(); + return super.onReadTimeout(timeout); } }, connector, endPoint); } diff --git a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc index d89a77bf391..49afbe47204 100644 --- a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc +++ b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc @@ -17,7 +17,7 @@ [[jetty-websocket-api-send-message]] === Send Messages to Remote Endpoint -The most important feature of the Session is access to the link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html[`org.eclipse.jetty.websocket.api.RemoteEndpoint`]needed to send messages. +The most important feature of the Session is access to the link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html[`org.eclipse.jetty.websocket.api.RemoteEndpoint`] needed to send messages. With RemoteEndpoint you can choose to send TEXT or BINARY WebSocket messages, or the WebSocket PING and PONG control frames. diff --git a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc index abcee12d5f5..754e0044c0e 100644 --- a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc +++ b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc @@ -44,7 +44,7 @@ What was in the Upgrade Request and Response. UpgradeRequest req = session.getUpgradeRequest(); String channelName = req.getParameterMap().get("channelName"); -UpgradeRespons resp = session.getUpgradeResponse(); +UpgradeResponse resp = session.getUpgradeResponse(); String subprotocol = resp.getAcceptedSubProtocol(); ---- diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 267bf249e4d..551d6bcee6e 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -38,18 +38,17 @@ public class HttpChannelOverFCGI extends HttpChannel { private final HttpConnectionOverFCGI connection; private final Flusher flusher; - private final int request; private final HttpSenderOverFCGI sender; private final HttpReceiverOverFCGI receiver; private final FCGIIdleTimeout idle; + private int request; private HttpVersion version; - public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout) + public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, long idleTimeout) { super(connection.getHttpDestination()); this.connection = connection; this.flusher = flusher; - this.request = request; this.sender = new HttpSenderOverFCGI(this); this.receiver = new HttpReceiverOverFCGI(this); this.idle = new FCGIIdleTimeout(connection, idleTimeout); @@ -60,6 +59,11 @@ public class HttpChannelOverFCGI extends HttpChannel return request; } + void setRequest(int request) + { + this.request = request; + } + @Override protected HttpSender getHttpSender() { @@ -72,6 +76,11 @@ public class HttpChannelOverFCGI extends HttpChannel return receiver; } + public boolean isFailed() + { + return sender.isFailed() || receiver.isFailed(); + } + @Override public void send() { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 28358d37736..82a8de881b0 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -23,7 +23,9 @@ import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.util.LinkedList; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,7 +58,8 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class); private final LinkedList requests = new LinkedList<>(); - private final Map channels = new ConcurrentHashMap<>(); + private final Map activeChannels = new ConcurrentHashMap<>(); + private final Queue idleChannels = new ConcurrentLinkedQueue<>(); private final AtomicBoolean closed = new AtomicBoolean(); private final HttpDestination destination; private final Promise promise; @@ -184,7 +187,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { // Close explicitly only if we are idle, since the request may still // be in progress, otherwise close only if we can fail the responses. - if (channels.isEmpty()) + if (activeChannels.isEmpty()) close(); else failAndClose(new EOFException(String.valueOf(getEndPoint()))); @@ -204,8 +207,14 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec protected void release(HttpChannelOverFCGI channel) { - channels.remove(channel.getRequest()); - destination.release(this); + if (activeChannels.remove(channel.getRequest()) != null) + { + channel.setRequest(0); + // Recycle only non-failed channels. + if (!channel.isFailed()) + idleChannels.offer(channel); + destination.release(this); + } } @Override @@ -249,19 +258,20 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec protected void abort(Throwable failure) { - for (HttpChannelOverFCGI channel : channels.values()) + for (HttpChannelOverFCGI channel : activeChannels.values()) { HttpExchange exchange = channel.getHttpExchange(); if (exchange != null) exchange.getRequest().abort(failure); } - channels.clear(); + activeChannels.clear(); + idleChannels.clear(); } private void failAndClose(Throwable failure) { boolean result = false; - for (HttpChannelOverFCGI channel : channels.values()) + for (HttpChannelOverFCGI channel : activeChannels.values()) result |= channel.responseFailure(failure); if (result) close(failure); @@ -286,9 +296,18 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec } } - protected HttpChannelOverFCGI newHttpChannel(int id, Request request) + protected HttpChannelOverFCGI provideHttpChannel(int id, Request request) { - return new HttpChannelOverFCGI(this, getFlusher(), id, request.getIdleTimeout()); + HttpChannelOverFCGI channel = idleChannels.poll(); + if (channel == null) + channel = newHttpChannel(request); + channel.setRequest(id); + return channel; + } + + protected HttpChannelOverFCGI newHttpChannel(Request request) + { + return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout()); } @Override @@ -314,10 +333,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec Request request = exchange.getRequest(); normalizeRequest(request); - // FCGI may be multiplexed, so create one channel for each request. + // FCGI may be multiplexed, so one channel for each exchange. int id = acquireRequest(); - HttpChannelOverFCGI channel = newHttpChannel(id, request); - channels.put(id, channel); + HttpChannelOverFCGI channel = provideHttpChannel(id, request); + activeChannels.put(id, channel); return send(channel, exchange); } @@ -351,7 +370,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onBegin(int request, int code, String reason) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) channel.responseBegin(code, reason); else @@ -361,7 +380,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onHeader(int request, HttpField field) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) channel.responseHeader(field); else @@ -371,7 +390,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onHeaders(int request) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) channel.responseHeaders(); else @@ -385,7 +404,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { case STD_OUT: { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) { CompletableCallback callback = new CompletableCallback() @@ -431,7 +450,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onEnd(int request) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) { if (channel.responseSuccess()) @@ -446,7 +465,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onFailure(int request, Throwable failure) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = activeChannels.get(request); if (channel != null) { if (channel.responseFailure(failure)) diff --git a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java index 7959f8a5072..766920ed9c3 100644 --- a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java +++ b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java @@ -121,6 +121,11 @@ public class HazelcastSessionDataStoreFactory return onlyClient; } + /** + * + * @param onlyClient if true the session manager will only connect to an external Hazelcast instance + * and not use this JVM to start an Hazelcast instance + */ public void setOnlyClient( boolean onlyClient ) { this.onlyClient = onlyClient; diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 7959d55f049..df9c3afc80d 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -84,6 +84,7 @@ org.eclipse.jetty jetty-start ${project.version} + shaded jar true ** @@ -403,6 +404,7 @@ org.eclipse.jetty jetty-start ${project.version} + shaded
org.eclipse.jetty diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java index 129a38c2983..b2ed0cfe5f3 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java @@ -678,6 +678,9 @@ public class StreamResetTest extends AbstractTest stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS)); + // Give time to the server to process the reset and drain the flusher queue. + Thread.sleep(500); + HTTP2Session session = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).getBean(HTTP2Session.class); HTTP2Flusher flusher = session.getBean(HTTP2Flusher.class); Assert.assertEquals(0, flusher.getFrameQueueSize()); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java index 55572146c21..babbbf9fa84 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.LifeCycle; @@ -37,7 +38,7 @@ import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.ReservedThreadExecutor; import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill; -public class HTTP2Connection extends AbstractConnection +public class HTTP2Connection extends AbstractConnection implements WriteFlusher.Listener { protected static final Logger LOG = Log.getLogger(HTTP2Connection.class); @@ -176,6 +177,13 @@ public class HTTP2Connection extends AbstractConnection } } + @Override + public void onFlushed(long bytes) throws IOException + { + // TODO: add method to ISession ? + ((HTTP2Session)session).onFlushed(bytes); + } + protected class HTTP2Producer implements ExecutionStrategy.Producer { private final Callback fillableCallback = new FillableCallback(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java index b3bb0ccf97a..94452f24ccc 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -175,7 +176,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable { if (entry.generate(lease)) { - if (entry.dataRemaining() > 0) + if (entry.getDataBytesRemaining() > 0) entries.offer(entry); } else @@ -207,6 +208,31 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable return Action.SCHEDULED; } + void onFlushed(long bytes) throws IOException + { + // For the given flushed bytes, we want to only + // forward those that belong to data frame content. + for (Entry entry : actives) + { + int frameBytesLeft = entry.getFrameBytesRemaining(); + if (frameBytesLeft > 0) + { + int update = (int)Math.min(bytes, frameBytesLeft); + entry.onFrameBytesFlushed(update); + bytes -= update; + IStream stream = entry.stream; + if (stream != null && !entry.isControl()) + { + Object channel = stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); + if (channel instanceof WriteFlusher.Listener) + ((WriteFlusher.Listener)channel).onFlushed(update - Frame.HEADER_LENGTH); + } + if (bytes == 0) + break; + } + } + } + @Override public void succeeded() { @@ -234,13 +260,13 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable for (int i = index; i < actives.size(); ++i) { Entry entry = actives.get(i); - if (entry.dataRemaining() > 0) + if (entry.getDataBytesRemaining() > 0) append(entry); } for (int i = 0; i < index; ++i) { Entry entry = actives.get(i); - if (entry.dataRemaining() > 0) + if (entry.getDataBytesRemaining() > 0) append(entry); } stalled = null; @@ -333,7 +359,11 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable this.stream = stream; } - public int dataRemaining() + public abstract int getFrameBytesRemaining(); + + public abstract void onFrameBytesFlushed(int bytesFlushed); + + public int getDataBytesRemaining() { return 0; } @@ -387,6 +417,17 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable } } + private boolean isControl() + { + switch (frame.getType()) + { + case DATA: + return false; + default: + return true; + } + } + @Override public String toString() { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 32e7c136323..e8b1c317146 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -955,6 +955,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { } + void onFlushed(long bytes) throws IOException + { + flusher.onFlushed(bytes); + } + public void disconnect() { if (LOG.isDebugEnabled()) @@ -1132,15 +1137,28 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private class ControlEntry extends HTTP2Flusher.Entry { private int bytes; + private int frameBytes; private ControlEntry(Frame frame, IStream stream, Callback callback) { super(frame, stream, callback); } + @Override + public int getFrameBytesRemaining() + { + return frameBytes; + } + + @Override + public void onFrameBytesFlushed(int bytesFlushed) + { + frameBytes -= bytesFlushed; + } + protected boolean generate(ByteBufferPool.Lease lease) { - bytes = generator.control(lease, frame); + bytes = frameBytes = generator.control(lease, frame); if (LOG.isDebugEnabled()) LOG.debug("Generated {}", frame); prepare(); @@ -1238,7 +1256,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private class DataEntry extends HTTP2Flusher.Entry { private int bytes; - private int dataRemaining; + private int frameBytes; + private int dataBytes; private int dataWritten; private DataEntry(DataFrame frame, IStream stream, Callback callback) @@ -1249,35 +1268,47 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // of data frames that cannot be completely written due to // the flow control window exhausting, since in that case // we would have to count the padding only once. - dataRemaining = frame.remaining(); + dataBytes = frame.remaining(); } @Override - public int dataRemaining() + public int getFrameBytesRemaining() { - return dataRemaining; + return frameBytes; + } + + @Override + public void onFrameBytesFlushed(int bytesFlushed) + { + frameBytes -= bytesFlushed; + } + + @Override + public int getDataBytesRemaining() + { + return dataBytes; } protected boolean generate(ByteBufferPool.Lease lease) { - int dataRemaining = dataRemaining(); + int dataBytes = getDataBytesRemaining(); int sessionSendWindow = getSendWindow(); int streamSendWindow = stream.updateSendWindow(0); int window = Math.min(streamSendWindow, sessionSendWindow); - if (window <= 0 && dataRemaining > 0) + if (window <= 0 && dataBytes > 0) return false; - int length = Math.min(dataRemaining, window); + int length = Math.min(dataBytes, window); // Only one DATA frame is generated. - bytes = generator.data(lease, (DataFrame)frame, length); + bytes = frameBytes = generator.data(lease, (DataFrame)frame, length); int written = bytes - Frame.HEADER_LENGTH; if (LOG.isDebugEnabled()) - LOG.debug("Generated {}, length/window/data={}/{}/{}", frame, written, window, dataRemaining); + LOG.debug("Generated {}, length/window/data={}/{}/{}", frame, written, window, dataBytes); this.dataWritten = written; - this.dataRemaining -= written; + this.dataBytes -= written; flowControl.onDataSending(stream, written); @@ -1292,7 +1323,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // Do we have more to send ? DataFrame dataFrame = (DataFrame)frame; - if (dataRemaining() == 0) + if (getDataBytesRemaining() == 0) { // Only now we can update the close state // and eventually remove the stream. diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java index 18fa3c560e2..3214823054f 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java @@ -34,17 +34,15 @@ public class HttpChannelOverHTTP2 extends HttpChannel { private final HttpConnectionOverHTTP2 connection; private final Session session; - private final boolean push; private final HttpSenderOverHTTP2 sender; private final HttpReceiverOverHTTP2 receiver; private Stream stream; - public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session, boolean push) + public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session) { super(destination); this.connection = connection; this.session = session; - this.push = push; this.sender = new HttpSenderOverHTTP2(this); this.receiver = new HttpReceiverOverHTTP2(this); } @@ -86,6 +84,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel this.stream = stream; } + public boolean isFailed() + { + return sender.isFailed() || receiver.isFailed(); + } + @Override public void send() { @@ -103,10 +106,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel @Override public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure) { + Stream stream = getStream(); boolean aborted = super.abort(exchange, requestFailure, responseFailure); if (aborted) { - Stream stream = getStream(); if (stream != null) stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); } @@ -117,7 +120,6 @@ public class HttpChannelOverHTTP2 extends HttpChannel public void exchangeTerminated(HttpExchange exchange, Result result) { super.exchangeTerminated(exchange, result); - if (!push) - release(); + release(); } } diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java index 7815d9c946c..8bcc8f8a0f0 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java @@ -62,6 +62,11 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport }); } + public HTTP2Client getHTTP2Client() + { + return client; + } + @ManagedAttribute(value = "The number of selectors", readonly = true) public int getSelectors() { diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java index 05f8456185f..f2d41a37542 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java @@ -19,8 +19,10 @@ package org.eclipse.jetty.http2.client.http; import java.nio.channels.AsynchronousCloseException; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -29,6 +31,7 @@ import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.SendFailure; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http2.ErrorCode; @@ -38,7 +41,8 @@ import org.eclipse.jetty.util.thread.Sweeper; public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.Sweepable { - private final Set channels = ConcurrentHashMap.newKeySet(); + private final Set activeChannels = ConcurrentHashMap.newKeySet(); + private final Queue idleChannels = new ConcurrentLinkedQueue<>(); private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicInteger sweeps = new AtomicInteger(); private final Session session; @@ -57,25 +61,41 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S @Override protected SendFailure send(HttpExchange exchange) { - exchange.getRequest().version(HttpVersion.HTTP_2); - normalizeRequest(exchange.getRequest()); + HttpRequest request = exchange.getRequest(); + request.version(HttpVersion.HTTP_2); + normalizeRequest(request); - // One connection maps to N channels, so for each exchange we create a new channel. - HttpChannel channel = newHttpChannel(false); - channels.add(channel); + // One connection maps to N channels, so one channel for each exchange. + HttpChannelOverHTTP2 channel = provideHttpChannel(); + activeChannels.add(channel); return send(channel, exchange); } - protected HttpChannelOverHTTP2 newHttpChannel(boolean push) + protected HttpChannelOverHTTP2 provideHttpChannel() { - return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push); + HttpChannelOverHTTP2 channel = idleChannels.poll(); + if (channel == null) + channel = newHttpChannel(); + return channel; } - protected void release(HttpChannel channel) + protected HttpChannelOverHTTP2 newHttpChannel() { - channels.remove(channel); - getHttpDestination().release(this); + return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession()); + } + + protected void release(HttpChannelOverHTTP2 channel) + { + // Only non-push channels are released. + if (activeChannels.remove(channel)) + { + channel.setStream(null); + // Recycle only non-failed channels. + if (!channel.isFailed()) + idleChannels.offer(channel); + getHttpDestination().release(this); + } } @Override @@ -113,13 +133,14 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S private void abort(Throwable failure) { - for (HttpChannel channel : channels) + for (HttpChannel channel : activeChannels) { HttpExchange exchange = channel.getHttpExchange(); if (exchange != null) exchange.getRequest().abort(failure); } - channels.clear(); + activeChannels.clear(); + idleChannels.clear(); } @Override diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java index 70913728500..dbf7c5a34cd 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java @@ -64,6 +64,13 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen return (HttpChannelOverHTTP2)super.getHttpChannel(); } + @Override + protected void reset() + { + super.reset(); + contentNotifier.reset(); + } + @Override public void onHeaders(Stream stream, HeadersFrame frame) { @@ -114,6 +121,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen HttpRequest request = exchange.getRequest(); MetaData.Request metaData = (MetaData.Request)frame.getMetaData(); HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString()); + // TODO: copy PUSH_PROMISE headers into pushRequest. BiFunction pushListener = request.getPushListener(); if (pushListener != null) @@ -121,7 +129,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen Response.CompleteListener listener = pushListener.apply(request, pushRequest); if (listener != null) { - HttpChannelOverHTTP2 pushChannel = getHttpChannel().getHttpConnection().newHttpChannel(true); + HttpChannelOverHTTP2 pushChannel = getHttpChannel().getHttpConnection().provideHttpChannel(); List listeners = Collections.singletonList(listener); HttpExchange pushExchange = new HttpExchange(getHttpDestination(), pushRequest, listeners); pushChannel.associate(pushExchange); @@ -187,16 +195,16 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen private final Queue queue = new ArrayDeque<>(); private DataInfo dataInfo; - private boolean offer(DataInfo dataInfo) + private void offer(DataInfo dataInfo) { synchronized (this) { - return queue.offer(dataInfo); + queue.offer(dataInfo); } } @Override - protected Action process() throws Exception + protected Action process() { DataInfo dataInfo; synchronized (this) diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java index bf9f2e7e438..7aff8056f86 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java @@ -247,16 +247,19 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest return new HttpConnectionOverHTTP2(destination, session) { @Override - protected HttpChannelOverHTTP2 newHttpChannel(boolean push) + protected HttpChannelOverHTTP2 newHttpChannel() { - return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push) + return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession()) { @Override public void setStream(Stream stream) { super.setStream(stream); - streamRef.set(stream); - streamLatch.countDown(); + if (stream != null) + { + streamRef.set(stream); + streamLatch.countDown(); + } } }; } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index 1a8168c53b5..4d8c34d1565 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; @@ -48,7 +49,7 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable +public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, WriteFlusher.Listener { private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class); private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION); @@ -85,6 +86,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable return getStream().getIdleTimeout(); } + @Override + public void onFlushed(long bytes) throws IOException + { + getResponse().getHttpOutput().onFlushed(bytes); + } + public Runnable onRequest(HeadersFrame frame) { try @@ -308,16 +315,19 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable public boolean onStreamTimeout(Throwable failure, Consumer consumer) { - boolean result = false; - if (isRequestIdle()) - { + boolean delayed = _delayedUntilContent; + _delayedUntilContent = false; + + boolean result = isRequestIdle(); + if (result) consumeInput(); - result = true; - } getHttpTransport().onStreamTimeout(failure); - if (getRequest().getHttpInput().onIdleTimeout(failure)) + if (getRequest().getHttpInput().onIdleTimeout(failure) || delayed) + { consumer.accept(this::handleWithContext); + result = false; + } return result; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index 7cbadbfb6ee..5dd0ff980e1 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -167,7 +167,7 @@ public abstract class AbstractConnection implements Connection { boolean close = true; if (cause instanceof TimeoutException) - close = onReadTimeout(); + close = onReadTimeout(cause); if (close) { if (_endPoint.isOutputShutdown()) @@ -183,9 +183,11 @@ public abstract class AbstractConnection implements Connection /** *

Callback method invoked when the endpoint failed to be ready to be read after a timeout

+ * + * @param timeout the cause of the read timeout * @return true to signal that the endpoint must be closed, false to keep the endpoint open */ - protected boolean onReadTimeout() + protected boolean onReadTimeout(Throwable timeout) { return true; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index 7732d38bd4a..d8dc1679f06 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -26,13 +26,15 @@ import java.nio.channels.CancelledKeyException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.Iterator; import java.util.List; -import java.util.Queue; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -64,7 +66,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private final Locker _locker = new Locker(); private boolean _selecting = false; - private final Queue _actions = new ArrayDeque<>(); + private final Deque _actions = new ArrayDeque<>(); private final SelectorManager _selectorManager; private final int _id; private final ExecutionStrategy _strategy; @@ -244,20 +246,31 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable @Override public void dump(Appendable out, String indent) throws IOException { - super.dump(out, indent); Selector selector = _selector; + List keys = null; + List actions = null; if (selector != null && selector.isOpen()) { - List actions; + DumpKeys dump = new DumpKeys(); + String actionsAt; try (Locker.Lock lock = _locker.lock()) { + actionsAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()); actions = new ArrayList<>(_actions); + _actions.addFirst(dump); + _selecting = false; } - List keys = new ArrayList<>(selector.keys().size()); - DumpKeys dumpKeys = new DumpKeys(keys); - submit(dumpKeys); - dumpKeys.await(5, TimeUnit.SECONDS); - dump(out, indent, Arrays.asList(new DumpableCollection("keys", keys), new DumpableCollection("actions", actions))); + selector.wakeup(); + keys = dump.get(5, TimeUnit.SECONDS); + String keysAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()); + if (keys==null) + keys = Collections.singletonList("No dump keys retrieved"); + dumpBeans(out, indent, Arrays.asList(new DumpableCollection("actions @ "+actionsAt, actions), + new DumpableCollection("keys @ "+keysAt, keys))); + } + else + { + dumpBeans(out, indent); } } @@ -495,47 +508,46 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private class DumpKeys extends Invocable.NonBlocking { - private final CountDownLatch latch = new CountDownLatch(1); - private final List _dumps; - - private DumpKeys(List dumps) - { - this._dumps = dumps; - } - + private CountDownLatch latch = new CountDownLatch(1); + private List keys; + @Override public void run() { Selector selector = _selector; if (selector != null && selector.isOpen()) - { - Set keys = selector.keys(); - _dumps.add(selector + " keys=" + keys.size()); - for (SelectionKey key : keys) + { + Set selector_keys = selector.keys(); + List list = new ArrayList<>(selector_keys.size()+1); + list.add(selector + " keys=" + selector_keys.size()); + for (SelectionKey key : selector_keys) { try { - _dumps.add(String.format("SelectionKey@%x{i=%d}->%s", key.hashCode(), key.interestOps(), key.attachment())); + list.add(String.format("SelectionKey@%x{i=%d}->%s", key.hashCode(), key.interestOps(), key.attachment())); } catch (Throwable x) { - _dumps.add(String.format("SelectionKey@%x[%s]->%s", key.hashCode(), x, key.attachment())); + list.add(String.format("SelectionKey@%x[%s]->%s", key.hashCode(), x, key.attachment())); } } + keys = list; } + latch.countDown(); } - public boolean await(long timeout, TimeUnit unit) + public List get(long timeout, TimeUnit unit) { try { - return latch.await(timeout, unit); + latch.await(timeout, unit); } catch (InterruptedException x) { - return false; + LOG.ignore(x); } + return keys; } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index 7f7c6070051..81a2cf32e2b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -35,7 +35,6 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable.InvocationType; - /** * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling * {@link EndPoint#flush(ByteBuffer...)} until all content is written. @@ -60,7 +59,7 @@ abstract public class WriteFlusher // fill the state machine __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING)); __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED)); - __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE)); + __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING, StateType.IDLE)); __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED)); __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE)); } @@ -104,29 +103,30 @@ abstract public class WriteFlusher /** * Tries to update the current state to the given new state. + * * @param previous the expected current state - * @param next the desired new state + * @param next the desired new state * @return the previous state or null if the state transition failed * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error) */ - private boolean updateState(State previous,State next) + private boolean updateState(State previous, State next) { - if (!isTransitionAllowed(previous,next)) + if (!isTransitionAllowed(previous, next)) throw new IllegalStateException(); boolean updated = _state.compareAndSet(previous, next); if (DEBUG) - LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next); + LOG.debug("update {}:{}{}{}", this, previous, updated ? "-->" : "!->", next); return updated; } private void fail(PendingState pending) { State current = _state.get(); - if (current.getType()==StateType.FAILED) + if (current.getType() == StateType.FAILED) { - FailedState failed=(FailedState)current; - if (updateState(failed,__IDLE)) + FailedState failed = (FailedState)current; + if (updateState(failed, __IDLE)) { pending.fail(failed.getCause()); return; @@ -138,9 +138,9 @@ abstract public class WriteFlusher private void ignoreFail() { State current = _state.get(); - while (current.getType()==StateType.FAILED) + while (current.getType() == StateType.FAILED) { - if (updateState(current,__IDLE)) + if (updateState(current, __IDLE)) return; current = _state.get(); } @@ -209,10 +209,11 @@ abstract public class WriteFlusher private static class FailedState extends State { private final Throwable _cause; + private FailedState(Throwable cause) { super(StateType.FAILED); - _cause=cause; + _cause = cause; } public Throwable getCause() @@ -257,7 +258,7 @@ abstract public class WriteFlusher protected boolean fail(Throwable cause) { - if (_callback!=null) + if (_callback != null) { _callback.failed(cause); return true; @@ -267,7 +268,7 @@ abstract public class WriteFlusher protected void complete() { - if (_callback!=null) + if (_callback != null) _callback.succeeded(); } @@ -286,8 +287,8 @@ abstract public class WriteFlusher { State s = _state.get(); return (s instanceof PendingState) - ?((PendingState)s).getCallbackInvocationType() - :Invocable.InvocationType.BLOCKING; + ? ((PendingState)s).getCallbackInvocationType() + : Invocable.InvocationType.BLOCKING; } /** @@ -300,13 +301,13 @@ abstract public class WriteFlusher * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition * fails it'll fail the callback. * - * If not all buffers can be written in one go it creates a new PendingState object to preserve the state + * If not all buffers can be written in one go it creates a new {@code PendingState} object to preserve the state * and then calls {@link #onIncompleteFlush()}. The remaining buffers will be written in {@link #completeWrite()}. * * If all buffers have been written it calls callback.complete(). * * @param callback the callback to call on either failed or complete - * @param buffers the buffers to flush to the endpoint + * @param buffers the buffers to flush to the endpoint * @throws WritePendingException if unable to write due to prior pending write */ public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException @@ -314,20 +315,20 @@ abstract public class WriteFlusher if (DEBUG) LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers)); - if (!updateState(__IDLE,__WRITING)) + if (!updateState(__IDLE, __WRITING)) throw new WritePendingException(); try { - buffers=flush(buffers); + buffers = flush(buffers); // if we are incomplete? - if (buffers!=null) + if (buffers != null) { if (DEBUG) LOG.debug("flushed incomplete"); - PendingState pending=new PendingState(buffers, callback); - if (updateState(__WRITING,pending)) + PendingState pending = new PendingState(buffers, callback); + if (updateState(__WRITING, pending)) onIncompleteFlush(); else fail(pending); @@ -335,18 +336,18 @@ abstract public class WriteFlusher } // If updateState didn't succeed, we don't care as our buffers have been written - if (!updateState(__WRITING,__IDLE)) + if (!updateState(__WRITING, __IDLE)) ignoreFail(); - if (callback!=null) + if (callback != null) callback.succeeded(); } catch (IOException e) { if (DEBUG) LOG.debug("write exception", e); - if (updateState(__WRITING,__IDLE)) + if (updateState(__WRITING, __IDLE)) { - if (callback!=null) + if (callback != null) callback.failed(e); } else @@ -354,7 +355,6 @@ abstract public class WriteFlusher } } - /** * Complete a write that has not completed and that called {@link #onIncompleteFlush()} to request a call to this * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress. @@ -370,27 +370,27 @@ abstract public class WriteFlusher State previous = _state.get(); - if (previous.getType()!=StateType.PENDING) + if (previous.getType() != StateType.PENDING) return; // failure already handled. PendingState pending = (PendingState)previous; - if (!updateState(pending,__COMPLETING)) + if (!updateState(pending, __COMPLETING)) return; // failure already handled. try { ByteBuffer[] buffers = pending.getBuffers(); - buffers=flush(buffers); + buffers = flush(buffers); // if we are incomplete? - if (buffers!=null) + if (buffers != null) { if (DEBUG) - LOG.debug("flushed incomplete {}",BufferUtil.toDetailString(buffers)); - if (buffers!=pending.getBuffers()) - pending=new PendingState(buffers, pending._callback); - if (updateState(__COMPLETING,pending)) + LOG.debug("flushed incomplete {}", BufferUtil.toDetailString(buffers)); + if (buffers != pending.getBuffers()) + pending = new PendingState(buffers, pending._callback); + if (updateState(__COMPLETING, pending)) onIncompleteFlush(); else fail(pending); @@ -398,7 +398,7 @@ abstract public class WriteFlusher } // If updateState didn't succeed, we don't care as our buffers have been written - if (!updateState(__COMPLETING,__IDLE)) + if (!updateState(__COMPLETING, __IDLE)) ignoreFail(); pending.complete(); } @@ -406,7 +406,7 @@ abstract public class WriteFlusher { if (DEBUG) LOG.debug("completeWrite exception", e); - if(updateState(__COMPLETING,__IDLE)) + if (updateState(__COMPLETING, __IDLE)) pending.fail(e); else fail(pending); @@ -422,59 +422,84 @@ abstract public class WriteFlusher */ protected ByteBuffer[] flush(ByteBuffer[] buffers) throws IOException { - boolean progress=true; - while(progress && buffers!=null) + boolean progress = true; + while (progress && buffers != null) { - int before=buffers.length==0?0:buffers[0].remaining(); - boolean flushed=_endPoint.flush(buffers); - int r=buffers.length==0?0:buffers[0].remaining(); + long before = remaining(buffers); + boolean flushed = _endPoint.flush(buffers); + long after = remaining(buffers); + long written = before - after; if (LOG.isDebugEnabled()) - LOG.debug("Flushed={} {}/{}+{} {}",flushed,before-r,before,buffers.length-1,this); + LOG.debug("Flushed={} written={} remaining={} {}", flushed, written, after, this); + + if (written > 0) + { + Connection connection = _endPoint.getConnection(); + if (connection instanceof Listener) + ((Listener)connection).onFlushed(written); + } if (flushed) return null; - progress=before!=r; + progress = written > 0; - int not_empty=0; - while(r==0) + int index = 0; + while (true) { - if (++not_empty==buffers.length) + if (index == buffers.length) { - buffers=null; - not_empty=0; + // All buffers consumed. + buffers = null; + index = 0; break; } - progress=true; - r=buffers[not_empty].remaining(); + else + { + int remaining = buffers[index].remaining(); + if (remaining > 0) + break; + ++index; + progress = true; + } } - - if (not_empty>0) - buffers=Arrays.copyOfRange(buffers,not_empty,buffers.length); + if (index > 0) + buffers = Arrays.copyOfRange(buffers, index, buffers.length); } if (LOG.isDebugEnabled()) - LOG.debug("!fully flushed {}",this); + LOG.debug("!fully flushed {}", this); // If buffers is null, then flush has returned false but has consumed all the data! // This is probably SSL being unable to flush the encrypted buffer, so return EMPTY_BUFFERS // and that will keep this WriteFlusher pending. - return buffers==null?EMPTY_BUFFERS:buffers; + return buffers == null ? EMPTY_BUFFERS : buffers; } - /* ------------------------------------------------------------ */ - /** Notify the flusher of a failure + private long remaining(ByteBuffer[] buffers) + { + if (buffers == null) + return 0; + long result = 0; + for (ByteBuffer buffer : buffers) + result += buffer.remaining(); + return result; + } + + /** + * Notify the flusher of a failure + * * @param cause The cause of the failure * @return true if the flusher passed the failure to a {@link Callback} instance */ public boolean onFail(Throwable cause) { // Keep trying to handle the failure until we get to IDLE or FAILED state - while(true) + while (true) { - State current=_state.get(); - switch(current.getType()) + State current = _state.get(); + switch (current.getType()) { case IDLE: case FAILED: @@ -487,7 +512,7 @@ abstract public class WriteFlusher LOG.debug("failed: " + this, cause); PendingState pending = (PendingState)current; - if (updateState(pending,__IDLE)) + if (updateState(pending, __IDLE)) return pending.fail(cause); break; @@ -495,7 +520,7 @@ abstract public class WriteFlusher if (DEBUG) LOG.debug("failed: " + this, cause); - if (updateState(current,new FailedState(cause))) + if (updateState(current, new FailedState(cause))) return false; break; } @@ -512,29 +537,9 @@ abstract public class WriteFlusher return _state.get().getType() == StateType.IDLE; } - public boolean isInProgress() - { - switch(_state.get().getType()) - { - case WRITING: - case PENDING: - case COMPLETING: - return true; - default: - return false; - } - } - - @Override - public String toString() - { - State s = _state.get(); - return String.format("WriteFlusher@%x{%s}->%s", hashCode(), s,s instanceof PendingState?((PendingState)s).getCallback():null); - } - public String toStateString() { - switch(_state.get().getType()) + switch (_state.get().getType()) { case WRITING: return "W"; @@ -550,4 +555,33 @@ abstract public class WriteFlusher return "?"; } } + + @Override + public String toString() + { + State s = _state.get(); + return String.format("WriteFlusher@%x{%s}->%s", hashCode(), s, s instanceof PendingState ? ((PendingState)s).getCallback() : null); + } + + /** + *

A listener of {@link WriteFlusher} events.

+ */ + public interface Listener + { + /** + *

Invoked when a {@link WriteFlusher} flushed bytes in a non-blocking way, + * as part of a - possibly larger - write.

+ *

This method may be invoked multiple times, for example when writing a large + * buffer: a first flush of bytes, then the connection became TCP congested, and + * a subsequent flush of bytes when the connection became writable again.

+ *

This method is never invoked concurrently, but may be invoked by different + * threads, so implementations may not rely on thread-local variables.

+ *

Implementations may throw an {@link IOException} to signal that the write + * should fail, for example if the implementation enforces a minimum data rate.

+ * + * @param bytes the number of bytes flushed + * @throws IOException if the write should fail + */ + void onFlushed(long bytes) throws IOException; + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 54dd7139615..c4ed4d420ae 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -399,6 +399,17 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque return !_delayedForContent; } + boolean onIdleTimeout(Throwable timeout) + { + if (_delayedForContent) + { + _delayedForContent = false; + getRequest().getHttpInput().onIdleTimeout(timeout); + execute(this); + return false; + } + return true; + } /** *

Attempts to perform a HTTP/1.1 upgrade.

diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java index e39da219bd6..37566401a15 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java @@ -66,6 +66,7 @@ public class HttpConfiguration private boolean _persistentConnectionsEnabled = true; private int _maxErrorDispatches = 10; private long _minRequestDataRate; + private long _minResponseDataRate; private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265; private boolean _notifyRemoteAsyncErrors = true; @@ -127,6 +128,7 @@ public class HttpConfiguration _persistentConnectionsEnabled=config._persistentConnectionsEnabled; _maxErrorDispatches=config._maxErrorDispatches; _minRequestDataRate=config._minRequestDataRate; + _minResponseDataRate=config._minResponseDataRate; _cookieCompliance=config._cookieCompliance; _notifyRemoteAsyncErrors=config._notifyRemoteAsyncErrors; } @@ -496,7 +498,29 @@ public class HttpConfiguration { _minRequestDataRate=bytesPerSecond; } - + + /** + * @return The minimum response data rate in bytes per second; or <=0 for no limit + */ + @ManagedAttribute("The minimum response content data rate in bytes per second") + public long getMinResponseDataRate() + { + return _minResponseDataRate; + } + + /** + *

Sets an minimum response content data rate.

+ *

The value is enforced only approximately - not precisely - due to the fact that + * for efficiency reasons buffer writes may be comprised of both response headers and + * response content.

+ * + * @param bytesPerSecond The minimum response data rate in bytes per second; or <=0 for no limit + */ + public void setMinResponseDataRate(long bytesPerSecond) + { + _minResponseDataRate = bytesPerSecond; + } + public CookieCompliance getCookieCompliance() { return _cookieCompliance; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index b7f51632dd7..3f0bb134c90 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; @@ -49,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger; /** *

A {@link Connection} that handles the HTTP protocol.

*/ -public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom +public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom, WriteFlusher.Listener { private static final Logger LOG = Log.getLogger(HttpConnection.class); public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE.asString()); @@ -195,6 +196,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return null; } + @Override + public void onFlushed(long bytes) throws IOException + { + // Unfortunately cannot distinguish between header and content + // bytes, and for content bytes whether they are chunked or not. + _channel.getResponse().getHttpOutput().onFlushed(bytes); + } + void releaseRequestBuffer() { if (_requestBuffer != null && !_requestBuffer.hasRemaining()) @@ -277,7 +286,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http LOG.debug("{} onFillable exit {} {}", this, _channel.getState(),BufferUtil.toDetailString(_requestBuffer)); } } - + /* ------------------------------------------------------------ */ /** Fill and parse data looking for content * @return true if an {@link RequestHandler} method was called and it returned true; @@ -477,6 +486,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } + @Override + protected boolean onReadTimeout(Throwable timeout) + { + return _channel.onIdleTimeout(timeout); + } + @Override protected void onFillInterestedFailed(Throwable cause) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 4a8a88eeb5e..05af7c68090 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -286,7 +286,7 @@ public class HttpInput extends ServletInputStream implements Runnable { long minimum_data = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); if (_contentArrived < minimum_data) - throw new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,String.format("Request data rate < %d B/s",minRequestDataRate)); + throw new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,String.format("Request content data rate < %d B/s",minRequestDataRate)); } } @@ -787,7 +787,8 @@ public class HttpInput extends ServletInputStream implements Runnable { synchronized (_inputQ) { - if (_waitingForContent && !isError()) + boolean neverDispatched = getHttpChannelState().isIdle(); + if ((_waitingForContent || neverDispatched) && !isError()) { x.addSuppressed(new Throwable("HttpInput idle timeout")); _state = new ErrorState(x); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 7c6691a7c01..ba07598ad7b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritePendingException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.RequestDispatcher; @@ -122,12 +123,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable private final HttpChannel _channel; private final SharedBlockingCallback _writeBlocker; private Interceptor _interceptor; - - /** - * Bytes written via the write API (excludes bytes written via sendContent). Used to autocommit once content length is written. - */ private long _written; - + private long _flushed; + private long _firstByteTimeStamp = -1; private ByteBuffer _aggregate; private int _bufferSize; private int _commitSize; @@ -231,6 +229,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable protected void write(ByteBuffer content, boolean complete, Callback callback) { + if (_firstByteTimeStamp == -1) + { + long minDataRate = getHttpChannel().getHttpConfiguration().getMinResponseDataRate(); + if (minDataRate > 0) + _firstByteTimeStamp = System.nanoTime(); + else + _firstByteTimeStamp = Long.MAX_VALUE; + } _interceptor.write(content, complete, callback); } @@ -908,6 +914,30 @@ public class HttpOutput extends ServletOutputStream implements Runnable _commitSize = size; } + /** + *

Invoked when bytes have been flushed to the network.

+ *

The number of flushed bytes may be different from the bytes written + * by the application if an {@link Interceptor} changed them, for example + * by compressing them.

+ * + * @param bytes the number of bytes flushed + * @throws IOException if the minimum data rate, when set, is not respected + * @see org.eclipse.jetty.io.WriteFlusher.Listener + */ + public void onFlushed(long bytes) throws IOException + { + if (_firstByteTimeStamp == -1 || _firstByteTimeStamp == Long.MAX_VALUE) + return; + long minDataRate = getHttpChannel().getHttpConfiguration().getMinResponseDataRate(); + _flushed += bytes; + long elapsed = System.nanoTime() - _firstByteTimeStamp; + long minFlushed = minDataRate * TimeUnit.NANOSECONDS.toMillis(elapsed) / TimeUnit.SECONDS.toMillis(1); + if (LOG.isDebugEnabled()) + LOG.debug("Flushed bytes min/actual {}/{}", minFlushed, _flushed); + if (_flushed < minFlushed) + throw new IOException(String.format("Response content data rate < %d B/s", minDataRate)); + } + public void recycle() { _interceptor = _channel; @@ -920,6 +950,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable _written = 0; _writeListener = null; _onError = null; + _firstByteTimeStamp = -1; + _flushed = 0; reopen(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index ef254920256..ce54bced74a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.URIUtil; @@ -370,7 +371,7 @@ public class Server extends HandlerWrapper implements Attributes String gitHash = Jetty.GIT_HASH; String timestamp = Jetty.BUILD_TIMESTAMP; - LOG.info("jetty-{}, build timestamp: {}, git hash: {}", getVersion(), timestamp, gitHash); + LOG.info("jetty-{}; built: {}; git: {}; jvm {}", getVersion(), timestamp, gitHash, JavaVersion.VERSION); if (!Jetty.STABLE) { LOG.warn("THIS IS NOT A STABLE RELEASE! DO NOT USE IN PRODUCTION!"); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java index 024e35c8074..e83875b4401 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java @@ -601,7 +601,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture long start = System.currentTimeMillis(); try { - IO.toString(is); + String response = IO.toString(is); + Assert.assertThat(response,Matchers.is("")); Assert.assertEquals(-1, is.read()); } catch(SSLException e) @@ -629,12 +630,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture long start = System.currentTimeMillis(); try { - IO.toString(is); + String response = IO.toString(is); + Assert.assertThat(response,Matchers.is("")); Assert.assertEquals(-1, is.read()); } catch(SSLException e) { - // e.printStackTrace(); + e.printStackTrace(); } catch(Exception e) { @@ -644,6 +646,88 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture } + @Test(timeout=60000) + public void testMaxIdleDelayedDispatch() throws Exception + { + configureServer(new EchoHandler()); + Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort()); + client.setSoTimeout(10000); + InputStream is=client.getInputStream(); + Assert.assertFalse(client.isClosed()); + + OutputStream os=client.getOutputStream(); + os.write(( + "GET / HTTP/1.1\r\n"+ + "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+ + "connection: keep-alive\r\n"+ + "Content-Length: 20\r\n"+ + "Content-Type: text/plain\r\n"+ + "Connection: close\r\n"+ + "\r\n").getBytes("utf-8")); + os.flush(); + + long start = System.currentTimeMillis(); + try + { + String response = IO.toString(is); + Assert.assertThat(response,Matchers.containsString("500")); + Assert.assertEquals(-1, is.read()); + } + catch(SSLException e) + { + e.printStackTrace(); + } + catch(Exception e) + { + e.printStackTrace(); + } + int duration = (int)(System.currentTimeMillis() - start); + Assert.assertThat(duration,Matchers.greaterThanOrEqualTo(MAX_IDLE_TIME)); + Assert.assertThat(duration,Matchers.lessThan(maximumTestRuntime)); + } + + @Test(timeout=60000) + public void testMaxIdleDispatch() throws Exception + { + configureServer(new EchoHandler()); + Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort()); + client.setSoTimeout(10000); + InputStream is=client.getInputStream(); + Assert.assertFalse(client.isClosed()); + + OutputStream os=client.getOutputStream(); + os.write(( + "GET / HTTP/1.1\r\n"+ + "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+ + "connection: keep-alive\r\n"+ + "Content-Length: 20\r\n"+ + "Content-Type: text/plain\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + "1234567890").getBytes("utf-8")); + os.flush(); + + long start = System.currentTimeMillis(); + try + { + String response = IO.toString(is); + Assert.assertThat(response,Matchers.containsString("500")); + Assert.assertEquals(-1, is.read()); + } + catch(SSLException e) + { + e.printStackTrace(); + } + catch(Exception e) + { + e.printStackTrace(); + } + int duration = (int)(System.currentTimeMillis() - start); + Assert.assertThat(duration,Matchers.greaterThanOrEqualTo(MAX_IDLE_TIME)); + Assert.assertThat(duration,Matchers.lessThan(maximumTestRuntime)); + } + + @Test(timeout=60000) public void testMaxIdleWithSlowRequest() throws Exception { diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index c38ef4e34c4..33d3b9d6e67 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -11,7 +11,6 @@ http://www.eclipse.org/jetty ${project.groupId}.start - start.jar @@ -32,9 +31,47 @@ org.eclipse.jetty.start.* + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + false + true + true + shaded + + + org.eclipse.jetty:jetty-util + + org/eclipse/jetty/util/JavaVersion* + org/eclipse/jetty/util/TopologicalSort* + + + + + + org.eclipse.jetty.util + org.eclipse.jetty.start.shaded.util + + + + + + + + org.eclipse.jetty + jetty-util + ${project.version} + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java b/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java deleted file mode 100644 index 5fe87ac85e0..00000000000 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java +++ /dev/null @@ -1,159 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.start; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Java Version Utility class. - *

Parses java versions to extract a consistent set of version parts

- */ -public class JavaVersion -{ - // Copy of code in jetty-util - - private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?"); - // Regexp from JEP 223 (http://openjdk.java.net/jeps/223). - private static final Pattern JDK9 = Pattern.compile("(\\d+)(\\.(\\d+))?(\\.(\\d+))?((-.+)?(\\+(\\d+)?(-.+)?)?)"); - - public static JavaVersion parse(String version) - { - if (version.startsWith("1.")) - return parsePreJDK9(version); - return parseJDK9(version); - } - - private static JavaVersion parsePreJDK9(String version) - { - Matcher matcher = PRE_JDK9.matcher(version); - if (!matcher.matches()) - throw new IllegalArgumentException("Invalid Java version " + version); - int major = 1; - int minor = Integer.parseInt(matcher.group(1)); - String microGroup = matcher.group(3); - int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup); - String updateGroup = matcher.group(5); - int update = updateGroup == null || updateGroup.isEmpty() ? 0 : Integer.parseInt(updateGroup); - String suffix = matcher.group(6); - return new JavaVersion(version, minor, major, minor, micro, update, suffix); - } - - private static JavaVersion parseJDK9(String version) - { - Matcher matcher = JDK9.matcher(version); - if (!matcher.matches()) - throw new IllegalArgumentException("Invalid Java version " + version); - int major = Integer.parseInt(matcher.group(1)); - String minorGroup = matcher.group(3); - int minor = minorGroup == null || minorGroup.isEmpty() ? 0 : Integer.parseInt(minorGroup); - String microGroup = matcher.group(5); - int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup); - String suffix = matcher.group(6); - return new JavaVersion(version, major, major, minor, micro, 0, suffix); - } - - private final String version; - private final int platform; - private final int major; - private final int minor; - private final int micro; - private final int update; - private final String suffix; - - private JavaVersion(String version, int platform, int major, int minor, int micro, int update, String suffix) - { - this.version = version; - this.platform = platform; - this.major = major; - this.minor = minor; - this.micro = micro; - this.update = update; - this.suffix = suffix; - } - - /** - * @return the string from which this JavaVersion was created - */ - public String getVersion() - { - return version; - } - - /** - *

Returns the Java Platform version, such as {@code 8} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.

- * - * @return the Java Platform version - */ - public int getPlatform() - { - return platform; - } - - /** - *

Returns the major number version, such as {@code 1} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.

- * - * @return the major number version - */ - public int getMajor() - { - return major; - } - - /** - *

Returns the minor number version, such as {@code 8} for JDK 1.8.0_92 and {@code 2} for JDK 9.2.4.

- * - * @return the minor number version - */ - public int getMinor() - { - return minor; - } - - /** - *

Returns the micro number version, such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.

- * - * @return the micro number version - */ - public int getMicro() - { - return micro; - } - - /** - *

Returns the update number version, such as {@code 92} for JDK 1.8.0_92 and {@code 0} for JDK 9.2.4.

- * - * @return the update number version - */ - public int getUpdate() - { - return update; - } - - /** - *

Returns the remaining string after the version numbers, such as {@code -internal} for - * JDK 1.8.0_92-internal and {@code -ea} for JDK 9-ea, or {@code +13} for JDK 9.2.4+13.

- * - * @return the remaining string after the version numbers - */ - public String getSuffix() - { - return suffix; - } -} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java index d0ab02faab1..aa3f1fecf7c 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java @@ -37,6 +37,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.jetty.util.TopologicalSort; + /** * Access for all modules declared, as well as what is enabled. */ diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 7b7ae07d627..a3819bd7507 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.config.ConfigSource; import org.eclipse.jetty.start.config.ConfigSources; import org.eclipse.jetty.start.config.DirConfigSource; +import org.eclipse.jetty.util.JavaVersion; /** * The Arguments required to start Jetty. diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/TopologicalSort.java b/jetty-start/src/main/java/org/eclipse/jetty/start/TopologicalSort.java deleted file mode 100644 index fec237c689c..00000000000 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/TopologicalSort.java +++ /dev/null @@ -1,204 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.start; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -/** - * Topological sort a list or array. - *

A Topological sort is used when you have a partial ordering expressed as - * dependencies between elements (also often represented as edges in a directed - * acyclic graph). A Topological sort should not be used when you have a total - * ordering expressed as a {@link Comparator} over the items. The algorithm has - * the additional characteristic that dependency sets are sorted by the original - * list order so that order is preserved when possible.

- *

- * The sort algorithm works by recursively visiting every item, once and - * only once. On each visit, the items dependencies are first visited and then the - * item is added to the sorted list. Thus the algorithm ensures that dependency - * items are always added before dependent items.

- * - * @param The type to be sorted. It must be able to be added to a {@link HashSet} - */ -public class TopologicalSort -{ - private final Map> _dependencies = new HashMap<>(); - - /** - * Add a dependency to be considered in the sort. - * @param dependent The dependent item will be sorted after all its dependencies - * @param dependency The dependency item, will be sorted before its dependent item - */ - public void addDependency(T dependent, T dependency) - { - Set set = _dependencies.get(dependent); - if (set==null) - { - set=new HashSet<>(); - _dependencies.put(dependent,set); - } - set.add(dependency); - } - - /** Sort the passed array according to dependencies previously set with - * {@link #addDependency(Object, Object)}. Where possible, ordering will be - * preserved if no dependency - * @param array The array to be sorted. - */ - public void sort(T[] array) - { - List sorted = new ArrayList<>(); - Set visited = new HashSet<>(); - Comparator comparator = new InitialOrderComparator<>(array); - - // Visit all items in the array - for (T t : array) - visit(t,visited,sorted,comparator); - - sorted.toArray(array); - } - - /** Sort the passed list according to dependencies previously set with - * {@link #addDependency(Object, Object)}. Where possible, ordering will be - * preserved if no dependency - * @param list The list to be sorted. - */ - public void sort(Collection list) - { - List sorted = new ArrayList<>(); - Set visited = new HashSet<>(); - Comparator comparator = new InitialOrderComparator<>(list); - - // Visit all items in the list - for (T t : list) - visit(t,visited,sorted,comparator); - - list.clear(); - list.addAll(sorted); - } - - /** Visit an item to be sorted. - * @param item The item to be visited - * @param visited The Set of items already visited - * @param sorted The list to sort items into - * @param comparator A comparator used to sort dependencies. - */ - private void visit(T item, Set visited, List sorted,Comparator comparator) - { - // If the item has not been visited - if(!visited.contains(item)) - { - // We are visiting it now, so add it to the visited set - visited.add(item); - - // Lookup the items dependencies - Set dependencies = _dependencies.get(item); - if (dependencies!=null) - { - // Sort the dependencies - SortedSet ordered_deps = new TreeSet<>(comparator); - ordered_deps.addAll(dependencies); - - // recursively visit each dependency - try - { - for (T d:ordered_deps) - visit(d,visited,sorted,comparator); - } - catch (CyclicException e) - { - throw new CyclicException(item,e); - } - } - - // Now that we have visited all our dependencies, they and their - // dependencies will have been added to the sorted list. So we can - // now add the current item and it will be after its dependencies - sorted.add(item); - } - else if (!sorted.contains(item)) - // If we have already visited an item, but it has not yet been put in the - // sorted list, then we must be in a cycle! - throw new CyclicException(item); - } - - - /** A comparator that is used to sort dependencies in the order they - * were in the original list. This ensures that dependencies are visited - * in the original order and no needless reordering takes place. - * @param - */ - private static class InitialOrderComparator implements Comparator - { - private final Map _indexes = new HashMap<>(); - InitialOrderComparator(T[] initial) - { - int i=0; - for (T t : initial) - _indexes.put(t,i++); - } - - InitialOrderComparator(Collection initial) - { - int i=0; - for (T t : initial) - _indexes.put(t,i++); - } - - @Override - public int compare(T o1, T o2) - { - Integer i1=_indexes.get(o1); - Integer i2=_indexes.get(o2); - if (i1==null || i2==null || i1.equals(o2)) - return 0; - if (i1=9 || version.length==1)?version[0]:version[1], + version[0], + version.length>1?version[1]:0, + version.length>2?version[2]:0, + Integer.parseInt(m.group("UPDATE")), + suffix(version,m.group("PRE"),m.group("OPT")) + ); + } + + if (m.group("PLUS")!=null) + { + return new JavaVersion( + v, + (version[0]>=9 || version.length==1)?version[0]:version[1], + version[0], + version.length>1?version[1]:0, + version.length>2?version[2]:0, + Integer.parseInt(m.group("BUILD")), + suffix(version,m.group("PRE"),m.group("OPT")) + ); + } + + return new JavaVersion( + v, + (version[0]>=9 || version.length==1)?version[0]:version[1], + version[0], + version.length>1?version[1]:0, + version.length>2?version[2]:0, + 0, + suffix(version,m.group("PRE"),m.group("OPT")) + ); + } - private static JavaVersion parsePreJDK9(String version) + private static String suffix(int[] version, String pre, String opt) { - Matcher matcher = PRE_JDK9.matcher(version); - if (!matcher.matches()) - throw new IllegalArgumentException("Invalid Java version " + version); - int major = 1; - int minor = Integer.parseInt(matcher.group(1)); - String microGroup = matcher.group(3); - int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup); - String updateGroup = matcher.group(5); - int update = updateGroup == null || updateGroup.isEmpty() ? 0 : Integer.parseInt(updateGroup); - String suffix = matcher.group(6); - return new JavaVersion(version, minor, major, minor, micro, update, suffix); + StringBuilder buf = new StringBuilder(); + for (int i=3;i3) + buf.append("."); + buf.append(version[i]); + } + + if (pre!=null) + { + if (buf.length()>0) + buf.append('-'); + buf.append(pre); + } + + if (opt!=null) + { + if (buf.length()>0) + buf.append('-'); + buf.append(opt); + } + + if (buf.length()==0) + return null; + + return buf.toString(); } - - private static JavaVersion parseJDK9(String version) - { - Matcher matcher = JDK9.matcher(version); - if (!matcher.matches()) - throw new IllegalArgumentException("Invalid Java version " + version); - int major = Integer.parseInt(matcher.group(1)); - String minorGroup = matcher.group(3); - int minor = minorGroup == null || minorGroup.isEmpty() ? 0 : Integer.parseInt(minorGroup); - String microGroup = matcher.group(5); - int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup); - String suffix = matcher.group(6); - return new JavaVersion(version, major, major, minor, micro, 0, suffix); - } - + private final String version; private final int platform; private final int major; @@ -136,7 +184,7 @@ public class JavaVersion } /** - *

Returns the micro number version, such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.

+ *

Returns the micro number version (aka security number), such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.

* * @return the micro number version */ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java index 98472b064b8..36cc61eff0d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java @@ -56,7 +56,10 @@ public class Jetty LOG.ignore( e ); } - GIT_HASH = __buildProperties.getProperty( "buildNumber", "unknown" ); + String git_hash = __buildProperties.getProperty( "buildNumber", "unknown" ); + if (git_hash.startsWith("${")) + git_hash = "unknown"; + GIT_HASH = git_hash; System.setProperty( "jetty.git.hash" , GIT_HASH ); BUILD_TIMESTAMP = formatTimestamp( __buildProperties.getProperty( "timestamp", "unknown" )); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java index 6c9b2fb43a1..e23d9e535d1 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java @@ -17,7 +17,6 @@ // package org.eclipse.jetty.util; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -29,9 +28,6 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.Dumpable; - /** * Topological sort a list or array. @@ -49,7 +45,7 @@ import org.eclipse.jetty.util.component.Dumpable; * * @param The type to be sorted. It must be able to be added to a {@link HashSet} */ -public class TopologicalSort implements Dumpable +public class TopologicalSort { private final Map> _dependencies = new HashMap<>(); @@ -185,7 +181,6 @@ public class TopologicalSort implements Dumpable return -1; return 1; } - } @Override @@ -193,19 +188,6 @@ public class TopologicalSort implements Dumpable { return "TopologicalSort "+_dependencies; } - - @Override - public String dump() - { - return ContainerLifeCycle.dump(this); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - out.append(String.format("TopologicalSort@%x%n",hashCode())); - ContainerLifeCycle.dump(out, indent,_dependencies.entrySet()); - } private static class CyclicException extends IllegalStateException { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/DumpableCollection.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/DumpableCollection.java index 32a3c0bb34f..56ca7bd8e65 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/DumpableCollection.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/DumpableCollection.java @@ -21,9 +21,6 @@ package org.eclipse.jetty.util.component; import java.io.IOException; import java.util.Collection; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.Dumpable; - public class DumpableCollection implements Dumpable { private final String _name; @@ -34,7 +31,7 @@ public class DumpableCollection implements Dumpable _name=name; _collection=collection; } - + @Override public String dump() { @@ -44,7 +41,7 @@ public class DumpableCollection implements Dumpable @Override public void dump(Appendable out, String indent) throws IOException { - out.append(_name).append("\n"); + out.append(_name).append(System.lineSeparator()); if (_collection!=null) ContainerLifeCycle.dump(out,indent,_collection); } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/JavaVersionTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/JavaVersionTest.java new file mode 100644 index 00000000000..c236fc286ec --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/JavaVersionTest.java @@ -0,0 +1,151 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +/** + * Tests for LazyList utility class. + */ +public class JavaVersionTest +{ + @Test + public void test9() + { + JavaVersion version = JavaVersion.parse("9.0.1"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(0)); + assertThat(version.getSuffix(),nullValue()); + } + + @Test + public void test9nano() + { + JavaVersion version = JavaVersion.parse("9.0.1.3"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(0)); + assertThat(version.getSuffix(),is("3")); + } + + @Test + public void test9build() + { + JavaVersion version = JavaVersion.parse("9.0.1+11"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(11)); + assertThat(version.getSuffix(),nullValue()); + } + + @Test + public void test9all() + { + JavaVersion version = JavaVersion.parse("9.0.1-ea+11-b01"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(11)); + assertThat(version.getSuffix(),is("ea-b01")); + } + + @Test + public void test9yuck() + { + JavaVersion version = JavaVersion.parse("9.0.1.2.3-ea+11-b01"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(11)); + assertThat(version.getSuffix(),is("2.3-ea-b01")); + } + + @Test + public void test10ea() + { + JavaVersion version = JavaVersion.parse("10-ea"); + assertThat(version.getPlatform(),is(10)); + assertThat(version.getMajor(),is(10)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(0)); + assertThat(version.getUpdate(),is(0)); + assertThat(version.getSuffix(),is("ea")); + } + + @Test + public void test8() + { + JavaVersion version = JavaVersion.parse("1.8.0_152"); + assertThat(version.getPlatform(),is(8)); + assertThat(version.getMajor(),is(1)); + assertThat(version.getMinor(),is(8)); + assertThat(version.getMicro(),is(0)); + assertThat(version.getUpdate(),is(152)); + assertThat(version.getSuffix(),nullValue()); + } + + @Test + public void test8ea() + { + JavaVersion version = JavaVersion.parse("1.8.1_03-ea"); + assertThat(version.getPlatform(),is(8)); + assertThat(version.getMajor(),is(1)); + assertThat(version.getMinor(),is(8)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(3)); + assertThat(version.getSuffix(),is("ea")); + } + + @Test + public void test3eaBuild() + { + JavaVersion version = JavaVersion.parse("1.3.1_05-ea-b01"); + assertThat(version.getPlatform(),is(3)); + assertThat(version.getMajor(),is(1)); + assertThat(version.getMinor(),is(3)); + assertThat(version.getMicro(),is(1)); + assertThat(version.getUpdate(),is(5)); + assertThat(version.getSuffix(),is("ea-b01")); + } + + @Test + public void testUbuntu() + { + JavaVersion version = JavaVersion.parse("9-Ubuntu+0-9b181-4"); + assertThat(version.getPlatform(),is(9)); + assertThat(version.getMajor(),is(9)); + assertThat(version.getMinor(),is(0)); + assertThat(version.getMicro(),is(0)); + assertThat(version.getUpdate(),is(0)); + assertThat(version.getSuffix(),is("Ubuntu-9b181-4")); + } +} diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java index c5a9823df24..ed357a3457c 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java @@ -18,14 +18,6 @@ package org.eclipse.jetty.websocket.client; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - import java.io.IOException; import java.lang.reflect.Field; import java.net.SocketTimeoutException; @@ -78,6 +70,14 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + public class ClientCloseTest { private static final Logger LOG = Log.getLogger(ClientCloseTest.class); @@ -147,7 +147,7 @@ public class ClientCloseTest @Override public void onWebSocketError(Throwable cause) { - LOG.warn("onWebSocketError",cause); + LOG.debug("onWebSocketError",cause); assertThat("Unique Error Event", error.compareAndSet(null, cause), is(true)); errorLatch.countDown(); } @@ -526,8 +526,7 @@ public class ClientCloseTest // client idle timeout triggers close event on client ws-endpoint assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true)); - assertThat("OnError", clientSocket.error.get(), instanceOf(SocketTimeoutException.class)); - assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Timeout on Read")); + assertThat("OnError", clientSocket.error.get(), instanceOf(TimeoutException.class)); } @Test(timeout = 5000L) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 21c7995b059..f614f06bdb9 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.common.io; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -453,7 +452,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp * @return true to signal that the endpoint must be closed, false to keep the endpoint open */ @Override - protected boolean onReadTimeout() + protected boolean onReadTimeout(Throwable timeout) { IOState state = getIOState(); ConnectionState cstate = state.getConnectionState(); @@ -471,7 +470,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp try { - notifyError(new SocketTimeoutException("Timeout on Read")); + notifyError(timeout); } finally { diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/EmptyServerHandler.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/EmptyServerHandler.java index eab1eedf66a..87f1315671c 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/EmptyServerHandler.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/EmptyServerHandler.java @@ -27,11 +27,16 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; -public class EmptyServerHandler extends AbstractHandler +public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + jettyRequest.setHandled(true); + service(target, jettyRequest, request, response); + } + + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - baseRequest.setHandled(true); } } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java index c6add504a19..7df005971b1 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.http.HttpChannelOverHTTP; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; @@ -146,9 +147,9 @@ public class HttpChannelAssociationTest extends AbstractTest return new HttpConnectionOverHTTP2(destination, session) { @Override - protected HttpChannelOverHTTP2 newHttpChannel(boolean push) + protected HttpChannelOverHTTP2 newHttpChannel() { - return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push) + return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession()) { @Override public boolean associate(HttpExchange exchange) @@ -171,9 +172,9 @@ public class HttpChannelAssociationTest extends AbstractTest return new HttpConnectionOverFCGI(endPoint, destination, promise, isMultiplexed()) { @Override - protected HttpChannelOverFCGI newHttpChannel(int id, org.eclipse.jetty.client.api.Request request) + protected HttpChannelOverFCGI newHttpChannel(Request request) { - return new HttpChannelOverFCGI(this, getFlusher(), id, request.getIdleTimeout()) + return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout()) { @Override public boolean associate(HttpExchange exchange) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index b7a020d59e2..409254e6711 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -37,7 +37,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.BytesContentProvider; @@ -497,9 +496,8 @@ public class HttpClientTest extends AbstractTest start(new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - super.handle(target, baseRequest, request, response); response.getWriter().write("Jetty"); } }); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java index f7c93c4837a..795a5930c59 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java @@ -43,14 +43,19 @@ import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http2.FlowControlStrategy; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.StacklessLogging; +import org.hamcrest.Matchers; import org.junit.Assert; +import org.junit.Assume; import org.junit.Test; public class ServerTimeoutsTest extends AbstractTest @@ -71,34 +76,114 @@ public class ServerTimeoutsTest extends AbstractTest } @Test - public void testDelayedDispatchRequestWithDelayedFirstContentIdleTimeoutFires() throws Exception + public void testBlockingReadWithDelayedFirstContentWithUndelayedDispatchIdleTimeoutFires() throws Exception { - httpConfig.setDelayDispatchUntilContent(true); - CountDownLatch handlerLatch = new CountDownLatch(1); - start(new AbstractHandler.ErrorDispatchHandler() + testBlockingReadWithDelayedFirstContentIdleTimeoutFires(false); + } + + @Test + public void testBlockingReadWithDelayedFirstContentWithDelayedDispatchIdleTimeoutFires() throws Exception + { + testBlockingReadWithDelayedFirstContentIdleTimeoutFires(true); + } + + @Test + public void testAsyncReadWithDelayedFirstContentWithUndelayedDispatchIdleTimeoutFires() throws Exception + { + testAsyncReadWithDelayedFirstContentIdleTimeoutFires(false); + } + + @Test + public void testAsyncReadWithDelayedFirstContentWithDelayedDispatchIdleTimeoutFires() throws Exception + { + testAsyncReadWithDelayedFirstContentIdleTimeoutFires(true); + } + + private void testBlockingReadWithDelayedFirstContentIdleTimeoutFires(boolean delayDispatch) throws Exception + { + testReadWithDelayedFirstContentIdleTimeoutFires(new EmptyServerHandler() { @Override - protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - baseRequest.setHandled(true); - handlerLatch.countDown(); + // The client did not send the content, + // idle timeout should result in IOException. + request.getInputStream().read(); } - }); - long idleTimeout = 2500; - setServerIdleTimeout(idleTimeout); + }, delayDispatch); + } - CountDownLatch resultLatch = new CountDownLatch(1); - client.POST(newURI()) - .content(new DeferredContentProvider()) - .send(result -> + private void testAsyncReadWithDelayedFirstContentIdleTimeoutFires(boolean delayDispatch) throws Exception + { + testReadWithDelayedFirstContentIdleTimeoutFires(new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + request.getInputStream().setReadListener(new ReadListener() { - if (result.isFailed()) - resultLatch.countDown(); + @Override + public void onDataAvailable() + { + } + + @Override + public void onAllDataRead() + { + } + + @Override + public void onError(Throwable t) + { + if (t instanceof TimeoutException) + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); + asyncContext.complete(); + } }); - // We did not send the content, the request was not - // dispatched, the server should have idle timed out. - Assert.assertFalse(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); + } + }, delayDispatch); + } + + private void testReadWithDelayedFirstContentIdleTimeoutFires(Handler handler, boolean delayDispatch) throws Exception + { + httpConfig.setDelayDispatchUntilContent(delayDispatch); + CountDownLatch handlerLatch = new CountDownLatch(1); + start(new AbstractHandler() + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + handler.handle(target, jettyRequest, request, response); + } + finally + { + handlerLatch.countDown(); + } + } + }); + long idleTimeout = 1000; + setServerIdleTimeout(idleTimeout); + + CountDownLatch resultLatch = new CountDownLatch(2); + DeferredContentProvider content = new DeferredContentProvider(); + client.POST(newURI()) + .content(content) + .onResponseSuccess(response -> + { + if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500) + resultLatch.countDown(); + content.close(); + }) + .send(result -> resultLatch.countDown()); + + // The client did not send the content, the request was + // dispatched, the server should have idle timed it out. + Assert.assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } @@ -736,6 +821,76 @@ public class ServerTimeoutsTest extends AbstractTest Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } + @Test + public void testBlockingWriteWithMinimumDataRateBelowLimit() throws Exception + { + // This test needs a large write to stall the server, and a slow reading client. + // In HTTP/1.1, when using the loopback interface, the buffers are so large that + // it would require a very large write (32 MiB) and a lot of time for this test + // to pass. On the first writes, the server fills in the large buffers with a lot + // of bytes (about 4 MiB), and so it would take a lot of time for the client to + // read those bytes and eventually produce a write rate that will make the server + // fail; and the write should be large enough to _not_ complete before the rate + // is below the minimum. + // In HTTP/2, we force the flow control window to be small, so that the server + // stalls almost immediately without having written many bytes, so that the test + // completes quickly. + Assume.assumeThat(transport, Matchers.isOneOf(Transport.H2, Transport.H2C)); + + int bytesPerSecond = 16 * 1024; + httpConfig.setMinResponseDataRate(bytesPerSecond); + CountDownLatch serverLatch = new CountDownLatch(1); + start(new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + ServletOutputStream output = response.getOutputStream(); + output.write(new byte[8 * 1024 * 1024]); + } + catch (IOException x) + { + serverLatch.countDown(); + } + } + }); + ((HttpClientTransportOverHTTP2)client.getTransport()).getHTTP2Client().setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); + + // Setup the client to read slower than the min data rate. + BlockingQueue objects = new LinkedBlockingQueue<>(); + CountDownLatch clientLatch = new CountDownLatch(1); + client.newRequest(newURI()) + .onResponseContentAsync((response, content, callback) -> + { + objects.offer(content.remaining()); + objects.offer(callback); + }) + .send(result -> + { + objects.offer(-1); + objects.offer(Callback.NOOP); + if (result.isFailed()) + clientLatch.countDown(); + }); + + long readRate = bytesPerSecond / 2; + while (true) + { + int bytes = (Integer)objects.poll(5, TimeUnit.SECONDS); + if (bytes < 0) + break; + long ms = bytes * 1000L / readRate; + Thread.sleep(ms); + Callback callback = (Callback)objects.poll(); + callback.succeeded(); + } + + Assert.assertTrue(serverLatch.await(15, TimeUnit.SECONDS)); + Assert.assertTrue(clientLatch.await(15, TimeUnit.SECONDS)); + } + private static class BlockingReadHandler extends AbstractHandler.ErrorDispatchHandler { private final CountDownLatch handlerLatch; diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index 2bd1287e31e..9ad03019564 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -49,6 +49,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.build.testOutputDirectory}/logging.properties + + + @@ -88,7 +97,6 @@ jetty-test-helper test - diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java index aabffb8dfda..204897ee78d 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java @@ -20,19 +20,29 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractClusteredLastAccessTimeTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; public class ClusteredLastAccessTimeTest extends AbstractClusteredLastAccessTimeTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } + } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java index e5a0be0a09a..466f430b647 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractClusteredOrphanedSessionTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; /** * ClusteredOrphanedSessionTest @@ -29,6 +30,7 @@ public class ClusteredOrphanedSessionTest extends AbstractClusteredOrphanedSessionTest { + HazelcastSessionDataStoreFactory factory; /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() @@ -36,9 +38,14 @@ public class ClusteredOrphanedSessionTest @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } - + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java index b6a310f22ff..361aac257bc 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractClusteredSessionMigrationTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; /** * ClusteredSessionMigrationTest @@ -28,6 +29,7 @@ import org.eclipse.jetty.server.session.SessionDataStoreFactory; public class ClusteredSessionMigrationTest extends AbstractClusteredSessionMigrationTest { + HazelcastSessionDataStoreFactory factory; /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() @@ -35,7 +37,14 @@ public class ClusteredSessionMigrationTest @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java index 0d626c33fa7..e4bd2f8868f 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractClusteredSessionScavengingTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; /** * ClusteredSessionScavengingTest @@ -29,13 +30,22 @@ public class ClusteredSessionScavengingTest extends AbstractClusteredSessionScavengingTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java index 5570d0946ba..68d9a51d468 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; /** * ModifyMaxInactiveIntervalTest @@ -29,14 +30,23 @@ public class ModifyMaxInactiveIntervalTest extends AbstractModifyMaxInactiveIntervalTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } + } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java index 8c024268b0f..e364bb085ad 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; import static org.junit.Assert.*; @@ -31,6 +32,8 @@ public class NonClusteredSessionScavengingTest extends AbstractNonClusteredSessionScavengingTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest#assertSession(java.lang.String, boolean) */ @@ -63,7 +66,14 @@ public class NonClusteredSessionScavengingTest @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java index ae911a0614b..2d51f25109d 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java @@ -21,20 +21,30 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractSessionExpiryTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; import org.junit.Test; public class SessionExpiryTest extends AbstractSessionExpiryTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } + } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java index c958fb6e6c5..434b11595cb 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session; import org.eclipse.jetty.server.session.AbstractSessionInvalidateCreateScavengeTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; /** * SessionInvalidateCreateScavengeTest @@ -29,13 +30,22 @@ public class SessionInvalidateCreateScavengeTest extends AbstractSessionInvalidateCreateScavengeTest { + HazelcastSessionDataStoreFactory factory; + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @Override public SessionDataStoreFactory createSessionDataStoreFactory() { - HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory = new HazelcastSessionDataStoreFactory(); + factory.setMapName( Long.toString( System.currentTimeMillis() ) ); return factory; } + + @After + public void shutdown() + { + factory.getHazelcastInstance().getMap( factory.getMapName() ).clear(); + } } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java index 2078882ee21..f2549d7b964 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java @@ -32,7 +32,7 @@ public class ClientLastAccessTimeTest extends AbstractClusteredLastAccessTimeTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java index f74d05746c9..effcafdecca 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java @@ -19,9 +19,15 @@ package org.eclipse.jetty.hazelcast.session.client; +import com.hazelcast.config.Config; +import com.hazelcast.config.MapConfig; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory; import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest; import org.eclipse.jetty.server.session.SessionDataStoreFactory; +import org.junit.After; +import org.junit.Before; /** * ModifyMaxInactiveIntervalTest @@ -30,6 +36,27 @@ public class ClientModifyMaxInactiveIntervalTest extends AbstractModifyMaxInactiveIntervalTest { + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); + + private HazelcastInstance hazelcastInstance; + + @Before + public void startHazelcast() + throws Exception + { + Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) // + .setInstanceName( "beer" ); + // start Hazelcast instance + hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config ); + } + + @After + public void stopHazelcast() + throws Exception + { + hazelcastInstance.shutdown(); + } + /** * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory() */ @@ -37,6 +64,8 @@ public class ClientModifyMaxInactiveIntervalTest public SessionDataStoreFactory createSessionDataStoreFactory() { HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory(); + factory.setOnlyClient( true ); + factory.setMapName( MAP_NAME ); return factory; } diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java index e9b00f20433..4abc6b1cedb 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java @@ -60,7 +60,7 @@ public class ClientNonClusteredSessionScavengingTest fail( e.getMessage() ); } } - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java index 00943a5f036..2a08debb557 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java @@ -33,7 +33,7 @@ public class ClientOrphanedSessionTest extends AbstractClusteredOrphanedSessionTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java index 4533b980d8a..a2fc8a24c37 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java @@ -33,7 +33,7 @@ public class ClientSessionExpiryTest extends AbstractSessionExpiryTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java index 1219907e689..cedd39abcaa 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java @@ -32,7 +32,7 @@ import org.junit.Before; public class ClientSessionInvalidateCreateScavengeTest extends AbstractSessionInvalidateCreateScavengeTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java index e5dc2cc1a9a..ba786895c02 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java @@ -35,7 +35,7 @@ import org.junit.Before; public class ClientSessionMigrationTest extends AbstractClusteredSessionMigrationTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java index 06a34569462..e7ca25a3877 100644 --- a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java @@ -33,7 +33,7 @@ public class ClientSessionScavengingTest extends AbstractClusteredSessionScavengingTest { - private static final String MAP_NAME = "jetty_foo_session"; + private static final String MAP_NAME = Long.toString( System.currentTimeMillis() ); private HazelcastInstance hazelcastInstance; diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/resources/jetty-logging.properties b/tests/test-sessions/test-hazelcast-sessions/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..07faa86dbce --- /dev/null +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/resources/jetty-logging.properties @@ -0,0 +1 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog \ No newline at end of file diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/resources/logging.properties b/tests/test-sessions/test-hazelcast-sessions/src/test/resources/logging.properties new file mode 100644 index 00000000000..256dbe49265 --- /dev/null +++ b/tests/test-sessions/test-hazelcast-sessions/src/test/resources/logging.properties @@ -0,0 +1,3 @@ +handlers=java.util.logging.ConsoleHandler +.level=INFO +com.hazelcast.level=SEVERE \ No newline at end of file diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java index 523867b63fb..cdf512c3ace 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; import org.junit.Test; /** @@ -182,8 +183,10 @@ public abstract class AbstractSessionExpiryTest extends AbstractTestBase server1.addContext(contextPath).addServlet(holder, servletMapping); HttpClient client = new HttpClient(); + try { + Log.getLogger(org.eclipse.jetty.util.thread.QueuedThreadPool.class).setDebugEnabled(true); server1.start(); int port1 = server1.getPort(); @@ -213,6 +216,7 @@ public abstract class AbstractSessionExpiryTest extends AbstractTestBase } finally { + Log.getLogger(org.eclipse.jetty.util.thread.QueuedThreadPool.class).setDebugEnabled(false); client.stop(); server1.stop(); }