diff --git a/VERSION.txt b/VERSION.txt index b2b13b7253b..8e06c966a89 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,5 +1,7 @@ jetty-10.0.0-SNAPSHOT +jetty-10.0.0.beta0 - 27 May 2020 + jetty-9.4.30.v20200611 - 11 June 2020 + 4776 Incorrect path matching for WebSocket using PathMappings + 4826 Upgrade to Apache Jasper 8.5.54 @@ -66,7 +68,7 @@ jetty-10.0.0.alpha1 - 26 November 2019 + 2709 current default for headerCacheSize is not large enough for many requests + 2815 hpack fields are opaque octets - + 3040 Allow RFC6265 Cookies to include optional SameSite attribute. + + 3040 Allow RFC6265 Cookies to include optional SameSite attribute + 3083 The ini-template for jetty.console-capture.dir does not match the default value + 3106 Websocket connection stats and request stats @@ -499,10 +501,10 @@ jetty-9.4.25.v20191220 - 20 December 2019 SslContextFactory usage + 4392 Suppress logging of QuietException in HttpChannelState.asyncError() + 4402 NPE in JettyRunWarExplodedMojo - + 4411 Jetty server spins on incomplete request due to delayed dispatch - until content - + 4415 GzipHandler invalid input zip size on large - (over 2,147,483,647 bytes) request body content + + 4411 Jetty server spins on incomplete request due to delayed dispatch until + content + + 4415 GzipHandler invalid input zip size on large (over 2,147,483,647 bytes) + request body content + 4421 HttpClient support for PROXY protocol + 4427 Retried HttpClient Requests can result in duplicates cookies diff --git a/apache-jsp/src/main/config/modules/apache-jsp.mod b/apache-jsp/src/main/config/modules/apache-jsp.mod index 62ea32b33c0..7db151afa38 100644 --- a/apache-jsp/src/main/config/modules/apache-jsp.mod +++ b/apache-jsp/src/main/config/modules/apache-jsp.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables use of the apache implementation of JSP diff --git a/apache-jstl/src/main/config/modules/apache-jstl.mod b/apache-jstl/src/main/config/modules/apache-jstl.mod index 55c89366ade..7e20f56af21 100644 --- a/apache-jstl/src/main/config/modules/apache-jstl.mod +++ b/apache-jstl/src/main/config/modules/apache-jstl.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables the apache version of JSTL diff --git a/build-resources/src/main/resources/jetty-checkstyle.xml b/build-resources/src/main/resources/jetty-checkstyle.xml index 16338ac3327..c95d421a03d 100644 --- a/build-resources/src/main/resources/jetty-checkstyle.xml +++ b/build-resources/src/main/resources/jetty-checkstyle.xml @@ -135,7 +135,7 @@ - + diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-11.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-11.mod index e0aa62d697c..36fdb782f97 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-11.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-11.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Provides support for ALPN based on JDK 9+ APIs. diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod index eebcdb72615..4dbcf25f583 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-12.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [depend] alpn-impl/alpn-11 diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod index eebcdb72615..4dbcf25f583 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [depend] alpn-impl/alpn-11 diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-14.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-14.mod index eebcdb72615..4dbcf25f583 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-14.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-14.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [depend] alpn-impl/alpn-11 diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-15.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-15.mod index eebcdb72615..4dbcf25f583 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-15.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-15.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [depend] alpn-impl/alpn-11 diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod index 281e978f84f..2c0dc5d1ccf 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables the ALPN (Application Layer Protocol Negotiation) TLS extension. diff --git a/jetty-annotations/src/main/config/modules/annotations.mod b/jetty-annotations/src/main/config/modules/annotations.mod index 4add32f3134..f7d4e034440 100644 --- a/jetty-annotations/src/main/config/modules/annotations.mod +++ b/jetty-annotations/src/main/config/modules/annotations.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables Annotation scanning for deployed webapplications. diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java index 1e6621ce8ec..fa4a46c9c53 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java @@ -941,7 +941,7 @@ public class AnnotationParser * @param name the class file name * @return whether the class file name is valid */ - private boolean isValidClassFileName(String name) + public boolean isValidClassFileName(String name) { //no name cannot be valid if (name == null || name.length() == 0) @@ -983,7 +983,7 @@ public class AnnotationParser * @param path the class file path * @return whether the class file path is valid */ - private boolean isValidClassFilePath(String path) + public boolean isValidClassFilePath(String path) { //no path is not valid if (path == null || path.length() == 0) diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java index 1daf3cd6366..4dd76377282 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java @@ -54,23 +54,28 @@ public class TestSecurityAnnotationConversions { } - @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = { - "tom", "dick", "harry" - })) + @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = + { + "tom", "dick", "harry" + })) public static class RolesServlet extends HttpServlet { } - @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = { - "tom", "dick", "harry" - }), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")}) + @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = + { + "tom", "dick", "harry" + }), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")}) public static class Method1Servlet extends HttpServlet { } - @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = { - "tom", "dick", "harry" - }), httpMethodConstraints = {@HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL)}) + @ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = + { + "tom", "dick", "harry" + }), httpMethodConstraints = { + @HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL) + }) public static class Method2Servlet extends HttpServlet { } diff --git a/jetty-client/src/main/config/modules/client.mod b/jetty-client/src/main/config/modules/client.mod index 4afc5bcb3d3..5687ff94fd3 100644 --- a/jetty-client/src/main/config/modules/client.mod +++ b/jetty-client/src/main/config/modules/client.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Adds the Jetty HTTP client to the server classpath. diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java index 1444cdc6e6f..8ffb9e703dc 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java @@ -92,10 +92,9 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable public Connection acquire(boolean create) { Connection connection = activate(); - if (connection == null) + if (connection == null && create) { - if (create) - tryCreate(destination.getQueuedRequestCount()); + tryCreate(destination.getQueuedRequestCount()); connection = activate(); } return connection; 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 9891d21509b..59322a3f44e 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 @@ -199,6 +199,7 @@ public abstract class HttpReceiver if (updateResponseState(ResponseState.TRANSIENT, ResponseState.BEGIN)) return true; + dispose(); terminateResponse(exchange); return false; } @@ -217,23 +218,17 @@ public abstract class HttpReceiver */ protected boolean responseHeader(HttpExchange exchange, HttpField field) { - out: while (true) { ResponseState current = responseState.get(); - switch (current) + if (current == ResponseState.BEGIN || current == ResponseState.HEADER) { - case BEGIN: - case HEADER: - { - if (updateResponseState(current, ResponseState.TRANSIENT)) - break out; + if (updateResponseState(current, ResponseState.TRANSIENT)) break; - } - default: - { - return false; - } + } + else + { + return false; } } @@ -267,6 +262,7 @@ public abstract class HttpReceiver if (updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADER)) return true; + dispose(); terminateResponse(exchange); return false; } @@ -334,7 +330,7 @@ public abstract class HttpReceiver { if (factory.getEncoding().equalsIgnoreCase(encoding)) { - decoder = new Decoder(response, factory.newContentDecoder()); + decoder = new Decoder(exchange, factory.newContentDecoder()); break; } } @@ -350,6 +346,7 @@ public abstract class HttpReceiver return hasDemand; } + dispose(); terminateResponse(exchange); return false; } @@ -393,39 +390,28 @@ public abstract class HttpReceiver { if (LOG.isDebugEnabled()) LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); - - ContentListeners listeners = this.contentListeners; - if (listeners != null) + if (contentListeners.isEmpty()) { - if (listeners.isEmpty()) - { - callback.succeeded(); - } - else - { - Decoder decoder = this.decoder; - if (decoder == null) - { - listeners.notifyContent(response, buffer, callback); - } - else - { - try - { - proceed = decoder.decode(buffer, callback); - } - catch (Throwable x) - { - callback.failed(x); - proceed = false; - } - } - } + callback.succeeded(); } else { - // May happen in case of concurrent abort. - proceed = false; + if (decoder == null) + { + contentListeners.notifyContent(response, buffer, callback); + } + else + { + try + { + proceed = decoder.decode(buffer, callback); + } + catch (Throwable x) + { + callback.failed(x); + proceed = false; + } + } } } @@ -444,6 +430,7 @@ public abstract class HttpReceiver } } + dispose(); terminateResponse(exchange); return false; } @@ -567,6 +554,7 @@ public abstract class HttpReceiver */ protected void dispose() { + assert responseState.get() != ResponseState.TRANSIENT; cleanup(); } @@ -598,7 +586,8 @@ public abstract class HttpReceiver this.failure = failure; - dispose(); + if (terminate) + dispose(); HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) @@ -776,14 +765,14 @@ public abstract class HttpReceiver */ private class Decoder implements Destroyable { - private final HttpResponse response; + private final HttpExchange exchange; private final ContentDecoder decoder; private ByteBuffer encoded; private Callback callback; - private Decoder(HttpResponse response, ContentDecoder decoder) + private Decoder(HttpExchange exchange, ContentDecoder decoder) { - this.response = response; + this.exchange = exchange; this.decoder = Objects.requireNonNull(decoder); } @@ -814,13 +803,13 @@ public abstract class HttpReceiver } ByteBuffer decoded = buffer; if (LOG.isDebugEnabled()) - LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(decoded)); + LOG.debug("Response content decoded ({}) {}{}{}", decoder, exchange, System.lineSeparator(), BufferUtil.toDetailString(decoded)); - contentListeners.notifyContent(response, decoded, Callback.from(() -> decoder.release(decoded), callback::failed)); + contentListeners.notifyContent(exchange.getResponse(), decoded, Callback.from(() -> decoder.release(decoded), callback::failed)); boolean hasDemand = hasDemandOrStall(); if (LOG.isDebugEnabled()) - LOG.debug("Response content decoded {}, hasDemand={}", response, hasDemand); + LOG.debug("Response content decoded {}, hasDemand={}", exchange, hasDemand); if (!hasDemand) return false; } @@ -829,9 +818,50 @@ public abstract class HttpReceiver private void resume() { if (LOG.isDebugEnabled()) - LOG.debug("Response content resuming decoding {}", response); - if (decode()) + LOG.debug("Response content resuming decoding {}", exchange); + + // The content and callback may be null + // if there is no initial content demand. + if (callback == null) + { receive(); + return; + } + + while (true) + { + ResponseState current = responseState.get(); + if (current == ResponseState.HEADERS || current == ResponseState.CONTENT) + { + if (updateResponseState(current, ResponseState.TRANSIENT)) + break; + } + else + { + callback.failed(new IllegalStateException("Invalid response state " + current)); + return; + } + } + + boolean decoded = false; + try + { + decoded = decode(); + } + catch (Throwable x) + { + callback.failed(x); + } + + if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT)) + { + if (decoded) + receive(); + return; + } + + dispose(); + terminateResponse(exchange); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index c6c329d2507..dc3a9219714 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -372,7 +372,7 @@ public class HttpRequest implements Request } @Override - public HttpFields.Mutable getHeaders() + public HttpFields getHeaders() { return headers; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java index 81f5f0815a2..3af05ee8e19 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java @@ -56,7 +56,7 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C public Connection acquire(boolean create) { Connection connection = activate(); - if (connection == null) + if (connection == null && create) { int queuedRequests = getHttpDestination().getQueuedRequestCount(); int maxMultiplex = getMaxMultiplex(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 5047b482338..efc29fe7789 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -100,7 +100,6 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res RetainableByteBuffer currentBuffer = networkBuffer; if (currentBuffer == null) throw new IllegalStateException(); - if (currentBuffer.hasRemaining()) throw new IllegalStateException(); @@ -121,9 +120,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res private void releaseNetworkBuffer() { if (networkBuffer == null) - throw new IllegalStateException(); - if (networkBuffer.hasRemaining()) - throw new IllegalStateException(); + return; networkBuffer.release(); if (LOG.isDebugEnabled()) LOG.debug("Released {}", networkBuffer); @@ -153,24 +150,27 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res while (true) { // Always parse even empty buffers to advance the parser. - boolean stopProcessing = parse(); + if (parse()) + { + // Return immediately, as this thread may be in a race + // with e.g. another thread demanding more content. + return; + } // Connection may be closed or upgraded in a parser callback. boolean upgraded = connection != endPoint.getConnection(); if (connection.isClosed() || upgraded) { if (LOG.isDebugEnabled()) - LOG.debug("{} {}", connection, upgraded ? "upgraded" : "closed"); + LOG.debug("{} {}", upgraded ? "Upgraded" : "Closed", connection); releaseNetworkBuffer(); return; } - if (stopProcessing) - return; - if (networkBuffer.getReferences() > 1) reacquireNetworkBuffer(); + // The networkBuffer may have been reacquired. int read = endPoint.fill(networkBuffer.getBuffer()); if (LOG.isDebugEnabled()) LOG.debug("Read {} bytes in {} from {}", read, networkBuffer, endPoint); @@ -196,8 +196,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res catch (Throwable x) { if (LOG.isDebugEnabled()) - LOG.debug("Unable to fill from endpoint {}", endPoint, x); - networkBuffer.clear(); + LOG.debug("Error processing {}", endPoint, x); releaseNetworkBuffer(); failAndClose(x); } @@ -213,14 +212,24 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res while (true) { boolean handle = parser.parseNext(networkBuffer.getBuffer()); + boolean failed = isFailed(); + if (LOG.isDebugEnabled()) + LOG.debug("Parse result={}, failed={}", handle, failed); + // When failed, it's safe to close the parser because there + // will be no races with other threads demanding more content. + if (failed) + parser.close(); + if (handle) + return !failed; + boolean complete = this.complete; this.complete = false; if (LOG.isDebugEnabled()) - LOG.debug("Parsed {}, remaining {} {}", handle, networkBuffer.remaining(), parser); - if (handle) - return true; + LOG.debug("Parse complete={}, remaining {} {}", complete, networkBuffer.remaining(), parser); + if (networkBuffer.isEmpty()) return false; + if (complete) { if (LOG.isDebugEnabled()) @@ -301,8 +310,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; + RetainableByteBuffer networkBuffer = this.networkBuffer; networkBuffer.retain(); - return !responseContent(exchange, buffer, Callback.from(networkBuffer::release, this::failAndClose)); + return !responseContent(exchange, buffer, Callback.from(networkBuffer::release, failure -> + { + networkBuffer.release(); + failAndClose(failure); + })); } @Override @@ -333,17 +347,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (status != HttpStatus.CONTINUE_100) complete = true; - boolean proceed = responseSuccess(exchange); - if (!proceed) - return true; - - if (status == HttpStatus.SWITCHING_PROTOCOLS_101) - return true; - - if (HttpMethod.CONNECT.is(exchange.getRequest().getMethod()) && status == HttpStatus.OK_200) - return true; - - return false; + return !responseSuccess(exchange); } @Override @@ -376,13 +380,6 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res parser.reset(); } - @Override - protected void dispose() - { - super.dispose(); - parser.close(); - } - private void failAndClose(Throwable failure) { if (responseFailure(failure)) 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 085ce79e211..b0c11e87e4e 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 @@ -20,6 +20,7 @@ package org.eclipse.jetty.client; import java.nio.file.Path; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; @@ -45,13 +46,13 @@ public abstract class AbstractHttpClientServerTest protected HttpClient client; protected ServerConnector connector; - public void start(final Scenario scenario, Handler handler) throws Exception + public void start(Scenario scenario, Handler handler) throws Exception { startServer(scenario, handler); startClient(scenario); } - protected void startServer(final Scenario scenario, Handler handler) throws Exception + protected void startServer(Scenario scenario, Handler handler) throws Exception { if (server == null) { @@ -66,23 +67,27 @@ public abstract class AbstractHttpClientServerTest server.start(); } - protected void startClient(final Scenario scenario) throws Exception + protected void startClient(Scenario scenario) throws Exception { startClient(scenario, null); } - protected void startClient(final Scenario scenario, Consumer config) throws Exception + protected void startClient(Scenario scenario, Consumer config) throws Exception + { + startClient(scenario, HttpClientTransportOverHTTP::new, config); + } + + protected void startClient(Scenario scenario, Function transport, Consumer config) throws Exception { ClientConnector clientConnector = new ClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(scenario.newClientSslContextFactory()); - HttpClientTransport transport = new HttpClientTransportOverHTTP(clientConnector); QueuedThreadPool executor = new QueuedThreadPool(); executor.setName("client"); clientConnector.setExecutor(executor); Scheduler scheduler = new ScheduledExecutorScheduler("client-scheduler", false); clientConnector.setScheduler(scheduler); - client = newHttpClient(transport); + client = newHttpClient(transport.apply(clientConnector)); client.setSocketAddressResolver(new SocketAddressResolver.Sync()); if (config != null) config.accept(client); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java index 8e11bfcc684..a2a9da58f15 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java @@ -18,21 +18,30 @@ package org.eclipse.jetty.client; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.LongConsumer; +import java.util.zip.GZIPOutputStream; +import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.http.HttpChannelOverHTTP; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.client.http.HttpReceiverOverHTTP; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -46,10 +55,10 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testSmallAsyncContent(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, 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 { ServletOutputStream output = response.getOutputStream(); output.write(65); @@ -58,30 +67,19 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest } }); - final AtomicInteger contentCount = new AtomicInteger(); - final AtomicReference callbackRef = new AtomicReference<>(); - final AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); - final CountDownLatch completeLatch = new CountDownLatch(1); + AtomicInteger contentCount = new AtomicInteger(); + AtomicReference callbackRef = new AtomicReference<>(); + AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); + CountDownLatch completeLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .onResponseContentAsync(new Response.AsyncContentListener() + .onResponseContentAsync((response, content, callback) -> { - @Override - public void onContent(Response response, ByteBuffer content, Callback callback) - { - contentCount.incrementAndGet(); - callbackRef.set(callback); - contentLatch.get().countDown(); - } + contentCount.incrementAndGet(); + callbackRef.set(callback); + contentLatch.get().countDown(); }) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - completeLatch.countDown(); - } - }); + .send(result -> completeLatch.countDown()); assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); Callback callback = callbackRef.get(); @@ -113,4 +111,294 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); assertEquals(2, contentCount.get()); } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testConcurrentAsyncContent(Scenario scenario) throws Exception + { + AtomicReference asyncContextRef = new AtomicReference<>(); + startServer(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + ServletOutputStream output = response.getOutputStream(); + output.write(new byte[1024]); + output.flush(); + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + asyncContextRef.set(asyncContext); + } + }); + AtomicReference demandRef = new AtomicReference<>(); + startClient(scenario, clientConnector -> new HttpClientTransportOverHTTP(clientConnector) + { + @Override + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException + { + return customize(new HttpConnectionOverHTTP(endPoint, context) + { + @Override + protected HttpChannelOverHTTP newHttpChannel() + { + return new HttpChannelOverHTTP(this) + { + @Override + protected HttpReceiverOverHTTP newHttpReceiver() + { + return new HttpReceiverOverHTTP(this) + { + @Override + public boolean content(ByteBuffer buffer) + { + try + { + boolean result = super.content(buffer); + // The content has been notified, but the listener has not demanded. + + // Simulate an asynchronous demand from otherThread. + // There is no further content, so otherThread will fill 0, + // set the fill interest, and release the network buffer. + CountDownLatch latch = new CountDownLatch(1); + Thread otherThread = new Thread(() -> + { + demandRef.get().accept(1); + latch.countDown(); + }); + otherThread.start(); + // Wait for otherThread to finish, then let this thread continue. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + return result; + } + catch (InterruptedException x) + { + throw new RuntimeException(x); + } + } + }; + } + }; + } + }, context); + } + }, null); + + CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .onResponseContentDemanded((response, demand, content, callback) -> + { + demandRef.set(demand); + // Don't demand and don't succeed the callback. + }) + .send(result -> + { + if (result.isSucceeded()) + latch.countDown(); + }); + + // Wait for the threads to finish their processing. + Thread.sleep(1000); + + // Complete the response. + asyncContextRef.get().complete(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testAsyncContentAbort(Scenario scenario) throws Exception + { + start(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.getOutputStream().write(new byte[1024]); + } + }); + + CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .onResponseContentDemanded((response, demand, content, callback) -> response.abort(new Throwable())) + .send(result -> + { + if (result.isFailed()) + latch.countDown(); + }); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testAsyncGzipContentAbortThenDemand(Scenario scenario) throws Exception + { + start(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setHeader("Content-Encoding", "gzip"); + GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream()); + gzip.write(new byte[1024]); + gzip.finish(); + } + }); + + CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .onResponseContentDemanded((response, demand, content, callback) -> + { + response.abort(new Throwable()); + demand.accept(1); + }) + .send(result -> + { + if (result.isFailed()) + latch.countDown(); + }); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testAsyncGzipContentDelayedDemand(Scenario scenario) throws Exception + { + start(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setHeader("Content-Encoding", "gzip"); + try (GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream())) + { + gzip.write(new byte[1024]); + } + } + }); + + AtomicReference demandRef = new AtomicReference<>(); + CountDownLatch headersLatch = new CountDownLatch(1); + CountDownLatch resultLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .onResponseContentDemanded(new Response.DemandedContentListener() + { + @Override + public void onBeforeContent(Response response, LongConsumer demand) + { + // Don't demand yet. + demandRef.set(demand); + headersLatch.countDown(); + } + + @Override + public void onContent(Response response, LongConsumer demand, ByteBuffer content, Callback callback) + { + demand.accept(1); + } + }) + .send(result -> + { + if (result.isSucceeded()) + resultLatch.countDown(); + }); + + assertTrue(headersLatch.await(5, TimeUnit.SECONDS)); + // Wait to make sure the demand is really delayed. + Thread.sleep(500); + demandRef.get().accept(1); + + assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testAsyncGzipContentAbortWhileDecodingWithDelayedDemand(Scenario scenario) throws Exception + { + // Use a large content so that the gzip decoding is done in multiple passes. + byte[] bytes = new byte[8 * 1024 * 1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) + { + gzip.write(bytes); + } + byte[] gzipBytes = baos.toByteArray(); + int half = gzipBytes.length / 2; + byte[] gzip1 = Arrays.copyOfRange(gzipBytes, 0, half); + byte[] gzip2 = Arrays.copyOfRange(gzipBytes, half, gzipBytes.length); + + AtomicReference asyncContextRef = new AtomicReference<>(); + start(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + asyncContextRef.set(asyncContext); + + response.setHeader("Content-Encoding", "gzip"); + ServletOutputStream output = response.getOutputStream(); + output.write(gzip1); + output.flush(); + } + }); + + AtomicReference demandRef = new AtomicReference<>(); + CountDownLatch firstChunkLatch = new CountDownLatch(1); + CountDownLatch secondChunkLatch = new CountDownLatch(1); + CountDownLatch resultLatch = new CountDownLatch(1); + AtomicInteger chunks = new AtomicInteger(); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .onResponseContentDemanded((response, demand, content, callback) -> + { + if (chunks.incrementAndGet() == 1) + { + try + { + // Don't demand, but make the server write the second chunk. + AsyncContext asyncContext = asyncContextRef.get(); + asyncContext.getResponse().getOutputStream().write(gzip2); + asyncContext.complete(); + demandRef.set(demand); + firstChunkLatch.countDown(); + } + catch (IOException x) + { + throw new RuntimeException(x); + } + } + else + { + response.abort(new Throwable()); + demandRef.set(demand); + secondChunkLatch.countDown(); + } + }) + .send(result -> + { + if (result.isFailed()) + resultLatch.countDown(); + }); + + assertTrue(firstChunkLatch.await(5, TimeUnit.SECONDS)); + // Wait to make sure the demand is really delayed. + Thread.sleep(500); + demandRef.get().accept(1); + + assertTrue(secondChunkLatch.await(5, TimeUnit.SECONDS)); + // Wait to make sure the demand is really delayed. + Thread.sleep(500); + demandRef.get().accept(1); + + assertTrue(resultLatch.await(555, TimeUnit.SECONDS)); + } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 0c516e4e24b..6cdd89ccbac 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -1582,11 +1582,6 @@ public class HttpClientTest extends AbstractHttpClientServerTest ContentResponse response = listener.get(5, TimeUnit.SECONDS); assertEquals(200, response.getStatus()); - // Because the tunnel was successful, this connection will be - // upgraded to an SslConnection, so it will not be fill interested. - // This test doesn't upgrade, so it needs to restore the fill interest. - ((AbstractConnection)connection).fillInterested(); - // Test that I can send another request on the same connection. request = client.newRequest(host, port); listener = new FutureResponseListener(request); diff --git a/jetty-deploy/src/main/config/modules/deploy.mod b/jetty-deploy/src/main/config/modules/deploy.mod index 6321ae50496..167005f349e 100644 --- a/jetty-deploy/src/main/config/modules/deploy.mod +++ b/jetty-deploy/src/main/config/modules/deploy.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables webapplication deployment from the webapps directory. diff --git a/jetty-deploy/src/main/config/modules/global-webapp-common.mod b/jetty-deploy/src/main/config/modules/global-webapp-common.mod index 59b7386f05a..a4bc484f94d 100644 --- a/jetty-deploy/src/main/config/modules/global-webapp-common.mod +++ b/jetty-deploy/src/main/config/modules/global-webapp-common.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables Deployer to apply common configuration to all webapp deployments diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/connectors/configuring-ssl.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/connectors/configuring-ssl.adoc index c98f63e231b..bfa721c3971 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/connectors/configuring-ssl.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/connectors/configuring-ssl.adoc @@ -989,3 +989,15 @@ As a reminder, when configuring your includes/excludes, *excludes always win*. Dumps can be configured as part of the `jetty.xml` configuration for your server. Please see the documentation on the link:#jetty-dump-tool[Jetty Dump Tool] for more information. + + +==== SslContextFactory KeyStore Reload + +Jetty can be configured to monitor the directory of the KeyStore file specified in the SslContextFactory, and reload the +SslContextFactory if any changes are detected to the KeyStore file. + +If changes need to be done to other file such as the TrustStore file, this must be done before the change to the Keystore +file which will then trigger the `SslContextFactory` reload. + +With the Jetty distribution this feature can be used by simply activating the `ssl-reload` startup module. +For embedded usage the `KeyStoreScanner` should be created given the `SslContextFactory` and added as a bean on the Server. diff --git a/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod index 9cb340d4387..dba696e2634 100644 --- a/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod +++ b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Adds the FastCGI implementation to the classpath. diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java index edfa3f8bdd4..33c4137be6d 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java @@ -39,8 +39,8 @@ import org.eclipse.jetty.util.StringUtil; /** * Inspired by nginx's try_files functionality. *

- * This filter accepts the files init-param as a list of space-separated - * file URIs. The special token $path represents the current request URL's + * This filter accepts the {@code files} init-param as a list of space-separated + * file URIs. The special token {@code $path} represents the current request URL's * path (the portion after the context path). *

* Typical example of how this filter can be configured is the following: @@ -50,14 +50,14 @@ import org.eclipse.jetty.util.StringUtil; * <filter-class>org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter</filter-class> * <init-param> * <param-name>files</param-name> - * <param-value>maintenance.html $path index.php?p=$path</param-value> + * <param-value>/maintenance.html $path /index.php?p=$path</param-value> * </init-param> * </filter> * - * For a request such as /context/path/to/resource.ext, this filter will - * try to serve the /maintenance.html file if it finds it; failing that, - * it will try to serve the /path/to/resource.ext file if it finds it; - * failing that it will forward the request to index.php?p=/path/to/resource.ext. + * For a request such as {@code /context/path/to/resource.ext}, this filter will + * try to serve the {@code /maintenance.html} file if it finds it; failing that, + * it will try to serve the {@code /path/to/resource.ext} file if it finds it; + * failing that it will forward the request to {@code /index.php?p=/path/to/resource.ext}. * The last file URI specified in the list is therefore the "fallback" to which the request * is forwarded to in case no previous files can be found. *

diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java index 9e791a2513f..77f74f6ba1e 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java @@ -93,6 +93,7 @@ public abstract class AbstractHttpClientServerTest { assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), Matchers.is(0L)); assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), Matchers.is(0L)); + assertThat("Server BufferPool - leaked removes", serverBufferPool.getLeakedRemoves(), Matchers.is(0L)); assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), Matchers.is(0L)); } @@ -101,6 +102,7 @@ public abstract class AbstractHttpClientServerTest LeakTrackingByteBufferPool pool = (LeakTrackingByteBufferPool)clientBufferPool; assertThat("Client BufferPool - leaked acquires", pool.getLeakedAcquires(), Matchers.is(0L)); assertThat("Client BufferPool - leaked releases", pool.getLeakedReleases(), Matchers.is(0L)); + assertThat("Client BufferPool - leaked removes", pool.getLeakedRemoves(), Matchers.is(0L)); assertThat("Client BufferPool - unreleased", pool.getLeakedResources(), Matchers.is(0L)); } diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud-datastore.mod b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud-datastore.mod index 11dbd23f2ff..93f6d0d8e92 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud-datastore.mod +++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud-datastore.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables GCloud Datastore API and implementation diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud.mod b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud.mod index 64e1fb2df00..81dca523f8d 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud.mod +++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/gcloud.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Control GCloud API classpath diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod index 69054019de9..80625f39fa1 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod +++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables GCloudDatastore session management. diff --git a/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-embedded.mod b/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-embedded.mod index a34c2d9a1b4..49585db2eb5 100644 --- a/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-embedded.mod +++ b/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-embedded.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables session data store in an embedded Hazelcast Map diff --git a/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-remote.mod b/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-remote.mod index dd276d3fbdb..a69092dda17 100644 --- a/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-remote.mod +++ b/jetty-hazelcast/src/main/config/modules/session-store-hazelcast-remote.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables session data store in a remote Hazelcast Map diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index eb6655b6962..37e5dda89cc 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -145,7 +145,7 @@ org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs - jetty-all,apache-jsp,apache-jstl,jetty-start,jetty-spring + jetty-all,apache-jsp,apache-jstl,jetty-start,jetty-spring,jetty-slf4j-impl jar ${assembly-directory}/lib diff --git a/jetty-home/src/main/resources/modules/conscrypt.mod b/jetty-home/src/main/resources/modules/conscrypt.mod index 9a886357be8..2e1e7f50f1a 100644 --- a/jetty-home/src/main/resources/modules/conscrypt.mod +++ b/jetty-home/src/main/resources/modules/conscrypt.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Installs the Conscrypt JSSE provider diff --git a/jetty-home/src/main/resources/modules/hawtio.mod b/jetty-home/src/main/resources/modules/hawtio.mod index a64f8327a0c..1f4c95649e5 100644 --- a/jetty-home/src/main/resources/modules/hawtio.mod +++ b/jetty-home/src/main/resources/modules/hawtio.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Deploys the Hawtio console as a webapplication. diff --git a/jetty-home/src/main/resources/modules/jamon.mod b/jetty-home/src/main/resources/modules/jamon.mod index db81214f3e0..21bac47de96 100644 --- a/jetty-home/src/main/resources/modules/jamon.mod +++ b/jetty-home/src/main/resources/modules/jamon.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Deploys the JAMon webapplication diff --git a/jetty-home/src/main/resources/modules/jminix.mod b/jetty-home/src/main/resources/modules/jminix.mod index 7f2ff89e3a2..fcf1c122709 100644 --- a/jetty-home/src/main/resources/modules/jminix.mod +++ b/jetty-home/src/main/resources/modules/jminix.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Deploys the Jminix JMX Console within the server diff --git a/jetty-home/src/main/resources/modules/jolokia.mod b/jetty-home/src/main/resources/modules/jolokia.mod index d1d61887044..aea83e48608 100644 --- a/jetty-home/src/main/resources/modules/jolokia.mod +++ b/jetty-home/src/main/resources/modules/jolokia.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Deploys the Jolokia console as a web application. diff --git a/jetty-home/src/main/resources/modules/jsp.mod b/jetty-home/src/main/resources/modules/jsp.mod index 12bbcd05b3c..3dd67713fc2 100644 --- a/jetty-home/src/main/resources/modules/jsp.mod +++ b/jetty-home/src/main/resources/modules/jsp.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables JSP for all webapplications deployed on the server. diff --git a/jetty-home/src/main/resources/modules/jstl.mod b/jetty-home/src/main/resources/modules/jstl.mod index 7311f47315a..67c88f7d91f 100644 --- a/jetty-home/src/main/resources/modules/jstl.mod +++ b/jetty-home/src/main/resources/modules/jstl.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables JSTL for all webapplications deployed on the server diff --git a/jetty-home/src/main/resources/modules/stop.mod b/jetty-home/src/main/resources/modules/stop.mod index a7b8c966c4d..1352cc95463 100644 --- a/jetty-home/src/main/resources/modules/stop.mod +++ b/jetty-home/src/main/resources/modules/stop.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] This module causes jetty to stop immediately after starting. This is good for testing configuration and/or precompiling quickstart webapps diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java index 219e9c13584..436dd14bb58 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java @@ -37,11 +37,11 @@ public class HttpCookie private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); /** - *If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true + * If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true **/ public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; /** - *These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment + * These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment **/ private static final String SAME_SITE_COMMENT = "__SAME_SITE_"; public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__"; @@ -474,10 +474,10 @@ public class HttpCookie LOG.debug("No default value for SameSite"); return null; } - + if (o instanceof SameSite) return (SameSite)o; - + try { SameSite samesite = Enum.valueOf(SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH)); diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java index 6aa7ae9eb4b..e8a2b9197d4 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.generator.Generator; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; @@ -118,17 +117,6 @@ public class HTTP2ClientSession extends HTTP2Session } } - @Override - protected void onResetForUnknownStream(ResetFrame frame) - { - int streamId = frame.getStreamId(); - boolean closed = isClientStream(streamId) ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId); - if (closed) - notifyReset(this, frame); - else - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame"); - } - @Override public void onPushPromise(PushPromiseFrame frame) { 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 2ac6077a988..5931417a2ed 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 @@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import javax.servlet.AsyncContext; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -51,6 +52,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.HTTP2Flusher; @@ -1042,6 +1044,64 @@ public class StreamResetTest extends AbstractTest } } + @Test + public void testResetBeforeReceivingWindowUpdate() throws Exception + { + int window = FlowControlStrategy.DEFAULT_WINDOW_SIZE; + float ratio = 0.5F; + AtomicReference streamRef = new AtomicReference<>(); + Consumer http2Factory = http2 -> + { + http2.setInitialSessionRecvWindow(window); + http2.setInitialStreamRecvWindow(window); + http2.setFlowControlStrategyFactory(() -> new BufferingFlowControlStrategy(ratio) + { + @Override + protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame) + { + // Before sending the window update, reset from the client side. + if (stream != null) + streamRef.get().reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + super.sendWindowUpdate(stream, session, frame); + } + }); + }; + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); + HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false); + Callback.Completable completable = new Callback.Completable(); + stream.headers(responseFrame, completable); + // Consume the request content as it arrives. + return new Stream.Listener.Adapter(); + } + }, http2Factory); + + CountDownLatch failureLatch = new CountDownLatch(1); + Session client = newClient(new Session.Listener.Adapter() + { + @Override + public void onFailure(Session session, Throwable failure) + { + failureLatch.countDown(); + } + }); + MetaData.Request request = newRequest("GET", HttpFields.EMPTY); + HeadersFrame requestFrame = new HeadersFrame(request, null, false); + FuturePromise promise = new FuturePromise<>(); + client.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + Stream stream = promise.get(5, TimeUnit.SECONDS); + streamRef.set(stream); + // Send enough bytes to trigger the server to send a window update. + ByteBuffer content = ByteBuffer.allocate((int)(window * ratio) + 1024); + stream.data(new DataFrame(stream.getId(), content, false), Callback.NOOP); + + assertFalse(failureLatch.await(1, TimeUnit.SECONDS)); + } + private void waitUntilTCPCongested(WriteFlusher flusher) throws TimeoutException, InterruptedException { long start = System.nanoTime(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java index f4b2823b7fa..8812c785a36 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java @@ -199,18 +199,22 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy protected void onSessionUnstalled(ISession session) { - sessionStallTime.addAndGet(System.nanoTime() - sessionStall.getAndSet(0)); + long stallTime = System.nanoTime() - sessionStall.getAndSet(0); + sessionStallTime.addAndGet(stallTime); if (LOG.isDebugEnabled()) - LOG.debug("Session unstalled {}", session); + LOG.debug("Session unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), session); } protected void onStreamUnstalled(IStream stream) { Long time = streamsStalls.remove(stream); if (time != null) - streamsStallTime.addAndGet(System.nanoTime() - time); - if (LOG.isDebugEnabled()) - LOG.debug("Stream unstalled {}", stream); + { + long stallTime = System.nanoTime() - time; + streamsStallTime.addAndGet(stallTime); + if (LOG.isDebugEnabled()) + LOG.debug("Stream unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), stream); + } } @ManagedAttribute(value = "The time, in milliseconds, that the session flow control has stalled", readonly = true) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java index 941dda4fcba..bebbd440682 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java @@ -123,7 +123,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy session.updateRecvWindow(level); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, {} bytes, updated session recv window by {}/{} for {}", length, level, maxLevel, session); - session.frames(null, Callback.NOOP, new WindowUpdateFrame(0, level), Frame.EMPTY_ARRAY); + sendWindowUpdate(null, session, new WindowUpdateFrame(0, level)); } else { @@ -157,7 +157,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy stream.updateRecvWindow(level); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, {} bytes, updated stream recv window by {}/{} for {}", length, level, maxLevel, stream); - session.frames(stream, Callback.NOOP, new WindowUpdateFrame(stream.getId(), level), Frame.EMPTY_ARRAY); + sendWindowUpdate(stream, session, new WindowUpdateFrame(stream.getId(), level)); } else { @@ -169,6 +169,11 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy } } + protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame) + { + session.frames(stream, Callback.NOOP, frame, Frame.EMPTY_ARRAY); + } + @Override public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame) { 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 2f103d8d609..206b869e58e 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 @@ -101,8 +101,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. { if (LOG.isDebugEnabled()) LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(buffer)); - if (buffer != null) - producer.setInputBuffer(buffer); + producer.setInputBuffer(buffer); } public boolean isUseInputDirectByteBuffers() 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 9c79b606224..93ffc90c3ac 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 @@ -436,7 +436,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable // It's an application frame; is the stream gone already? if (stream == null) return true; - return stream.isReset(); + return stream.isResetOrFailed(); } private boolean isProtocolFrame(Frame frame) 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 8d0584fb48e..107cb15be93 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 @@ -239,7 +239,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onData(final DataFrame frame, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); int streamId = frame.getStreamId(); IStream stream = getStream(streamId); @@ -259,19 +259,27 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio else { if (LOG.isDebugEnabled()) - LOG.debug("Stream #{} not found", streamId); + LOG.debug("Stream #{} not found on {}", streamId, this); // We must enlarge the session flow control window, // otherwise other requests will be stalled. flowControl.onDataConsumed(this, null, flowControlLength); - boolean local = (streamId & 1) == (localStreamIds.get() & 1); - boolean closed = local ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId); - if (closed) + if (isStreamClosed(streamId)) reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), callback); else onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", callback); } } + private boolean isStreamClosed(int streamId) + { + return isLocalStream(streamId) ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId); + } + + private boolean isLocalStream(int streamId) + { + return (streamId & 1) == (localStreamIds.get() & 1); + } + protected boolean isLocalStreamClosed(int streamId) { return streamId <= localStreamIds.get(); @@ -289,14 +297,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onPriority(PriorityFrame frame) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); } @Override public void onReset(ResetFrame frame) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); int streamId = frame.getStreamId(); IStream stream = getStream(streamId); @@ -310,7 +318,13 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected abstract void onResetForUnknownStream(ResetFrame frame); + protected void onResetForUnknownStream(ResetFrame frame) + { + if (isStreamClosed(frame.getStreamId())) + notifyReset(this, frame); + else + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame"); + } @Override public void onSettings(SettingsFrame frame) @@ -322,7 +336,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onSettings(SettingsFrame frame, boolean reply) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); if (frame.isReply()) return; @@ -406,7 +420,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onPing(PingFrame frame) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); if (frame.isReply()) { @@ -440,7 +454,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onGoAway(final GoAwayFrame frame) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.REMOTELY_CLOSED)) { @@ -459,7 +473,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onWindowUpdate(WindowUpdateFrame frame) { if (LOG.isDebugEnabled()) - LOG.debug("Received {}", frame); + LOG.debug("Received {} on {}", frame, this); int streamId = frame.getStreamId(); int windowDelta = frame.getWindowDelta(); @@ -481,7 +495,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } else { - if (!isRemoteStreamClosed(streamId)) + if (!isStreamClosed(streamId)) onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame"); } } @@ -514,7 +528,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio public void onStreamFailure(int streamId, int error, String reason) { Callback callback = new ResetCallback(streamId, error, Callback.NOOP); - Throwable failure = toFailure("Stream failure", error, reason); + Throwable failure = toFailure(error, reason); + if (LOG.isDebugEnabled()) + LOG.debug("Stream #{} failure {}", streamId, this, failure); onStreamFailure(streamId, error, reason, failure, callback); } @@ -535,12 +551,16 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio protected void onConnectionFailure(int error, String reason, Callback callback) { - Throwable failure = toFailure("Session failure", error, reason); + Throwable failure = toFailure(error, reason); + if (LOG.isDebugEnabled()) + LOG.debug("Session failure {}", this, failure); onFailure(error, reason, failure, new FailureCallback(error, reason, callback)); } protected void abort(Throwable failure) { + if (LOG.isDebugEnabled()) + LOG.debug("Session abort {}", this, failure); onFailure(ErrorCode.NO_ERROR.code, null, failure, new TerminateCallback(failure)); } @@ -560,7 +580,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { int error = frame.getError(); String reason = frame.tryConvertPayload(); - Throwable failure = toFailure("Session close", error, reason); + Throwable failure = toFailure(error, reason); + if (LOG.isDebugEnabled()) + LOG.debug("Session close {}", this, failure); Collection streams = getStreams(); int count = streams.size(); Callback countCallback = new CountingCallback(callback, count + 1); @@ -571,9 +593,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio notifyClose(this, frame, countCallback); } - private Throwable toFailure(String message, int error, String reason) + private Throwable toFailure(int error, String reason) { - return new IOException(String.format("%s %s/%s", message, ErrorCode.toString(error, null), reason)); + return new IOException(String.format("%s/%s", ErrorCode.toString(error, null), reason)); } @Override @@ -671,14 +693,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED)) { if (LOG.isDebugEnabled()) - LOG.debug("Closing {}/{}", error, reason); + LOG.debug("Closing {}/{} {}", error, reason, this); closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason); control(null, callback, closeFrame); return true; } if (LOG.isDebugEnabled()) - LOG.debug("Ignoring close {}/{}, already closed", error, reason); + LOG.debug("Ignoring close {}/{}, already closed {}", error, reason, this); callback.succeeded(); return false; } @@ -763,7 +785,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private void frame(HTTP2Flusher.Entry entry, boolean flush) { if (LOG.isDebugEnabled()) - LOG.debug("{} {}", flush ? "Sending" : "Queueing", entry.frame); + LOG.debug("{} {} on {}", flush ? "Sending" : "Queueing", entry.frame, this); // Ping frames are prepended to process them as soon as possible. boolean queued = entry.frame.getType() == FrameType.PING ? flusher.prepend(entry) : flusher.append(entry); if (queued && flush) @@ -859,7 +881,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio onStreamClosed(stream); flowControl.onStreamDestroyed(stream); if (LOG.isDebugEnabled()) - LOG.debug("Removed {} {}", stream.isLocal() ? "local" : "remote", stream); + LOG.debug("Removed {} {} from {}", stream.isLocal() ? "local" : "remote", stream, this); } } @@ -1129,7 +1151,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return lastRemoteStreamId.get(); } - private void updateLastRemoteStreamId(int streamId) + protected void updateLastRemoteStreamId(int streamId) { Atomics.updateMax(lastRemoteStreamId, streamId); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 9a3b4607046..e13c7352e49 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -42,6 +42,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.IdleTimeout; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.MathUtils; @@ -61,7 +62,6 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private final AtomicReference attachment = new AtomicReference<>(); private final AtomicReference> attributes = new AtomicReference<>(); private final AtomicReference closeState = new AtomicReference<>(CloseState.NOT_CLOSED); - private final AtomicReference writing = new AtomicReference<>(); private final AtomicInteger sendWindow = new AtomicInteger(); private final AtomicInteger recvWindow = new AtomicInteger(); private final long timeStamp = System.nanoTime(); @@ -69,9 +69,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private final int streamId; private final MetaData.Request request; private final boolean local; + private Callback sendCallback; + private Throwable failure; private boolean localReset; - private Listener listener; private boolean remoteReset; + private Listener listener; private long dataLength; private long dataDemand; private boolean dataInitial; @@ -141,17 +143,31 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public void reset(ResetFrame frame, Callback callback) { - if (isReset()) - return; - localReset = true; + synchronized (this) + { + if (isReset()) + return; + localReset = true; + failure = new EOFException("reset"); + } session.frames(this, callback, frame, Frame.EMPTY_ARRAY); } private boolean startWrite(Callback callback) { - if (writing.compareAndSet(null, callback)) - return true; - callback.failed(new WritePendingException()); + Throwable failure; + synchronized (this) + { + failure = this.failure; + if (failure == null && sendCallback == null) + { + sendCallback = callback; + return true; + } + } + if (failure == null) + failure = new WritePendingException(); + callback.failed(failure); return false; } @@ -176,7 +192,27 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public boolean isReset() { - return localReset || remoteReset; + synchronized (this) + { + return localReset || remoteReset; + } + } + + private boolean isFailed() + { + synchronized (this) + { + return failure != null; + } + } + + @Override + public boolean isResetOrFailed() + { + synchronized (this) + { + return isReset() || isFailed(); + } } @Override @@ -440,7 +476,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private void onReset(ResetFrame frame, Callback callback) { - remoteReset = true; + synchronized (this) + { + remoteReset = true; + failure = new EofException("reset"); + } close(); session.removeStream(this); notifyReset(this, frame, callback); @@ -461,8 +501,12 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private void onFailure(FailureFrame frame, Callback callback) { - // Don't close or remove the stream, as the listener may - // want to use it, for example to send a RST_STREAM frame. + synchronized (this) + { + failure = frame.getFailure(); + } + close(); + session.removeStream(this); notifyFailure(this, frame, callback); } @@ -645,7 +689,12 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private Callback endWrite() { - return writing.getAndSet(null); + synchronized (this) + { + Callback callback = sendCallback; + sendCallback = null; + return callback; + } } private void notifyNewStream(Stream stream) @@ -794,10 +843,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public String toString() { - return String.format("%s@%x#%d{sendWindow=%s,recvWindow=%s,demand=%d,reset=%b/%b,%s,age=%d,attachment=%s}", + return String.format("%s@%x#%d@%x{sendWindow=%s,recvWindow=%s,demand=%d,reset=%b/%b,%s,age=%d,attachment=%s}", getClass().getSimpleName(), hashCode(), getId(), + session.hashCode(), sendWindow, recvWindow, demand(), diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java index 893cfdbdd04..a2f58724ca0 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java @@ -114,4 +114,11 @@ public interface IStream extends Stream, Closeable * @see #isClosed() */ boolean isRemotelyClosed(); + + /** + * @return whether this stream has been reset (locally or remotely) or has been failed + * @see #isReset() + * @see Listener#onFailure(Stream, int, String, Throwable, Callback) + */ + boolean isResetOrFailed(); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index 5997c00d199..a04c81c42b7 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -149,7 +149,7 @@ public class Parser return false; if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame header from {}", headerParser, buffer); + LOG.debug("Parsed {} frame header from {}@{}", headerParser, buffer, Integer.toHexString(buffer.hashCode())); if (headerParser.getLength() > getMaxFrameLength()) return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR, "invalid_frame_length"); @@ -199,7 +199,7 @@ public class Parser return false; } if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame body from {}", FrameType.from(type), buffer); + LOG.debug("Parsed {} frame body from {}@{}", FrameType.from(type), buffer, Integer.toHexString(buffer.hashCode())); reset(); return true; } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index 72f9e70d7c5..67bb7a8f8f6 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -406,7 +406,9 @@ public class HpackEncoder encodeName(buffer, (byte)0x00, 4, header.asString(), name); encodeValue(buffer, true, field.getValue()); if (_debug) - encoding = "LitIdxNS" + (1 + NBitInteger.octectsNeeded(4, _context.index(name))) + "HuffV!Idx"; + encoding = "Lit" + + ((name == null) ? "HuffN" : "IdxNS" + (1 + NBitInteger.octectsNeeded(4, _context.index(name)))) + + "HuffV!Idx"; } else { diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java index 7cbfe76b05d..5d77bf99dc2 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java @@ -175,6 +175,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest @Override public void onSettings(Session session, SettingsFrame frame) { + super.onSettings(session, frame); // Send another request to simulate a request being // sent concurrently with connection establishment. // Sending this request will trigger the creation of @@ -199,7 +200,6 @@ public class MaxConcurrentStreamsTest extends AbstractTest } }); } - super.onSettings(session, frame); } }, promise, context); } diff --git a/jetty-http2/http2-server/src/main/config/modules/http2.mod b/jetty-http2/http2-server/src/main/config/modules/http2.mod index c33beaf2c25..46cb5961385 100644 --- a/jetty-http2/http2-server/src/main/config/modules/http2.mod +++ b/jetty-http2/http2-server/src/main/config/modules/http2.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables HTTP2 protocol support on the TLS(SSL) Connector, diff --git a/jetty-http2/http2-server/src/main/config/modules/http2c.mod b/jetty-http2/http2-server/src/main/config/modules/http2c.mod index cac69f065ad..aae7e6d342a 100644 --- a/jetty-http2/http2-server/src/main/config/modules/http2c.mod +++ b/jetty-http2/http2-server/src/main/config/modules/http2c.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables the HTTP2C protocol on the HTTP Connector diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java index 1660a244a85..3f91042fd30 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java @@ -106,6 +106,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis { if (isClosed()) { + updateLastRemoteStreamId(streamId); reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP); } else @@ -157,17 +158,6 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis } } - @Override - protected void onResetForUnknownStream(ResetFrame frame) - { - int streamId = frame.getStreamId(); - boolean closed = isClientStream(streamId) ? isRemoteStreamClosed(streamId) : isLocalStreamClosed(streamId); - if (closed) - notifyReset(this, frame); - else - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame"); - } - @Override public void onPushPromise(PushPromiseFrame frame) { diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index 2df824cf904..e08993de746 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -330,7 +330,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport public void onStreamFailure(Throwable failure) { - transportCallback.failed(failure); + transportCallback.abort(failure); } public boolean onStreamTimeout(Throwable failure) @@ -408,17 +408,6 @@ public class HttpTransportOverHTTP2 implements HttpTransport * being reset, or the connection being closed *
  • an asynchronous idle timeout
  • * - *

    The last 2 cases may happen during a send, when the frames - * are being generated in the flusher. - * In such cases, this class must avoid that the nested callback is notified - * while the frame generation is in progress, because the nested callback - * may modify other states (such as clearing the {@code HttpOutput._buffer}) - * that are accessed during frame generation.

    - *

    The solution implemented in this class works by splitting the send - * operation in 3 parts: {@code pre-send}, {@code send} and {@code post-send}. - * Asynchronous state changes happening during {@code send} are stored - * and only executed in {@code post-send}, therefore never interfering - * with frame generation.

    * * @see State */ @@ -442,14 +431,14 @@ public class HttpTransportOverHTTP2 implements HttpTransport { Throwable failure = sending(callback, commit); if (failure == null) - { sendFrame.accept(this); - pending(); - } else - { callback.failed(failure); - } + } + + private void abort(Throwable failure) + { + failed(failure); } private Throwable sending(Callback callback, boolean commit) @@ -477,58 +466,6 @@ public class HttpTransportOverHTTP2 implements HttpTransport } } - private void pending() - { - Callback callback; - boolean commit; - Throwable failure; - synchronized (this) - { - switch (_state) - { - case SENDING: - { - // The send has not completed the callback yet, - // wait for succeeded() or failed() to be called. - _state = State.PENDING; - return; - } - case SUCCEEDING: - { - // The send already completed successfully, but the - // call to succeeded() was delayed, so call it now. - callback = _callback; - commit = _commit; - failure = null; - reset(null); - break; - } - case FAILING: - { - // The send already completed with a failure, but - // the call to failed() was delayed, so call it now. - callback = _callback; - commit = _commit; - failure = _failure; - reset(failure); - break; - } - default: - { - callback = _callback; - commit = _commit; - failure = new IllegalStateException("Invalid transport state: " + _state); - reset(failure); - break; - } - } - } - if (failure == null) - succeed(callback, commit); - else - fail(callback, commit, failure); - } - @Override public void succeeded() { @@ -536,30 +473,21 @@ public class HttpTransportOverHTTP2 implements HttpTransport boolean commit; synchronized (this) { - switch (_state) + if (_state != State.SENDING) { - case SENDING: - { - _state = State.SUCCEEDING; - // Succeeding the callback will be done in postSend(). - return; - } - case PENDING: - { - callback = _callback; - commit = _commit; - reset(null); - break; - } - default: - { - // This thread lost the race to succeed the current - // send, as other threads likely already failed it. - return; - } + // This thread lost the race to succeed the current + // send, as other threads likely already failed it. + return; } + callback = _callback; + commit = _commit; + reset(null); } - succeed(callback, commit); + if (LOG.isDebugEnabled()) + LOG.debug("HTTP2 Response #{}/{} {} success", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + commit ? "commit" : "flush"); + callback.succeeded(); } @Override @@ -569,104 +497,37 @@ public class HttpTransportOverHTTP2 implements HttpTransport boolean commit; synchronized (this) { - switch (_state) + if (_state != State.SENDING) { - case SENDING: - { - _state = State.FAILING; - _failure = failure; - // Failing the callback will be done in postSend(). - return; - } - case IDLE: - case PENDING: - { - callback = _callback; - commit = _commit; - reset(failure); - break; - } - default: - { - // This thread lost the race to fail the current send, - // as other threads already succeeded or failed it. - return; - } + reset(failure); + return; } + callback = _callback; + commit = _commit; + reset(failure); } - fail(callback, commit, failure); - } - - private boolean idleTimeout(Throwable failure) - { - Callback callback; - boolean timeout; - synchronized (this) - { - switch (_state) - { - case PENDING: - { - // The send was started but idle timed out, fail it. - callback = _callback; - timeout = true; - reset(failure); - break; - } - case IDLE: - // The application may be suspended, ignore the idle timeout. - case SENDING: - // A send has been started at the same time of an idle timeout; - // Ignore the idle timeout and let the write continue normally. - case SUCCEEDING: - case FAILING: - // An idle timeout during these transient states is ignored. - case FAILED: - // Already failed, ignore the idle timeout. - { - callback = null; - timeout = false; - break; - } - default: - { - // Should not happen, but just in case. - callback = _callback; - if (callback == null) - callback = Callback.NOOP; - timeout = true; - failure = new IllegalStateException("Invalid transport state: " + _state, failure); - reset(failure); - break; - } - } - } - idleTimeout(callback, timeout, failure); - return timeout; - } - - private void succeed(Callback callback, boolean commit) - { - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}/{} {} success", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - commit ? "commit" : "flush"); - callback.succeeded(); - } - - private void fail(Callback callback, boolean commit, Throwable failure) - { if (LOG.isDebugEnabled()) LOG.debug("HTTP2 Response #{}/{} {} failure", stream.getId(), Integer.toHexString(stream.getSession().hashCode()), commit ? "commit" : "flush", failure); - if (callback != null) - callback.failed(failure); + callback.failed(failure); } - private void idleTimeout(Callback callback, boolean timeout, Throwable failure) + private boolean idleTimeout(Throwable failure) { + Callback callback = null; + synchronized (this) + { + // Ignore idle timeouts if not writing, + // as the application may be suspended. + if (_state == State.SENDING) + { + callback = _callback; + reset(failure); + } + } + boolean timeout = callback != null; if (LOG.isDebugEnabled()) LOG.debug("HTTP2 Response #{}/{} idle timeout {}", stream.getId(), Integer.toHexString(stream.getSession().hashCode()), @@ -674,6 +535,18 @@ public class HttpTransportOverHTTP2 implements HttpTransport failure); if (timeout) callback.failed(failure); + return timeout; + } + + @Override + public InvocationType getInvocationType() + { + Callback callback; + synchronized (this) + { + callback = _callback; + } + return callback != null ? callback.getInvocationType() : Callback.super.getInvocationType(); } } @@ -686,67 +559,12 @@ public class HttpTransportOverHTTP2 implements HttpTransport { /** *

    No send initiated or in progress.

    - *

    Next states could be:

    - *
      - *
    • {@link #SENDING}, when {@link TransportCallback#send(Callback, boolean, Consumer)} - * is called by the transport to initiate a send
    • - *
    • {@link #FAILED}, when {@link TransportCallback#failed(Throwable)} - * is called by an asynchronous failure
    • - *
    */ IDLE, /** - *

    A send is initiated; the nested callback in {@link TransportCallback} - * cannot be notified while in this state.

    - *

    Next states could be:

    - *
      - *
    • {@link #SUCCEEDING}, when {@link TransportCallback#succeeded()} - * is called synchronously because the send succeeded
    • - *
    • {@link #FAILING}, when {@link TransportCallback#failed(Throwable)} - * is called synchronously because the send failed
    • - *
    • {@link #PENDING}, when {@link TransportCallback#pending()} - * is called before the send completes
    • - *
    + *

    A send is initiated and possibly in progress.

    */ SENDING, - /** - *

    A send was initiated and is now pending, waiting for the {@link TransportCallback} - * to be notified of success or failure.

    - *

    Next states could be:

    - *
      - *
    • {@link #IDLE}, when {@link TransportCallback#succeeded()} - * is called because the send succeeded
    • - *
    • {@link #FAILED}, when {@link TransportCallback#failed(Throwable)} - * is called because either the send failed, or an asynchronous failure happened
    • - *
    - */ - PENDING, - /** - *

    A send was initiated and succeeded, but {@link TransportCallback#pending()} - * has not been called yet.

    - *

    This state indicates that the success actions (such as notifying the - * {@link TransportCallback} nested callback) must be performed when - * {@link TransportCallback#pending()} is called.

    - *

    Next states could be:

    - *
      - *
    • {@link #IDLE}, when {@link TransportCallback#pending()} - * is called
    • - *
    - */ - SUCCEEDING, - /** - *

    A send was initiated and failed, but {@link TransportCallback#pending()} - * has not been called yet.

    - *

    This state indicates that the failure actions (such as notifying the - * {@link TransportCallback} nested callback) must be performed when - * {@link TransportCallback#pending()} is called.

    - *

    Next states could be:

    - *
      - *
    • {@link #FAILED}, when {@link TransportCallback#pending()} - * is called
    • - *
    - */ - FAILING, /** *

    The terminal state indicating failure of the send.

    */ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index b4483f703d2..efa195d0d40 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -435,7 +435,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint { Connection oldConnection = getConnection(); - ByteBuffer prefilled = (oldConnection instanceof Connection.UpgradeFrom) + ByteBuffer buffer = (oldConnection instanceof Connection.UpgradeFrom) ? ((Connection.UpgradeFrom)oldConnection).onUpgradeFrom() : null; oldConnection.onClose(null); @@ -443,12 +443,15 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint if (LOG.isDebugEnabled()) LOG.debug("{} upgrading from {} to {} with {}", - this, oldConnection, newConnection, BufferUtil.toDetailString(prefilled)); + this, oldConnection, newConnection, BufferUtil.toDetailString(buffer)); - if (newConnection instanceof Connection.UpgradeTo) - ((Connection.UpgradeTo)newConnection).onUpgradeTo(prefilled); - else if (BufferUtil.hasContent(prefilled)) - throw new IllegalStateException("Cannot upgrade: " + newConnection + " does not implement " + Connection.UpgradeTo.class.getName()); + if (BufferUtil.hasContent(buffer)) + { + if (newConnection instanceof Connection.UpgradeTo) + ((Connection.UpgradeTo)newConnection).onUpgradeTo(buffer); + else + throw new IllegalStateException("Cannot upgrade: " + newConnection + " does not implement " + Connection.UpgradeTo.class.getName()); + } newConnection.onOpen(); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java index d1dc3c09ae6..19d2d9651b6 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java @@ -23,8 +23,11 @@ import java.util.Arrays; import java.util.Objects; import java.util.function.IntFunction; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

    A ByteBuffer pool where ByteBuffers are held in queues that are held in array elements.

    @@ -35,6 +38,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject; @ManagedObject public class ArrayByteBufferPool extends AbstractByteBufferPool { + private static final Logger LOG = LoggerFactory.getLogger(MappedByteBufferPool.class); + private final int _minCapacity; private final ByteBufferPool.Bucket[] _direct; private final ByteBufferPool.Bucket[] _indirect; @@ -119,8 +124,18 @@ public class ArrayByteBufferPool extends AbstractByteBufferPool { if (buffer == null) return; + + int capacity = buffer.capacity(); + // Validate that this buffer is from this pool. + if ((capacity % getCapacityFactor()) != 0) + { + if (LOG.isDebugEnabled()) + LOG.debug("ByteBuffer {} does not belong to this pool, discarding it", BufferUtil.toDetailString(buffer)); + return; + } + boolean direct = buffer.isDirect(); - ByteBufferPool.Bucket bucket = bucketFor(buffer.capacity(), direct, this::newBucket); + ByteBufferPool.Bucket bucket = bucketFor(capacity, direct, this::newBucket); if (bucket != null) { bucket.release(buffer); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java index 06e64fd9406..307c627d90a 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java @@ -57,6 +57,18 @@ public interface ByteBufferPool */ public void release(ByteBuffer buffer); + /** + *

    Removes a {@link ByteBuffer} that was previously obtained with {@link #acquire(int, boolean)}.

    + *

    The buffer will not be available for further reuse.

    + * + * @param buffer the buffer to remove + * @see #acquire(int, boolean) + * @see #release(ByteBuffer) + */ + default void remove(ByteBuffer buffer) + { + } + /** *

    Creates a new ByteBuffer of the given capacity and the given directness.

    * diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java index def9daa7657..a4214a6c425 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java @@ -99,31 +99,49 @@ public interface Connection extends Closeable public long getCreatedTimeStamp(); + /** + *

    {@link Connection} implementations implement this interface when they + * can upgrade from the protocol they speak (for example HTTP/1.1) + * to a different protocol (e.g. HTTP/2).

    + * + * @see EndPoint#upgrade(Connection) + * @see UpgradeTo + */ public interface UpgradeFrom { /** - *

    Takes the input buffer from the connection on upgrade.

    - *

    This method is used to take any unconsumed input from - * a connection during an upgrade.

    + *

    Invoked during an {@link EndPoint#upgrade(Connection) upgrade} + * to produce a buffer containing bytes that have not been consumed by + * this connection, and that must be consumed by the upgrade-to + * connection.

    * - * @return A buffer of unconsumed input. The caller must return the buffer - * to the bufferpool when consumed and this connection must not. + * @return a buffer of unconsumed bytes to pass to the upgrade-to connection. + * The buffer does not belong to any pool and should be discarded after + * having consumed its bytes. + * The returned buffer may be null if there are no unconsumed bytes. */ - ByteBuffer onUpgradeFrom(); + public ByteBuffer onUpgradeFrom(); } + /** + *

    {@link Connection} implementations implement this interface when they + * can be upgraded to the protocol they speak (e.g. HTTP/2) + * from a different protocol (e.g. HTTP/1.1).

    + */ public interface UpgradeTo { /** - *

    Callback method invoked when this connection is upgraded.

    - *

    This must be called before {@link #onOpen()}.

    + *

    Invoked during an {@link EndPoint#upgrade(Connection) upgrade} + * to receive a buffer containing bytes that have not been consumed by + * the upgrade-from connection, and that must be consumed by this + * connection.

    * - * @param prefilled An optional buffer that can contain prefilled data. Typically this - * results from an upgrade of one protocol to the other where the old connection has buffered - * data destined for the new connection. The new connection must take ownership of the buffer - * and is responsible for returning it to the buffer pool + * @param buffer a non-null buffer of unconsumed bytes received from + * the upgrade-from connection. + * The buffer does not belong to any pool and should be discarded after + * having consumed its bytes. */ - void onUpgradeTo(ByteBuffer prefilled); + public void onUpgradeTo(ByteBuffer buffer); } /** diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 09126f49124..b82f5ecff6f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -31,7 +31,7 @@ import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.thread.Invocable; /** - * A transport EndPoint + *

    EndPoint is the abstraction for an I/O channel that transports bytes.

    * *

    Asynchronous Methods

    *

    The asynchronous scheduling methods of {@link EndPoint} @@ -40,86 +40,90 @@ import org.eclipse.jetty.util.thread.Invocable; * some inefficiencies.

    *

    This class will frequently be used in conjunction with some of the utility * implementations of {@link Callback}, such as {@link FutureCallback} and - * {@link IteratingCallback}. Examples are:

    + * {@link IteratingCallback}.

    * - *

    Blocking Read

    - *

    A FutureCallback can be used to block until an endpoint is ready to be filled - * from:

    - *
    - * FutureCallback<String> future = new FutureCallback<>();
    - * endpoint.fillInterested("ContextObj",future);
    - * ...
    - * String context = future.get(); // This blocks
    - * int filled=endpoint.fill(mybuffer);
    - * 
    + *

    Reads

    + *

    A {@link FutureCallback} can be used to block until an endpoint is ready + * to fill bytes - the notification will be emitted by the NIO subsystem:

    + *
    + * FutureCallback callback = new FutureCallback();
    + * endPoint.fillInterested(callback);
      *
    - * 

    Dispatched Read

    - *

    By using a different callback, the read can be done asynchronously in its own dispatched thread:

    - *
    - * endpoint.fillInterested("ContextObj",new ExecutorCallback<String>(executor)
    + * // Blocks until read to fill bytes.
    + * callback.get();
    + *
    + * // Now bytes can be filled in a ByteBuffer.
    + * int filled = endPoint.fill(byteBuffer);
    + * 
    + * + *

    Asynchronous Reads

    + *

    A {@link Callback} can be used to read asynchronously in its own dispatched + * thread:

    + *
    + * endPoint.fillInterested(new Callback()
      * {
    - *   public void onCompleted(String context)
    + *   public void onSucceeded()
      *   {
    - *     int filled=endpoint.fill(mybuffer);
    - *     ...
    + *     executor.execute(() ->
    + *     {
    + *       // Fill bytes in a different thread.
    + *       int filled = endPoint.fill(byteBuffer);
    + *     });
    + *   }
    + *   public void onFailed(Throwable failure)
    + *   {
    + *     endPoint.close();
      *   }
    - *   public void onFailed(String context,Throwable cause) {...}
      * });
    - * 
    - *

    The executor callback can also be customized to not dispatch in some circumstances when - * it knows it can use the callback thread and does not need to dispatch.

    + *
    * - *

    Blocking Write

    - *

    The write contract is that the callback complete is not called until all data has been - * written or there is a failure. For blocking this looks like:

    - *
    - * FutureCallback<String> future = new FutureCallback<>();
    - * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
    - * String context = future.get(); // This blocks
    - * 
    + *

    Blocking Writes

    + *

    The write contract is that the callback is completed when all the bytes + * have been written or there is a failure. + * Blocking writes look like this:

    + *
    + * FutureCallback callback = new FutureCallback();
    + * endpoint.write(callback, headerBuffer, contentBuffer);
      *
    - * 

    Dispatched Write

    - *

    Note also that multiple buffers may be passed in write so that gather writes - * can be done:

    - *
    - * endpoint.write("ContextObj",new ExecutorCallback<String>(executor)
    - * {
    - *   public void onCompleted(String context)
    - *   {
    - *     int filled=endpoint.fill(mybuffer);
    - *     ...
    - *   }
    - *   public void onFailed(String context,Throwable cause) {...}
    - * },headerBuffer,contentBuffer);
    - * 
    + * // Blocks until the write succeeds or fails. + * future.get(); + *
    + *

    Note also that multiple buffers may be passed in {@link #write(Callback, ByteBuffer...)} + * so that gather writes can be performed for efficiency.

    */ public interface EndPoint extends Closeable { /** - * Marks an EndPoint that wraps another EndPoint. + * Marks an {@code EndPoint} that wraps another {@code EndPoint}. */ public interface Wrapper { /** - * @return The wrapped EndPoint + * @return The wrapped {@code EndPoint} */ EndPoint unwrap(); } - + /** - * @return The local Inet address to which this EndPoint is bound, or null - * if this EndPoint does not represent a network connection. + * @return The local Inet address to which this {@code EndPoint} is bound, or {@code null} + * if this {@code EndPoint} does not represent a network connection. */ InetSocketAddress getLocalAddress(); /** - * @return The remote Inet address to which this EndPoint is bound, or null - * if this EndPoint does not represent a network connection. + * @return The remote Inet address to which this {@code EndPoint} is bound, or {@code null} + * if this {@code EndPoint} does not represent a network connection. */ InetSocketAddress getRemoteAddress(); + /** + * @return whether this EndPoint is open + */ boolean isOpen(); + /** + * @return the epoch time in milliseconds when this EndPoint was created + */ long getCreatedTimeStamp(); /** @@ -177,7 +181,7 @@ public interface EndPoint extends Closeable * * @param buffer The buffer to fill. The position and limit are modified during the fill. After the * operation, the position is unchanged and the limit is increased to reflect the new data filled. - * @return an int value indicating the number of bytes + * @return an {@code int} value indicating the number of bytes * filled or -1 if EOF is read or the input is shutdown. * @throws IOException if the endpoint is closed. */ @@ -252,27 +256,27 @@ public interface EndPoint extends Closeable void write(Callback callback, ByteBuffer... buffers) throws WritePendingException; /** - * @return the {@link Connection} associated with this {@link EndPoint} + * @return the {@link Connection} associated with this EndPoint * @see #setConnection(Connection) */ Connection getConnection(); /** - * @param connection the {@link Connection} associated with this {@link EndPoint} + * @param connection the {@link Connection} associated with this EndPoint * @see #getConnection() * @see #upgrade(Connection) */ void setConnection(Connection connection); /** - *

    Callback method invoked when this {@link EndPoint} is opened.

    + *

    Callback method invoked when this EndPoint is opened.

    * * @see #onClose(Throwable) */ void onOpen(); /** - *

    Callback method invoked when this {@link EndPoint} is close.

    + *

    Callback method invoked when this {@link EndPoint} is closed.

    * * @param cause The reason for the close, or null if a normal close. * @see #onOpen() @@ -280,13 +284,18 @@ public interface EndPoint extends Closeable void onClose(Throwable cause); /** - * Upgrade connections. - * Close the old connection, update the endpoint and open the new connection. - * If the oldConnection is an instance of {@link Connection.UpgradeFrom} then - * a prefilled buffer is requested and passed to the newConnection if it is an instance - * of {@link Connection.UpgradeTo} + *

    Upgrades this EndPoint from the current connection to the given new connection.

    + *

    Closes the current connection, links this EndPoint to the new connection and + * then opens the new connection.

    + *

    If the current connection is an instance of {@link Connection.UpgradeFrom} then + * a buffer of unconsumed bytes is requested. + * If the buffer of unconsumed bytes is non-null and non-empty, then the new + * connection is tested: if it is an instance of {@link Connection.UpgradeTo}, then + * the unconsumed buffer is passed to the new connection; otherwise, an exception + * is thrown since there are unconsumed bytes that cannot be consumed by the new + * connection.

    * - * @param newConnection The connection to upgrade to + * @param newConnection the connection to upgrade to */ public void upgrade(Connection newConnection); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java index b904638d665..3892802a8f8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java @@ -92,6 +92,8 @@ public abstract class FillInterest /** * Call to signal that a read is now possible. + * + * @return whether the callback was notified that a read is now possible */ public boolean fillable() { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java index d61dce79115..4d85393d6ec 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java @@ -23,10 +23,13 @@ import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.LeakDetector; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@ManagedObject public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool { private static final Logger LOG = LoggerFactory.getLogger(LeakTrackingByteBufferPool.class); @@ -47,11 +50,11 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By } }; - private static final boolean NOISY = Boolean.getBoolean(LeakTrackingByteBufferPool.class.getName() + ".NOISY"); - private final ByteBufferPool delegate; - private final AtomicLong leakedReleases = new AtomicLong(0); private final AtomicLong leakedAcquires = new AtomicLong(0); + private final AtomicLong leakedReleases = new AtomicLong(0); + private final AtomicLong leakedRemoves = new AtomicLong(0); private final AtomicLong leaked = new AtomicLong(0); + private final ByteBufferPool delegate; public LeakTrackingByteBufferPool(ByteBufferPool delegate) { @@ -64,12 +67,12 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By public ByteBuffer acquire(int size, boolean direct) { ByteBuffer buffer = delegate.acquire(size, direct); - boolean leaked = leakDetector.acquired(buffer); - if (NOISY || !leaked) + boolean acquired = leakDetector.acquired(buffer); + if (!acquired) { leakedAcquires.incrementAndGet(); - LOG.info(String.format("ByteBuffer acquire %s leaked.acquired=%s", leakDetector.id(buffer), leaked ? "normal" : "LEAK"), - new Throwable("LeakStack.Acquire")); + if (LOG.isDebugEnabled()) + LOG.debug("ByteBuffer leaked acquire for id {}", leakDetector.id(buffer), new Throwable("acquire")); } return buffer; } @@ -79,16 +82,36 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By { if (buffer == null) return; - boolean leaked = leakDetector.released(buffer); - if (NOISY || !leaked) + boolean released = leakDetector.released(buffer); + if (!released) { leakedReleases.incrementAndGet(); - LOG.info(String.format("ByteBuffer release %s leaked.released=%s", leakDetector.id(buffer), leaked ? "normal" : "LEAK"), new Throwable( - "LeakStack.Release")); + if (LOG.isDebugEnabled()) + LOG.debug("ByteBuffer leaked release for id {}", leakDetector.id(buffer), new Throwable("release")); } delegate.release(buffer); } + @Override + public void remove(ByteBuffer buffer) + { + if (buffer == null) + return; + boolean released = leakDetector.released(buffer); + if (!released) + { + leakedRemoves.incrementAndGet(); + if (LOG.isDebugEnabled()) + LOG.debug("ByteBuffer leaked remove for id {}", leakDetector.id(buffer), new Throwable("remove")); + } + delegate.remove(buffer); + } + + /** + * Clears the tracking data returned by {@link #getLeakedAcquires()}, + * {@link #getLeakedReleases()}, {@link #getLeakedResources()}. + */ + @ManagedAttribute("Clears the tracking data") public void clearTracking() { leakedAcquires.set(0); @@ -96,24 +119,36 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By } /** - * @return count of BufferPool.acquire() calls that detected a leak + * @return count of ByteBufferPool.acquire() calls that detected a leak */ + @ManagedAttribute("The number of acquires that produced a leak") public long getLeakedAcquires() { return leakedAcquires.get(); } /** - * @return count of BufferPool.release() calls that detected a leak + * @return count of ByteBufferPool.release() calls that detected a leak */ + @ManagedAttribute("The number of releases that produced a leak") public long getLeakedReleases() { return leakedReleases.get(); } + /** + * @return count of ByteBufferPool.remove() calls that detected a leak + */ + @ManagedAttribute("The number of removes that produced a leak") + public long getLeakedRemoves() + { + return leakedRemoves.get(); + } + /** * @return count of resources that were acquired but not released */ + @ManagedAttribute("The number of resources that were leaked") public long getLeakedResources() { return leaked.get(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java index 75ff2f9af9b..4fe14e44cf3 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java @@ -28,6 +28,8 @@ import java.util.function.Function; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

    A ByteBuffer pool where ByteBuffers are held in queues that are held in a Map.

    @@ -38,6 +40,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject; @ManagedObject public class MappedByteBufferPool extends AbstractByteBufferPool { + private static final Logger LOG = LoggerFactory.getLogger(MappedByteBufferPool.class); + private final ConcurrentMap _directBuffers = new ConcurrentHashMap<>(); private final ConcurrentMap _heapBuffers = new ConcurrentHashMap<>(); private final Function _newBucket; @@ -127,7 +131,12 @@ public class MappedByteBufferPool extends AbstractByteBufferPool int capacity = buffer.capacity(); // Validate that this buffer is from this pool. - assert ((capacity % getCapacityFactor()) == 0); + if ((capacity % getCapacityFactor()) != 0) + { + if (LOG.isDebugEnabled()) + LOG.debug("ByteBuffer {} does not belong to this pool, discarding it", BufferUtil.toDetailString(buffer)); + return; + } int b = bucketFor(capacity); boolean direct = buffer.isDirect(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index a23e92e9e9f..25caea62ad8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -326,11 +326,8 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr @Override public void onUpgradeTo(ByteBuffer buffer) { - if (BufferUtil.hasContent(buffer)) - { - acquireEncryptedInput(); - BufferUtil.append(_encryptedInput, buffer); - } + acquireEncryptedInput(); + BufferUtil.append(_encryptedInput, buffer); } @Override diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java index 0f290c12800..0239fd16e0b 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Objects; import org.eclipse.jetty.io.ByteBufferPool.Bucket; +import org.eclipse.jetty.util.StringUtil; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -150,6 +151,17 @@ public class ArrayByteBufferPoolTest } } + @Test + public void testReleaseNonPooledBuffer() + { + ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(); + + // Release a few small non-pool buffers + bufferPool.release(ByteBuffer.wrap(StringUtil.getUtf8Bytes("Hello"))); + + assertEquals(0, bufferPool.getHeapByteBufferCount()); + } + @Test public void testMaxQueue() { diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java index 16e20928e78..bbe05db3541 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java @@ -35,7 +35,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; public class MappedByteBufferPoolTest { @@ -95,34 +94,15 @@ public class MappedByteBufferPoolTest assertTrue(buckets.isEmpty()); } - /** - * In a scenario where MappedByteBufferPool is being used improperly, - * such as releasing a buffer that wasn't created/acquired by the - * MappedByteBufferPool, an assertion is tested for. - */ @Test - public void testReleaseAssertion() + public void testReleaseNonPooledBuffer() { - int factor = 1024; - MappedByteBufferPool bufferPool = new MappedByteBufferPool(factor); + MappedByteBufferPool bufferPool = new MappedByteBufferPool(); - try - { - // Release a few small non-pool buffers - bufferPool.release(ByteBuffer.wrap(StringUtil.getUtf8Bytes("Hello"))); + // Release a few small non-pool buffers + bufferPool.release(ByteBuffer.wrap(StringUtil.getUtf8Bytes("Hello"))); - /* NOTES: - * - * 1) This test will pass on command line maven build, as its surefire setup uses "-ea" already. - * 2) In Eclipse, goto the "Run Configuration" for this test case. - * Select the "Arguments" tab, and make sure "-ea" is present in the text box titled "VM arguments" - */ - fail("Expected java.lang.AssertionError, do you have '-ea' JVM command line option enabled?"); - } - catch (java.lang.AssertionError e) - { - // Expected path. - } + assertEquals(0, bufferPool.getHeapByteBufferCount()); } @Test diff --git a/jetty-jaas/src/main/config/modules/jaas.mod b/jetty-jaas/src/main/config/modules/jaas.mod index d8c78b63869..4b8fff5b78b 100644 --- a/jetty-jaas/src/main/config/modules/jaas.mod +++ b/jetty-jaas/src/main/config/modules/jaas.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enable JAAS for deployed webapplications. diff --git a/jetty-jaspi/src/main/config/modules/jaspi.mod b/jetty-jaspi/src/main/config/modules/jaspi.mod index 372c00e07bd..46cb7d8b314 100644 --- a/jetty-jaspi/src/main/config/modules/jaspi.mod +++ b/jetty-jaspi/src/main/config/modules/jaspi.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enable JASPI authentication for deployed webapplications. diff --git a/jetty-jmx/src/main/config/modules/jmx-remote.mod b/jetty-jmx/src/main/config/modules/jmx-remote.mod index bf6b200471c..c3b64af94dc 100644 --- a/jetty-jmx/src/main/config/modules/jmx-remote.mod +++ b/jetty-jmx/src/main/config/modules/jmx-remote.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables remote RMI access to JMX diff --git a/jetty-jmx/src/main/config/modules/jmx.mod b/jetty-jmx/src/main/config/modules/jmx.mod index 969b5f167fb..183e29c7778 100644 --- a/jetty-jmx/src/main/config/modules/jmx.mod +++ b/jetty-jmx/src/main/config/modules/jmx.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables JMX instrumentation for server beans and diff --git a/jetty-jndi/src/main/config/modules/jndi.mod b/jetty-jndi/src/main/config/modules/jndi.mod index 989a37cc883..efa8d7ff318 100644 --- a/jetty-jndi/src/main/config/modules/jndi.mod +++ b/jetty-jndi/src/main/config/modules/jndi.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Adds the Jetty JNDI implementation to the classpath. diff --git a/jetty-maven-plugin/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod b/jetty-maven-plugin/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod index e451df43f4a..83d7fc98732 100644 --- a/jetty-maven-plugin/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod +++ b/jetty-maven-plugin/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables test setup diff --git a/jetty-maven-plugin/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod b/jetty-maven-plugin/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod index e451df43f4a..83d7fc98732 100644 --- a/jetty-maven-plugin/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod +++ b/jetty-maven-plugin/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables test setup diff --git a/jetty-maven-plugin/src/main/resources/maven.mod b/jetty-maven-plugin/src/main/resources/maven.mod index 0fd52cdeb80..be5322b055e 100644 --- a/jetty-maven-plugin/src/main/resources/maven.mod +++ b/jetty-maven-plugin/src/main/resources/maven.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables an unassembled maven webapp to run in a jetty distro diff --git a/jetty-memcached/jetty-memcached-sessions/src/main/config/modules/sessions/session-data-cache/xmemcached.mod b/jetty-memcached/jetty-memcached-sessions/src/main/config/modules/sessions/session-data-cache/xmemcached.mod index 017c004f4b9..8fc766168e6 100644 --- a/jetty-memcached/jetty-memcached-sessions/src/main/config/modules/sessions/session-data-cache/xmemcached.mod +++ b/jetty-memcached/jetty-memcached-sessions/src/main/config/modules/sessions/session-data-cache/xmemcached.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Memcache cache for SessionData diff --git a/jetty-nosql/src/main/config/modules/session-store-mongo.mod b/jetty-nosql/src/main/config/modules/session-store-mongo.mod index e3ffafdd42d..9999bc7785b 100644 --- a/jetty-nosql/src/main/config/modules/session-store-mongo.mod +++ b/jetty-nosql/src/main/config/modules/session-store-mongo.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Enables NoSql session management with a MongoDB driver. diff --git a/jetty-nosql/src/main/config/modules/sessions/mongo/address.mod b/jetty-nosql/src/main/config/modules/sessions/mongo/address.mod index 333877d8cb4..299c0898bee 100644 --- a/jetty-nosql/src/main/config/modules/sessions/mongo/address.mod +++ b/jetty-nosql/src/main/config/modules/sessions/mongo/address.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Server/port address connections for session storage diff --git a/jetty-nosql/src/main/config/modules/sessions/mongo/uri.mod b/jetty-nosql/src/main/config/modules/sessions/mongo/uri.mod index 813904e4f8f..8dd37d23086 100644 --- a/jetty-nosql/src/main/config/modules/sessions/mongo/uri.mod +++ b/jetty-nosql/src/main/config/modules/sessions/mongo/uri.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] MongoURI connections for session storage diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod index 77ea5c35e6e..fde0b9882e1 100644 --- a/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-openid/src/main/config/modules/openid.mod @@ -1,4 +1,4 @@ -DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] Adds OpenId Connect authentication. diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index 28a8ee7bb65..c5680e70a2c 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -56,77 +56,73 @@ org.eclipse.jetty.osgi.boot !org.eclipse.jetty.osgi.boot.* - - org.eclipse.jdt.*;resolution:=optional, - org.eclipse.jdt.core.compiler.*;resolution:=optional, - com.sun.el;resolution:=optional, - com.sun.el.lang;resolution:=optional, - com.sun.el.parser;resolution:=optional, - com.sun.el.util;resolution:=optional, - javax.el;version="[3.0,3.1)", - javax.servlet;version="[3.1,4.1)", - javax.servlet.resources;version="[3.1,4.1)", - javax.servlet.jsp.resources;version="[2.3,4.1)", - javax.servlet.jsp;version="[2.3,2.4.1)", - javax.servlet.jsp.el;version="[2.3,2.4.1)", - javax.servlet.jsp.tagext;version="[2.3,2.4.1)", - javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional, - javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, - javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, - javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional, - org.apache.el;version="[8.0.23,10)";resolution:=optional, - org.apache.el.lang;version="[8.0.23,10)";resolution:=optional, - org.apache.el.stream;version="[8.0.23,10)";resolution:=optional, - org.apache.el.util;version="[8.0.23,10)";resolution:=optional, - org.apache.el.parser;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional, - org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional, - org.apache.taglibs.standard;version="1.2";resolution:=optional, - org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional, - org.apache.taglibs.standard.functions;version="1.2";resolution:=optional, - org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional, - org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional, - org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional, - org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional, - org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional, - org.apache.taglibs.standard.resources;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tei;version="1.2";resolution:=optional, - org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional, - org.apache.tomcat;version="[8.0.23,10)";resolution:=optional, - org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional, - org.slf4j.*, - org.osgi.*, - org.xml.*;resolution:=optional, - org.xml.sax.*;resolution:=optional, - javax.xml.*;resolution:=optional, - org.w3c.dom;resolution:=optional, - org.w3c.dom.ls;resolution:=optional, - javax.xml.parser;resolution:=optional + org.eclipse.jdt.*;resolution:=optional, + org.eclipse.jdt.core.compiler.*;resolution:=optional, + com.sun.el;resolution:=optional, + com.sun.el.lang;resolution:=optional, + com.sun.el.parser;resolution:=optional, + com.sun.el.util;resolution:=optional, + javax.el;version="[3.0,3.1)", + javax.servlet;version="[$(version;==;${servlet.api.version}),$(version;+;${servlet.api.version}))", + javax.servlet.resources;version="[$(version;==;${servlet.api.version}),$(version;+;${servlet.api.version}))", + javax.servlet.jsp.resources;version="[2.3,4.1)", + javax.servlet.jsp;version="[2.3,2.4.1)", + javax.servlet.jsp.el;version="[2.3,2.4.1)", + javax.servlet.jsp.tagext;version="[2.3,2.4.1)", + javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional, + org.apache.el;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.el.lang;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.el.stream;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.el.util;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.el.parser;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.compiler;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.compiler.tagplugin;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.runtime;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.security;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.servlet;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.tagplugins.jstl;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.util;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.jasper.xmlparser;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.apache.taglibs.standard;version="1.2";resolution:=optional, + org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional, + org.apache.taglibs.standard.functions;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional, + org.apache.taglibs.standard.resources;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tei;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional, + org.apache.tomcat;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))";resolution:=optional, + org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional, + org.osgi.*, + org.xml.*;resolution:=optional, + org.xml.sax.*;resolution:=optional, + javax.xml.*;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.w3c.dom.ls;resolution:=optional, + javax.xml.parser;resolution:=optional - - org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))", - org.apache.jasper.*;version="8.0.23", - org.apache.el.*;version="8.0.23" - + org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))", + org.apache.jasper.*;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))", + org.apache.el.*;version="[$(version;===;${jsp.version}),$(version;+;${jsp.version}))" diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 1bfdd01b97c..e116132abb6 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -70,8 +70,30 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator - org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))" - javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, javax.mail.search;version="1.4.0";resolution:=optional, javax.mail.util;version="1.4.0";resolution:=optional, javax.servlet;version="[3.1,4.1)", javax.servlet.http;version="[3.1,4.1)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, org.objectweb.asm;version="5";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.xml.sax, org.xml.sax.helpers, org.eclipse.jetty.annotations;resolution:=optional, * + org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;+;${parsedVersion.osgiVersion}))" + javax.mail;version="1.4.0";resolution:=optional, + javax.mail.event;version="1.4.0";resolution:=optional, + javax.mail.internet;version="1.4.0";resolution:=optional, + javax.mail.search;version="1.4.0";resolution:=optional, + javax.mail.util;version="1.4.0";resolution:=optional, + javax.servlet;version="[$(version;==;${servlet.api.version}),$(version;+;${servlet.api.version}))", + javax.servlet.http;version="[$(version;==;${servlet.api.version}),$(version;+;${servlet.api.version}))", + javax.transaction;version="1.1.0";resolution:=optional, + javax.transaction.xa;version="1.1.0";resolution:=optional, + org.objectweb.asm;version="$(version;=;${asm.version})";resolution:=optional, + org.osgi.framework, + org.osgi.service.cm;version="1.2.0", + org.osgi.service.packageadmin, + org.osgi.service.startlevel;version="1.0.0", + org.osgi.service.url;version="1.0.0", + org.osgi.util.tracker;version="1.3.0", + org.slf4j;resolution:=optional, + org.slf4j.spi;resolution:=optional, + org.slf4j.helpers;resolution:=optional, + org.xml.sax, + org.xml.sax.helpers, + org.eclipse.jetty.annotations;resolution:=optional, + * osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java index a28bef28592..f4b78fc8f3b 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java @@ -50,7 +50,7 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa public AnnotationParser(int javaPlatform) { - super(javaPlatform, Opcodes.ASM7); + super(javaPlatform); } /** @@ -60,7 +60,7 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa * @return the resource for the bundle * @throws Exception if unable to create the resource reference */ - protected Resource indexBundle(Bundle bundle) throws Exception + public Resource indexBundle(Bundle bundle) throws Exception { File bundleFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle); Resource resource = Resource.newResource(bundleFile.toURI()); @@ -114,7 +114,7 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa } } - protected void parse(Set handlers, Bundle bundle) + public void parse(Set handlers, Bundle bundle) throws Exception { URI uri = _bundleToUri.get(bundle); @@ -211,10 +211,17 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa //or the bundle classpath wasn't simply ".", so skip it continue; } + + if (!isValidClassFileName(name)) + { + continue; //eg skip module-info.class + } + //transform into a classname to pass to the resolver String shortName = StringUtil.replace(name, '/', '.').substring(0, name.length() - 6); addParsedClass(shortName, getResource(bundle)); + try (InputStream classInputStream = classUrl.openStream()) { scanClass(handlers, getResource(bundle), classInputStream); diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 49ce22029ee..7cc1a5a4d3e 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -77,7 +77,7 @@ javax.servlet;version="[3.1,4.1)", javax.servlet.resources;version="[3.1,4.1)", org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, * - org.eclipse.jetty.*;version="[9.1,10.0)" + org.eclipse.jetty.*;version="[$(version;==;${parsedVersion.osgiVersion}),$(version;+;${parsedVersion.osgiVersion}))" diff --git a/jetty-osgi/test-jetty-osgi-server/pom.xml b/jetty-osgi/test-jetty-osgi-server/pom.xml index ccf17f5a981..5aa756067ca 100644 --- a/jetty-osgi/test-jetty-osgi-server/pom.xml +++ b/jetty-osgi/test-jetty-osgi-server/pom.xml @@ -69,7 +69,7 @@ javax.servlet;version="[3.1,4.1)", javax.servlet.resources;version="[3.1,4.1)", org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.o", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, * - org.eclipse.jetty.*;version="[9.1,10.0)" + org.eclipse.jetty.*;version="[$(version;==;${parsedVersion.osgiVersion}),$(version;+;${parsedVersion.osgiVersion}))" diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 04c634c8e5d..0983f62033b 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -73,7 +73,6 @@ org.ops4j.pax.tinybundles tinybundles 3.0.0 - test biz.aQute.bnd diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiAnnotationParser.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiAnnotationParser.java new file mode 100644 index 00000000000..a33f8ce7354 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiAnnotationParser.java @@ -0,0 +1,94 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.osgi.test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Inject; + +import aQute.bnd.osgi.Constants; +import org.eclipse.jetty.annotations.ClassInheritanceHandler; +import org.eclipse.jetty.osgi.annotations.AnnotationParser; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.CoreOptions; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.tinybundles.core.TinyBundle; +import org.ops4j.pax.tinybundles.core.TinyBundles; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +import static org.junit.Assert.assertTrue; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +/** + * TestJettyOSGiAnnotationParser + * + */ + +@RunWith(PaxExam.class) +public class TestJettyOSGiAnnotationParser +{ + @Inject + BundleContext bundleContext = null; + + @Configuration + public static Option[] configure() throws IOException + { + ArrayList