diff --git a/VERSION.txt b/VERSION.txt index aa06e5e5d2e..660c2d92606 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,5 +1,101 @@ jetty-10.0.0-SNAPSHOT +jetty-9.4.7.v20170914 - 14 September 2017 + + 215 Consider native ALPN/SSL provider + + 487 JDK 9 build compatibility + + 1116 Support empty HTTP header values + + 1200 Use PathWatcher in DeploymentManager + + 1357 RolloverFileOutputStream: No rollout performed at midnight + + 1416 GzipHandler generated ETag suffix has problems with If-Match header + logic + + 1468 Configure PKIX Revocation Checker for SslContextFactory + + 1469 RolloverFileOutputStream: IllegalStateException Task already scheduled + + 1498 Add JRTResource to support future Java 9 classloader behaviors + + 1499 ClasspathPattern needs MODULE ruleset to support future Java 9 + classloader behaviors + + 1503 IPv6 address needs normalization (without brackets) in + ForwardedRequestCustomizer + + 1507 RolloverFileOutputStream: Negative delay Timer.schedule exception + + 1513 RolloverFileOutputStream: can't handle multiple instances + + 1515 Improved RollOverFileOutputStream removeOldFiles() behavior + + 1520 PropertyUserStore should extract packed config file + + 1556 Remove a timing channel in Password matching + + 1571 Support Hazelcast session management in 9.4 + + 1590 Improve RolloverFileOutputStream functionality with multiple TimeZones + + 1591 JDBCSessionDataStore doesn't work with root context on Oracle DB + + 1592 CompressedContentFormat.tagEquals() - incorrect comparison of entity + tag hashes + + 1595 HTTP/2: Avoid sending unnecessary stream WINDOW_UPDATE frames + + 1600 Update jndi.mod and plus.mod + + 1603 WebSocketServerFactory NPE in toString() + + 1604 WebSocketContainer stop needs improvement + + 1605 ContainerProvider.getWebSocketContainer() behavior is not to spec + + 1618 AsyncContext.dispatch() does not use raw/encoded URI + + 1622 HeaderFilter doesn't work if the response has been committed + + 1623 JettyRunMojo use dependencies from reactor (outputdirectory) + + 1625 Support new IANA declared Websocket Close Status Codes + + 1637 Thread per connection retained in HTTP/2 + + 1638 Add it test for Maven Plugin + + 1642 Using RewriteHandler with AsyncContext.dispatch() and + HttpServletRequestWrapper not possible + + 1643 ProxyServlet always uses default number of selector threads - + constructor should allow to overwrite the default. + + 1645 NotSerializableException: DoSFilter when using Non-Clustered Session + Management: File System + + 1655 Improve extensibility of ServerConnector + + 1656 Improve configurability of ConnectionPools + + 1661 AbstractProxyServlet onProxyResponseFailure Error + + 1662 NPE with WebSocket Compress Extensions + + 1664 IPAccessHandler CIDR IP range check is incorrect + + 1671 Asymmetric usage of trailers in MetaData.Request + + 1675 Session id should not be logged with INFO level in AbstractSessionCache + + 1679 DeploymentManagerMBean not usable through JMX + + 1682 Jetty-WarFragmentFolderPath directive has no effect in eclipse runtime + mode except for the first launch + + 1685 Update ALPN support for Java 8u141 + + 1687 HTTP2: Correcting missing callback notification when channel not found + + 1692 Annotation scanning should ignore `module-info.class` files + + 1698 Missing WWW-Authenticate from SpnegoAuthenticator when other + Authorization header provided + + 1702 Update ALPN support for Java 8u144 + + 1703 Improve HttpInput failure logging + + 1706 Log Implementation ignored when executing under OSGi + + 1709 SpnegoAuthenticator improperly handling case-insensitive Negotiate + header + + 1713 Do not over allocate selectors for small thread pools + + 1715 Standardise properties and ids in jetty XML files + + 1717 DoSFilter getRateTracker IP/Port loadId minor improvement + + 1718 QueuedThreadPool not exposed on JMX + + 1719 HTTP/2: Improve handling of queued requests + + 1721 Async I/O POST fails with big files + + 1724 Add dependency on jetty-annotations for apache-jsp + + 1732 Allow pause accepting new connections during high load + + 1737 DefaultServlet wrong welcome dispatcher using non-root URL path + + 1738 jetty-bom fails oss.sonatype.org validation + + 1741 Java 9 javadoc failure in build + + 1749 Dump HttpDestination exchange queue + + 1750 PoolingHttpDestination creates ConnectionPool twice + + 1759 HTTP/2: producer can block in onReset + + 1766 JettyClientContainerProvider does not actually use common objects + correctly + + 1789 PropertyUserStoreTest failures in Windows + + 1790 HTTP/2: 100% CPU usage seen during close/shutdown of endpoint + + 1792 Accept ISO-8859-1 characters in response reason + + 1794 Config properties typos in session-store-cache.mod + + 1795 Fix session id manager workerName + + 1796 ReservedThreadExecutor defaulting to capacity=1 only + + 1797 JEP 238 - Multi-Release JAR files break bytecode scanning + + 1798 JMXify EatWhatYouKill + + 1804 Make EndPoint creation and destroy a non-blocking task + + 1805 ReservedThreadExecutor should start ReservedThreads lazily + + 1809 NPE: StandardDescriptorProcessor.visitSecurityConstraint() with null/no + security manager + + 1814 Move JavaVersion to jetty-util for future Java 9 support requirements + + 1816 HttpClientTest.testClientCannotValidateServerCertificate() hangs with + JDK 9 + + 475546 ClosedChannelException when connection to HTTPS over HTTP proxy + with CONNECT + jetty-9.4.6.v20170531 - 31 May 2017 + 523 TLS close behaviour breaking session resumption + 1108 Please improve logging in SslContextFactory when there are no approved @@ -28,26 +124,59 @@ jetty-9.4.6.v20170531 - 31 May 2017 + 1569 Allow setting of maxBinaryMessageSize to 0 in WebSocketPolicy + 1579 NPE in Quoted Quality CSV -jetty-9.2.22.v20170606 - 06 June 2017 - + 920 no main manifest attribute, in jetty-runner-9.2.19.v20160908.jar - + 1108 Please improve logging in SslContextFactory when there are no approved - cipher suites +jetty-9.3.21.v20170918 - 18 September 2017 + + 487 JDK 9 build compatibility + + 1116 Support empty HTTP header values + 1357 RolloverFileOutputStream: No rollout performed at midnight - + 1469 IllegalStateException in RolloverFileOutputStream - + 1507 Negative delay Timer.schedule exception due to mismatched local and - _logTimeZone values - + 1532 RolloverFileOutputStream can't handle multiple instances - + 1523 Update ALPN support for Java 8u131 - + 1556 A timing channel in Password.java - + 1590 RolloverFileOutputStream not functioning in Jetty 9.2.21+ + + 1469 RolloverFileOutputStream: IllegalStateException Task already scheduled + + 1507 RolloverFileOutputStream: Negative delay Timer.schedule exception + + 1513 RolloverFileOutputStream: can't handle multiple instances + + 1515 Improved RollOverFileOutputStream removeOldFiles() behavior + + 1556 Remove a timing channel in Password matching + + 1590 Improve RolloverFileOutputStream functionality with multiple TimeZones + + 1655 Improve extensibility of ServerConnector + + 1661 AbstractProxyServlet onProxyResponseFailure Error + + 1664 IPAccessHandler CIDR IP range check is incorrect + + 1685 Update ALPN support for Java 8u141 + + 1687 HTTP2: Correcting missing callback notification when channel not found + + 1702 Update ALPN support for Java 8u144 + + 1703 Improve HttpInput failure logging + + 1719 HTTP/2: Improve handling of queued requests + + 1741 Java 9 javadoc failure in build + + 1749 Dump HttpDestination exchange queue + + 1750 PoolingHttpDestination creates ConnectionPool twice + + 1759 HTTP/2: producer can block in onReset + + 1790 HTTP/2: 100% CPU usage seen during close/shutdown of endpoint + + 475546 ClosedChannelException when connection to HTTPS over HTTP proxy + with CONNECT -jetty-9.3.20.v20170531 - 31 May 2017 +jetty-9.4.6.v20170531 - 31 May 2017 + 523 TLS close behaviour breaking session resumption + 1108 Please improve logging in SslContextFactory when there are no approved cipher suites + + 1505 Adding jetty.base.uri and jetty.home.uri + + 1514 websocket dump badly formatted + + 1516 Delay starting of WebSocketClient until an attempt to connect is made + + 1520 PropertyUserStore should extract packed config file + + 1526 MongoSessionDataStore old session scavenging is broken due to the + missing $ sign in "and" operation + 1527 Jetty BOM should not depend on jetty-parent + + 1528 Internal HttpClient usages should have common configurable technique + + 1536 Jetty BOM should include more artifacts + + 1538 NPE in Response.putHeaders + + 1539 JarFileResource mishandles paths with spaces + + 1544 Disabling JSR-356 doesn't indicate context it was disabled for + + 1546 Improve handling of quotes in cookies + + 1553 X509.isCertSign() can throw ArrayIndexOutOfBoundsException on + non-standard implementations + 1556 A timing channel in Password.java + + 1558 When creating WebAppContext without session-config and with NO_SESSIONS + throws NPE + 1567 XmlConfiguration will start the same object multiple times + + 1568 ServletUpgradeRequest mangles query strings containing percent-escapes + by re-escaping them + + 1569 Allow setting of maxBinaryMessageSize to 0 in WebSocketPolicy + + 1579 NPE in Quoted Quality CSV jetty-9.4.5.v20170502 - 02 May 2017 + 304 Review dead code - StringUtil.sidBytesToString @@ -81,6 +210,27 @@ jetty-9.4.5.v20170502 - 02 May 2017 + 1521 Prevent copy of jetty jars to lib/gcloud + 1523 Update ALPN support for Java 8u131 +jetty-9.3.20.v20170531 - 31 May 2017 + + 523 TLS close behaviour breaking session resumption + + 1108 Please improve logging in SslContextFactory when there are no approved + cipher suites + + 1527 Jetty BOM should not depend on jetty-parent + + 1556 A timing channel in Password.java + + 1567 XmlConfiguration will start the same object multiple times + +jetty-9.2.22.v20170606 - 06 June 2017 + + 920 no main manifest attribute, in jetty-runner-9.2.19.v20160908.jar + + 1108 Please improve logging in SslContextFactory when there are no approved + cipher suites + + 1357 RolloverFileOutputStream: No rollout performed at midnight + + 1469 IllegalStateException in RolloverFileOutputStream + + 1507 Negative delay Timer.schedule exception due to mismatched local and + _logTimeZone values + + 1532 RolloverFileOutputStream can't handle multiple instances + + 1523 Update ALPN support for Java 8u131 + + 1556 A timing channel in Password.java + + 1590 RolloverFileOutputStream not functioning in Jetty 9.2.21+ + jetty-9.3.19.v20170502 - 02 May 2017 + 877 Programmatic servlet mappings cannot override mappings from webdefault.xml using quickstart diff --git a/apache-jstl/src/test/java/org/eclipse/jetty/jstl/JstlTest.java b/apache-jstl/src/test/java/org/eclipse/jetty/jstl/JstlTest.java index 7c5d903a79d..1af557f1eae 100644 --- a/apache-jstl/src/test/java/org/eclipse/jetty/jstl/JstlTest.java +++ b/apache-jstl/src/test/java/org/eclipse/jetty/jstl/JstlTest.java @@ -18,11 +18,6 @@ package org.eclipse.jetty.jstl; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -37,15 +32,22 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.JAR; +import org.eclipse.jetty.toolchain.test.JDK; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + public class JstlTest { private static Server server; @@ -54,6 +56,8 @@ public class JstlTest @BeforeClass public static void startServer() throws Exception { + Assume.assumeFalse(JDK.IS_9); + // Setup Server server = new Server(); ServerConnector connector = new ServerConnector(server); @@ -98,7 +102,8 @@ public class JstlTest @AfterClass public static void stopServer() throws Exception { - server.stop(); + if (server != null) + server.stop(); } @Test diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index 1f820dc616e..d9b4a30a9a3 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -15,13 +15,6 @@ ${project.groupId}.embedded - - - com.google.guava - guava - 18.0 - - org.eclipse.jetty jetty-util-ajax @@ -82,6 +75,16 @@ jetty-alpn-server ${project.version} + + org.eclipse.jetty + jetty-alpn-openjdk8-server + ${project.version} + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${project.version} + org.eclipse.jetty jetty-annotations @@ -138,4 +141,20 @@ test + + + + jdk9 + + [1.9,) + + + + org.eclipse.jetty + jetty-alpn-java-server + ${project.version} + + + + diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java index d75498b0bac..5a4a068c4bc 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java @@ -19,8 +19,10 @@ package org.eclipse.jetty.embedded; +import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; +import java.nio.file.Files; import java.util.Date; import java.util.EnumSet; @@ -64,7 +66,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; public class Http2Server { public static void main(String... args) throws Exception - { + { Server server = new Server(); MBeanContainer mbContainer = new MBeanContainer( @@ -72,7 +74,10 @@ public class Http2Server server.addBean(mbContainer); ServletContextHandler context = new ServletContextHandler(server, "/",ServletContextHandler.SESSIONS); - context.setResourceBase("src/main/resources/docroot"); + String docroot = "src/main/resources/docroot"; + if (!new File(docroot).exists()) + docroot = "examples/embedded/src/main/resources/docroot"; + context.setResourceBase(docroot); context.addFilter(PushCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST)); // context.addFilter(PushSessionCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST)); context.addFilter(PushedTilesFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST)); @@ -94,11 +99,14 @@ public class Http2Server // SSL Context Factory for HTTPS and HTTP/2 String jetty_distro = System.getProperty("jetty.distro","../../jetty-distribution/target/distribution"); + if (!new File(jetty_distro).exists()) + jetty_distro = "jetty-distribution/target/distribution"; SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setKeyStorePath(jetty_distro + "/demo-base/etc/keystore"); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); + // sslContextFactory.setProvider("Conscrypt"); // HTTPS Configuration HttpConfiguration https_config = new HttpConfiguration(http_config); @@ -122,7 +130,6 @@ public class Http2Server ALPN.debug=false; server.start(); - //server.dumpStdErr(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java index 931921402a2..127778a3310 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java @@ -20,12 +20,8 @@ package org.eclipse.jetty.embedded; import java.io.File; import java.io.FileNotFoundException; -import java.lang.reflect.Method; import java.security.Provider; import java.security.Security; -import java.util.function.BiFunction; - -import javax.net.ssl.SSLEngine; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Connector; @@ -43,7 +39,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; public class ManyConnectors { public static void main( String[] args ) throws Exception - { + { // Since this example shows off SSL configuration, we need a keystore // with the appropriate key. These lookup of jetty.home is purely a hack // to get access to a keystore that we use in many unit tests and should @@ -97,7 +93,6 @@ public class ManyConnectors sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath()); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); - sslContextFactory.setProvider("Conscrypt"); // HTTPS Configuration // A new HttpConfiguration object is needed for the next connector and @@ -137,18 +132,6 @@ public class ManyConnectors // Start the server server.start(); - SSLEngine engine = sslContextFactory.newSSLEngine(); - System.err.println(engine); - System.err.println(engine.getClass()); - for (Method m : engine.getClass().getMethods()) - { - System.err.println(m); - } - - - - - server.join(); } } diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties index 7bffba83dbb..810f9896c7e 100644 --- a/examples/embedded/src/main/resources/jetty-logging.properties +++ b/examples/embedded/src/main/resources/jetty-logging.properties @@ -8,3 +8,4 @@ #org.eclipse.jetty.io.ssl.LEVEL=DEBUG #org.eclipse.jetty.server.LEVEL=DEBUG #org.eclipse.jetty.servlets.LEVEL=DEBUG +#org.eclipse.jetty.alpn.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml index 7c406851c21..4766cc66123 100644 --- a/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-alpn/jetty-alpn-client/pom.xml @@ -50,12 +50,6 @@ jetty-io ${project.version} - - org.eclipse.jetty.alpn - alpn-api - ${alpn.api.version} - provided - org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java index cc31f3b9d50..b224c86e05d 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java @@ -24,14 +24,13 @@ import java.util.concurrent.Executor; import javax.net.ssl.SSLEngine; -import org.eclipse.jetty.alpn.ALPN; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.NegotiatingClientConnection; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class ALPNClientConnection extends NegotiatingClientConnection implements ALPN.ClientProvider +public class ALPNClientConnection extends NegotiatingClientConnection { private static final Logger LOG = Log.getLogger(ALPNClientConnection.class); @@ -41,41 +40,18 @@ public class ALPNClientConnection extends NegotiatingClientConnection implements { super(endPoint, executor, sslEngine, connectionFactory, context); this.protocols = protocols; - ALPN.put(sslEngine, this); } - @Override - public void unsupported() - { - ALPN.remove(getSSLEngine()); - completed(); - } - - @Override - public List protocols() + public List getProtocols() { return protocols; } - @Override public void selected(String protocol) { - if (protocols.contains(protocol)) - { - ALPN.remove(getSSLEngine()); - completed(); - } - else - { - LOG.info("Could not negotiate protocol: server [{}] - client {}", protocol, protocols); + if (protocol==null || !protocols.contains(protocol)) close(); - } - } - - @Override - public void close() - { - ALPN.remove(getSSLEngine()); - super.close(); + else + super.completed(); } } diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java index 26657b5c490..1fc8b560956 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.alpn.client; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,17 +32,19 @@ import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.NegotiatingClientConnectionFactory; -import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.ALPNProcessor.Client; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.io.ssl.SslHandshakeListener; -import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory implements SslHandshakeListener { - private final SslHandshakeListener alpnListener = new ALPNListener(); + private static final Logger LOG = Log.getLogger(ALPNClientConnectionFactory.class); + + private final List processors = new ArrayList<>(); private final Executor executor; private final List protocols; - private final ALPNProcessor.Client alpnProcessor; public ALPNClientConnectionFactory(Executor executor, ClientConnectionFactory connectionFactory, List protocols) { @@ -50,39 +53,64 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact throw new IllegalArgumentException("ALPN protocol list cannot be empty"); this.executor = executor; this.protocols = protocols; - Iterator processors = ServiceLoader.load(ALPNProcessor.Client.class).iterator(); - alpnProcessor = processors.hasNext() ? processors.next() : ALPNProcessor.Client.NOOP; - } - public ALPNProcessor.Client getALPNProcessor() - { - return alpnProcessor; + IllegalStateException failure = new IllegalStateException("No Client ALPNProcessors!"); + + // Use a for loop on iterator so load exceptions can be caught and ignored + for (Iterator i = ServiceLoader.load(Client.class).iterator(); i.hasNext();) + { + Client processor; + try + { + processor = i.next(); + } + catch(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + failure.addSuppressed(x); + continue; + } + + try + { + processor.init(); + processors.add(processor); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Could not initialize " + processor, x); + failure.addSuppressed(x); + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("protocols: {}", protocols); + LOG.debug("processors: {}", processors); + } + + if (processors.isEmpty()) + throw failure; } @Override public Connection newConnection(EndPoint endPoint, Map context) throws IOException { - SSLEngine sslEngine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY); - getALPNProcessor().configure(sslEngine, protocols); - ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY); - // Method addBean() has set semantic, so the listener is added only once. - connector.addBean(alpnListener); - ALPNClientConnection connection = new ALPNClientConnection(endPoint, executor, getClientConnectionFactory(), - sslEngine, context, protocols); - return customize(connection, context); - } - - private class ALPNListener implements SslHandshakeListener - { - @Override - public void handshakeSucceeded(Event event) - { - getALPNProcessor().process(event.getSSLEngine()); - } - - @Override - public void handshakeFailed(Event event, Throwable failure) + SSLEngine engine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY); + for (Client processor : processors) { + if (processor.appliesTo(engine)) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} for {} on {}", processor, engine, endPoint); + ALPNClientConnection connection = new ALPNClientConnection(endPoint, executor, getClientConnectionFactory(), + engine, context, protocols); + processor.configure(engine, connection); + return customize(connection, context); + } } + throw new IllegalStateException("No ALPNProcessor for " + engine); } } diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml new file mode 100644 index 00000000000..a6866b10edc --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -0,0 +1,39 @@ + + + + + org.eclipse.jetty + jetty-alpn-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + jetty-alpn-conscrypt-client + Jetty :: ALPN :: Conscrypt Client Implementation + + + ${project.groupId}.alpn.java.client + 1.0.0.RC10 + + + + + org.conscrypt + conscrypt-openjdk-uber + ${conscrypt.version} + + + org.eclipse.jetty + jetty-alpn-client + ${project.version} + + + org.eclipse.jetty.http2 + http2-client + ${project.version} + test + + + diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/org/eclipse/jetty/alpn/conscrypt/client/ConscryptClientALPNProcessor.java b/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/org/eclipse/jetty/alpn/conscrypt/client/ConscryptClientALPNProcessor.java new file mode 100644 index 00000000000..20f8f146ff7 --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/org/eclipse/jetty/alpn/conscrypt/client/ConscryptClientALPNProcessor.java @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.conscrypt.client; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.security.Security; + +import javax.net.ssl.SSLEngine; + +import org.conscrypt.OpenSSLProvider; +import org.eclipse.jetty.alpn.client.ALPNClientConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class ConscryptClientALPNProcessor implements ALPNProcessor.Client +{ + private static final Logger LOG = Log.getLogger(ConscryptClientALPNProcessor.class); + + @Override + public void init() + { + if (Security.getProvider("Conscrypt")==null) + { + Security.addProvider(new OpenSSLProvider()); + if (LOG.isDebugEnabled()) + LOG.debug("Added Conscrypt provider"); + } + } + + @Override + public boolean appliesTo(SSLEngine sslEngine) + { + return sslEngine.getClass().getName().startsWith("org.conscrypt."); + } + + @Override + public void configure(SSLEngine sslEngine, Connection connection) + { + try + { + Method setAlpnProtocols = sslEngine.getClass().getDeclaredMethod("setAlpnProtocols", String[].class); + setAlpnProtocols.setAccessible(true); + ALPNClientConnection alpn = (ALPNClientConnection)connection; + String[] protocols = alpn.getProtocols().toArray(new String[0]); + setAlpnProtocols.invoke(sslEngine, (Object)protocols); + ((SslConnection.DecryptedEndPoint)connection.getEndPoint()).getSslConnection() + .addHandshakeListener(new ALPNListener(alpn)); + } + catch (RuntimeException x) + { + throw x; + } + catch (Exception x) + { + throw new RuntimeException(x); + } + } + + private final class ALPNListener implements SslHandshakeListener + { + private final ALPNClientConnection alpnConnection; + + private ALPNListener(ALPNClientConnection connection) + { + alpnConnection = connection; + } + + @Override + public void handshakeSucceeded(Event event) + { + try + { + SSLEngine sslEngine = alpnConnection.getSSLEngine(); + Method method = sslEngine.getClass().getDeclaredMethod("getAlpnSelectedProtocol"); + method.setAccessible(true); + String protocol = new String((byte[])method.invoke(sslEngine), StandardCharsets.US_ASCII); + alpnConnection.selected(protocol); + } + catch (Throwable e) + { + alpnConnection.selected(null); + LOG.warn(e); + } + } + } +} diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client b/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client new file mode 100644 index 00000000000..bfb6cd8b55f --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client @@ -0,0 +1 @@ +org.eclipse.jetty.alpn.conscrypt.client.ConscryptClientALPNProcessor diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java b/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java new file mode 100644 index 00000000000..576d544f482 --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java @@ -0,0 +1,89 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.java.client; + +import java.net.InetSocketAddress; +import java.security.Security; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.conscrypt.OpenSSLProvider; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class ConscryptHTTP2Client +{ + public static void main(String[] args) throws Exception + { + Security.addProvider(new OpenSSLProvider()); + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setProvider("Conscrypt"); + HTTP2Client client = new HTTP2Client(); + client.addBean(sslContextFactory); + client.start(); + + String host = "webtide.com"; + int port = 443; + + FuturePromise sessionPromise = new FuturePromise<>(); + client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); + Session session = sessionPromise.get(5, TimeUnit.SECONDS); + + HttpFields requestFields = new HttpFields(); + requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION); + MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields); + HeadersFrame headersFrame = new HeadersFrame(metaData, null, true); + CountDownLatch latch = new CountDownLatch(1); + session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + System.err.println(frame); + if (frame.isEndStream()) + latch.countDown(); + } + + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + System.err.println(frame); + callback.succeeded(); + if (frame.isEndStream()) + latch.countDown(); + } + }); + + latch.await(5, TimeUnit.SECONDS); + + client.stop(); + } +} diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..d96a696f82e --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties @@ -0,0 +1,2 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml new file mode 100644 index 00000000000..7fa067bbbb5 --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -0,0 +1,43 @@ + + + + org.eclipse.jetty + jetty-alpn-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + jetty-alpn-conscrypt-server + Jetty :: ALPN :: Conscrypt Server Implementation + + + ${project.groupId}.alpn.conscrypt.server + 1.0.0.RC10 + + + + + org.conscrypt + conscrypt-openjdk-uber + ${conscrypt.version} + + + org.eclipse.jetty + jetty-alpn-server + ${project.version} + + + org.eclipse.jetty + jetty-io + ${project.version} + + + + org.eclipse.jetty.http2 + http2-server + ${project.version} + test + + + diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptServerALPNProcessor.java b/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptServerALPNProcessor.java new file mode 100644 index 00000000000..0d8acbbf874 --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptServerALPNProcessor.java @@ -0,0 +1,115 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.conscrypt.server; + +import java.lang.reflect.Method; +import java.security.Security; +import java.util.List; +import java.util.function.BiFunction; + +import javax.net.ssl.SSLEngine; + +import org.conscrypt.OpenSSLProvider; +import org.eclipse.jetty.alpn.server.ALPNServerConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class ConscryptServerALPNProcessor implements ALPNProcessor.Server +{ + private static final Logger LOG = Log.getLogger(ConscryptServerALPNProcessor.class); + + @Override + public void init() + { + if (Security.getProvider("Conscrypt")==null) + { + Security.addProvider(new OpenSSLProvider()); + if (LOG.isDebugEnabled()) + LOG.debug("Added Conscrypt provider"); + } + } + + @Override + public boolean appliesTo(SSLEngine sslEngine) + { + return sslEngine.getClass().getName().startsWith("org.conscrypt."); + } + + @Override + public void configure(SSLEngine sslEngine,Connection connection) + { + try + { + Method method = sslEngine.getClass().getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class); + method.setAccessible(true); + method.invoke(sslEngine,new ALPNCallback((ALPNServerConnection)connection)); + } + catch (RuntimeException x) + { + throw x; + } + catch (Exception x) + { + throw new RuntimeException(x); + } + } + + private final class ALPNCallback implements BiFunction,String>, SslHandshakeListener + { + private final ALPNServerConnection alpnConnection; + + private ALPNCallback(ALPNServerConnection connection) + { + alpnConnection = connection; + ((DecryptedEndPoint)alpnConnection.getEndPoint()).getSslConnection().addHandshakeListener(this); + } + + @Override + public String apply(SSLEngine engine, List protocols) + { + if (LOG.isDebugEnabled()) + LOG.debug("apply {} {}", alpnConnection, protocols); + alpnConnection.select(protocols); + return alpnConnection.getProtocol(); + } + + @Override + public void handshakeSucceeded(Event event) + { + if (LOG.isDebugEnabled()) + LOG.debug("handshakeSucceeded {} {}", alpnConnection, event); + if (alpnConnection.getProtocol()==null) + { + LOG.warn("No ALPN callback! {} {}",alpnConnection, event); + alpnConnection.unsupported(); + } + } + + @Override + public void handshakeFailed(Event event, Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("handshakeFailed {} {} {}", alpnConnection, event, failure); + } + } +} diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server b/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server new file mode 100644 index 00000000000..255e5b479af --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server @@ -0,0 +1 @@ +org.eclipse.jetty.alpn.conscrypt.server.ConscryptServerALPNProcessor diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2Server.java b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2Server.java new file mode 100644 index 00000000000..f663cd41c2c --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2Server.java @@ -0,0 +1,72 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.conscrypt.server; + +import java.security.Security; + +import org.conscrypt.OpenSSLProvider; +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +/** + * Test server that verifies that the Conscrypt ALPN mechanism works. + */ +public class ConscryptHTTP2Server +{ + public static void main(String[] args) throws Exception + { + Security.addProvider(new OpenSSLProvider()); + + Server server = new Server(); + + HttpConfiguration httpsConfig = new HttpConfiguration(); + httpsConfig.setSecureScheme("https"); + httpsConfig.setSecurePort(8443); + httpsConfig.setSendXPoweredBy(true); + httpsConfig.setSendServerVersion(true); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setProvider("Conscrypt"); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); + sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); + sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); + + HttpConnectionFactory http = new HttpConnectionFactory(httpsConfig); + HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig); + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol(http.getProtocol()); + SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); + + ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2, http); + http2Connector.setPort(8443); + server.addConnector(http2Connector); + + server.start(); + } +} diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..c391f84e35b --- /dev/null +++ b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties @@ -0,0 +1,3 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.alpn.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.jks b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.jks new file mode 100644 index 00000000000..d6592f95ee9 Binary files /dev/null and b/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.jks differ diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index 3ca778c136d..f11438dcc8a 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -33,22 +33,15 @@ org.eclipse.jetty - jetty-io + jetty-alpn-client ${project.version} - - org.eclipse.jetty.alpn - alpn-api - ${alpn.api.version} - - org.eclipse.jetty.http2 http2-client ${project.version} test - diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java index c9f7bb4059c..432adcde610 100644 --- a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java +++ b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java @@ -18,38 +18,66 @@ package org.eclipse.jetty.alpn.java.client; -import java.io.UncheckedIOException; import java.util.List; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; -import org.eclipse.jetty.alpn.ALPN; +import org.eclipse.jetty.alpn.client.ALPNClientConnection; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.util.JavaVersion; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class JDK9ClientALPNProcessor implements ALPNProcessor.Client { + private static final Logger LOG = Log.getLogger(JDK9ClientALPNProcessor.class); + @Override - public void configure(SSLEngine sslEngine, List protocols) + public void init() { - SSLParameters sslParameters = sslEngine.getSSLParameters(); - sslParameters.setApplicationProtocols(protocols.toArray(new String[0])); - sslEngine.setSSLParameters(sslParameters); + if (JavaVersion.VERSION.getPlatform()<9) + throw new IllegalStateException(this + " not applicable for java "+JavaVersion.VERSION); } @Override - public void process(SSLEngine sslEngine) + public boolean appliesTo(SSLEngine sslEngine) { - try + Module module = sslEngine.getClass().getModule(); + return module!=null && "java.base".equals(module.getName()); + } + + @Override + public void configure(SSLEngine sslEngine, Connection connection) + { + ALPNClientConnection alpn = (ALPNClientConnection)connection; + SSLParameters sslParameters = sslEngine.getSSLParameters(); + List protocols = alpn.getProtocols(); + sslParameters.setApplicationProtocols(protocols.toArray(new String[protocols.size()])); + sslEngine.setSSLParameters(sslParameters); + ((DecryptedEndPoint)connection.getEndPoint()).getSslConnection() + .addHandshakeListener(new ALPNListener(alpn)); + } + + private final class ALPNListener implements SslHandshakeListener + { + private final ALPNClientConnection alpnConnection; + + private ALPNListener(ALPNClientConnection connection) { - ALPN.ClientProvider provider = (ALPN.ClientProvider)ALPN.get(sslEngine); - if (provider != null) - provider.selected(sslEngine.getApplicationProtocol()); + alpnConnection = connection; } - catch (SSLException x) + + @Override + public void handshakeSucceeded(Event event) { - throw new UncheckedIOException(x); + String protocol = alpnConnection.getSSLEngine().getApplicationProtocol(); + if (LOG.isDebugEnabled()) + LOG.debug("selected protocol {}", protocol); + alpnConnection.selected(protocol); } } } diff --git a/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java index 3ff2219dd28..d33278f17d5 100644 --- a/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java +++ b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java @@ -42,16 +42,16 @@ public class JDK9HTTP2Client public static void main(String[] args) throws Exception { HTTP2Client client = new HTTP2Client(); - SslContextFactory sslContextFactory = new SslContextFactory(true); + SslContextFactory sslContextFactory = new SslContextFactory(); client.addBean(sslContextFactory); client.start(); - String host = "localhost"; - int port = 8443; + String host = "webtide.com"; + int port = 443; FuturePromise sessionPromise = new FuturePromise<>(); client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); - Session session = sessionPromise.get(555, TimeUnit.SECONDS); + Session session = sessionPromise.get(5, TimeUnit.SECONDS); HttpFields requestFields = new HttpFields(); requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION); diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml index 9c08813aa97..c36114cd8bf 100644 --- a/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -40,12 +40,10 @@ alpn-api ${alpn.api.version} - org.eclipse.jetty - jetty-server + jetty-alpn-server ${project.version} - test org.eclipse.jetty.http2 @@ -53,12 +51,6 @@ ${project.version} test - - org.eclipse.jetty - jetty-alpn-server - ${project.version} - test - junit junit diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java index 606ac5ef796..f9e0313a211 100644 --- a/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java +++ b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java @@ -19,13 +19,16 @@ package org.eclipse.jetty.alpn.java.server; import java.util.List; +import java.util.function.BiFunction; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; -import org.eclipse.jetty.alpn.ALPN; +import org.eclipse.jetty.alpn.server.ALPNServerConnection; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -34,40 +37,61 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandsha private static final Logger LOG = Log.getLogger(JDK9ServerALPNProcessor.class); @Override - public void configure(SSLEngine sslEngine) + public void init() { - sslEngine.setHandshakeApplicationProtocolSelector(this::process); - } - - private String process(SSLEngine sslEngine, List protocols) - { - try - { - if (LOG.isDebugEnabled()) - LOG.debug("ALPN selecting among client{}", protocols); - ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(sslEngine); - return provider == null ? "" : provider.select(protocols); - } - catch (SSLException x) - { - return null; - } + if (JavaVersion.VERSION.getPlatform()<9) + throw new IllegalStateException(this + " not applicable for java "+JavaVersion.VERSION); } @Override - public void handshakeSucceeded(Event event) + public boolean appliesTo(SSLEngine sslEngine) { - ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(event.getSSLEngine()); - if (provider != null) - { - if (LOG.isDebugEnabled()) - LOG.debug("ALPN unsupported by client"); - provider.unsupported(); - } + Module module = sslEngine.getClass().getModule(); + return module!=null && "java.base".equals(module.getName()); } @Override - public void handshakeFailed(Event event, Throwable failure) + public void configure(SSLEngine sslEngine, Connection connection) { + sslEngine.setHandshakeApplicationProtocolSelector(new ALPNCallback((ALPNServerConnection)connection)); + } + + private final class ALPNCallback implements BiFunction,String>, SslHandshakeListener + { + private final ALPNServerConnection alpnConnection; + + private ALPNCallback(ALPNServerConnection connection) + { + alpnConnection = connection; + ((SslConnection.DecryptedEndPoint)alpnConnection.getEndPoint()).getSslConnection().addHandshakeListener(this); + } + + @Override + public String apply(SSLEngine engine, List protocols) + { + if (LOG.isDebugEnabled()) + LOG.debug("apply {} {}", alpnConnection, protocols); + alpnConnection.select(protocols); + return alpnConnection.getProtocol(); + } + + @Override + public void handshakeSucceeded(Event event) + { + if (LOG.isDebugEnabled()) + LOG.debug("handshakeSucceeded {} {}", alpnConnection, event); + if (alpnConnection.getProtocol()==null) + { + LOG.warn("No ALPN callback! {} {}",alpnConnection, event); + alpnConnection.unsupported(); + } + } + + @Override + public void handshakeFailed(Event event, Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("handshakeFailed {} {} {}", alpnConnection, event, failure); + } } } diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml new file mode 100644 index 00000000000..250ff6a7333 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml @@ -0,0 +1,38 @@ + + + + + org.eclipse.jetty + jetty-alpn-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + jetty-alpn-openjdk8-client + Jetty :: ALPN :: OpenJDK8 Client Implementation + + + ${project.groupId}.alpn.java.client + + + + + org.eclipse.jetty + jetty-alpn-client + ${project.version} + + + org.eclipse.jetty.alpn + alpn-api + ${alpn.api.version} + + + org.eclipse.jetty.http2 + http2-client + ${project.version} + test + + + diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/src/main/java/org/eclipse/jetty/alpn/java/client/OpenJDK8ClientALPNProcessor.java b/jetty-alpn/jetty-alpn-openjdk8-client/src/main/java/org/eclipse/jetty/alpn/java/client/OpenJDK8ClientALPNProcessor.java new file mode 100644 index 00000000000..539cb6cd872 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-client/src/main/java/org/eclipse/jetty/alpn/java/client/OpenJDK8ClientALPNProcessor.java @@ -0,0 +1,104 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.java.client; + +import java.util.List; + +import javax.net.ssl.SSLEngine; + +import org.eclipse.jetty.alpn.ALPN; +import org.eclipse.jetty.alpn.client.ALPNClientConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.util.JavaVersion; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class OpenJDK8ClientALPNProcessor implements ALPNProcessor.Client +{ + private static final Logger LOG = Log.getLogger(OpenJDK8ClientALPNProcessor.class); + + @Override + public void init() + { + if (JavaVersion.VERSION.getPlatform()!=8) + throw new IllegalStateException(this + " not applicable for java "+JavaVersion.VERSION); + if (ALPN.class.getClassLoader()!=null) + throw new IllegalStateException(this + " must be on JVM boot classpath"); + } + + @Override + public boolean appliesTo(SSLEngine sslEngine) + { + return sslEngine.getClass().getName().startsWith("sun.security.ssl."); + } + + @Override + public void configure(SSLEngine sslEngine, Connection connection) + { + connection.addListener(new ALPNListener((ALPNClientConnection)connection)); + } + + private final class ALPNListener implements ALPN.ClientProvider, Connection.Listener + { + private final ALPNClientConnection alpnConnection; + + private ALPNListener(ALPNClientConnection connection) + { + alpnConnection = connection; + } + + @Override + public void onOpened(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onOpened {}", alpnConnection); + ALPN.put(alpnConnection.getSSLEngine(), this); + } + + @Override + public void onClosed(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onClosed {}", alpnConnection); + ALPN.remove(alpnConnection.getSSLEngine()); + } + + @Override + public List protocols() + { + return alpnConnection.getProtocols(); + } + + @Override + public void unsupported() + { + if (LOG.isDebugEnabled()) + LOG.debug("unsupported {}", alpnConnection); + ALPN.remove(alpnConnection.getSSLEngine()); + alpnConnection.selected(null); + } + + @Override + public void selected(String protocol) + { + alpnConnection.selected(protocol); + } + } +} diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client b/jetty-alpn/jetty-alpn-openjdk8-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client new file mode 100644 index 00000000000..de5d9cd9ec4 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client @@ -0,0 +1 @@ +org.eclipse.jetty.alpn.java.client.OpenJDK8ClientALPNProcessor diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/src/test/java/org/eclipse/jetty/alpn/java/client/OpenJDK8HTTP2Client.java b/jetty-alpn/jetty-alpn-openjdk8-client/src/test/java/org/eclipse/jetty/alpn/java/client/OpenJDK8HTTP2Client.java new file mode 100644 index 00000000000..24e51849039 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-client/src/test/java/org/eclipse/jetty/alpn/java/client/OpenJDK8HTTP2Client.java @@ -0,0 +1,85 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.java.client; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class OpenJDK8HTTP2Client +{ + public static void main(String[] args) throws Exception + { + HTTP2Client client = new HTTP2Client(); + SslContextFactory sslContextFactory = new SslContextFactory(); + client.addBean(sslContextFactory); + client.start(); + + String host = "webtide.com"; + int port = 443; + + FuturePromise sessionPromise = new FuturePromise<>(); + client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); + Session session = sessionPromise.get(5, TimeUnit.SECONDS); + + HttpFields requestFields = new HttpFields(); + requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION); + MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields); + HeadersFrame headersFrame = new HeadersFrame(metaData, null, true); + CountDownLatch latch = new CountDownLatch(1); + session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + System.err.println(frame); + if (frame.isEndStream()) + latch.countDown(); + } + + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + System.err.println(frame); + callback.succeeded(); + if (frame.isEndStream()) + latch.countDown(); + } + }); + + latch.await(5, TimeUnit.SECONDS); + + client.stop(); + } +} diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-openjdk8-client/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..d96a696f82e --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-client/src/test/resources/jetty-logging.properties @@ -0,0 +1,2 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml new file mode 100644 index 00000000000..264fabe3457 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml @@ -0,0 +1,43 @@ + + + + org.eclipse.jetty + jetty-alpn-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + jetty-alpn-openjdk8-server + Jetty :: ALPN :: OpenJDK8 Server Implementation + + + ${project.groupId}.alpn.conscrypt.server + + + + + org.eclipse.jetty.alpn + alpn-api + ${alpn.api.version} + provided + + + org.eclipse.jetty + jetty-alpn-server + ${project.version} + + + org.eclipse.jetty + jetty-io + ${project.version} + + + + org.eclipse.jetty.http2 + http2-server + ${project.version} + test + + + diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/src/main/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8ServerALPNProcessor.java b/jetty-alpn/jetty-alpn-openjdk8-server/src/main/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8ServerALPNProcessor.java new file mode 100644 index 00000000000..6e25f9dc311 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-server/src/main/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8ServerALPNProcessor.java @@ -0,0 +1,104 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.openjdk8.server; + +import java.util.Collections; +import java.util.List; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; + +import org.eclipse.jetty.alpn.ALPN; +import org.eclipse.jetty.alpn.server.ALPNServerConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.util.JavaVersion; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class OpenJDK8ServerALPNProcessor implements ALPNProcessor.Server +{ + private static final Logger LOG = Log.getLogger(OpenJDK8ServerALPNProcessor.class); + + @Override + public void init() + { + if (JavaVersion.VERSION.getPlatform()!=8) + throw new IllegalStateException(this + " not applicable for java "+JavaVersion.VERSION); + if (ALPN.class.getClassLoader()!=null) + throw new IllegalStateException(ALPN.class.getName() + " must be on JVM boot classpath"); + if (LOG.isDebugEnabled()) + ALPN.debug = true; + } + + @Override + public boolean appliesTo(SSLEngine sslEngine) + { + return sslEngine.getClass().getName().startsWith("sun.security.ssl."); + } + + @Override + public void configure(SSLEngine sslEngine, Connection connection) + { + connection.addListener(new ALPNListener((ALPNServerConnection)connection)); + } + + private final class ALPNListener implements ALPN.ServerProvider, Connection.Listener + { + private final ALPNServerConnection alpnConnection; + + private ALPNListener(ALPNServerConnection connection) + { + alpnConnection = connection; + } + + @Override + public void onOpened(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onOpened {}", alpnConnection); + ALPN.put(alpnConnection.getSSLEngine(), this); + } + + @Override + public void onClosed(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onClosed {}", alpnConnection); + ALPN.remove(alpnConnection.getSSLEngine()); + } + + @Override + public void unsupported() + { + if (LOG.isDebugEnabled()) + LOG.debug("unsupported {}", alpnConnection); + alpnConnection.select(Collections.emptyList()); + } + + @Override + public String select(List protocols) throws SSLException + { + if (LOG.isDebugEnabled()) + LOG.debug("select {} {}", alpnConnection, protocols); + alpnConnection.select(protocols); + return alpnConnection.getProtocol(); + } + } +} diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server b/jetty-alpn/jetty-alpn-openjdk8-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server new file mode 100644 index 00000000000..3a2141ba0e8 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server @@ -0,0 +1 @@ +org.eclipse.jetty.alpn.openjdk8.server.OpenJDK8ServerALPNProcessor diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/src/test/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8HTTP2Server.java b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8HTTP2Server.java new file mode 100644 index 00000000000..3efbd4bdbc3 --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/java/org/eclipse/jetty/alpn/openjdk8/server/OpenJDK8HTTP2Server.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.alpn.openjdk8.server; + +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +/** + * Test server that verifies that the JDK 8 ALPN mechanism works. + */ +public class OpenJDK8HTTP2Server +{ + public static void main(String... args) throws Exception + { + Server server = new Server(); + + HttpConfiguration httpsConfig = new HttpConfiguration(); + httpsConfig.setSecureScheme("https"); + httpsConfig.setSecurePort(8443); + httpsConfig.setSendXPoweredBy(true); + httpsConfig.setSendServerVersion(true); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); + sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); + sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); + + HttpConnectionFactory http = new HttpConnectionFactory(httpsConfig); + HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig); + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol(http.getProtocol()); + SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); + + ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2, http); + http2Connector.setPort(8443); + server.addConnector(http2Connector); + + server.start(); + } +} diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..c391f84e35b --- /dev/null +++ b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/jetty-logging.properties @@ -0,0 +1,3 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.alpn.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/keystore.jks b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/keystore.jks new file mode 100644 index 00000000000..d6592f95ee9 Binary files /dev/null and b/jetty-alpn/jetty-alpn-openjdk8-server/src/test/resources/keystore.jks differ diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml index 4cc144a4a16..0fc6f0f3712 100644 --- a/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-alpn/jetty-alpn-server/pom.xml @@ -59,12 +59,6 @@ jetty-server ${project.version} - - org.eclipse.jetty.alpn - alpn-api - ${alpn.api.version} - provided - org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/etc/jetty-alpn.xml b/jetty-alpn/jetty-alpn-server/src/main/config/etc/jetty-alpn.xml index f1288e6c056..e577c2baa44 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/etc/jetty-alpn.xml +++ b/jetty-alpn/jetty-alpn-server/src/main/config/etc/jetty-alpn.xml @@ -15,18 +15,9 @@ - - - - - - + + - - - - - diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl.mod new file mode 100644 index 00000000000..d726e163d24 --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl.mod @@ -0,0 +1,5 @@ +[description] +Selects an ALPN (Application Layer Protocol Negotiation) implementation by java version. + +[depend] +alpn-impl/alpn-${java.version.platform} diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod index a591d419f89..6ace07a2e6b 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod @@ -17,6 +17,9 @@ specific version of Java. [depend] alpn-impl/alpn-${java.version} +[lib] +lib/jetty-alpn-openjdk8-server-${jetty.version}.jar + [files] lib/ lib/alpn/ 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 e25948f4bc8..cb02222ed89 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 @@ -3,7 +3,7 @@ Enables the ALPN (Application Layer Protocol Negotiation) TLS extension. [depend] ssl -alpn-impl/alpn-${java.version.platform} +alpn-impl [lib] lib/jetty-alpn-client-${jetty.version}.jar @@ -21,6 +21,3 @@ etc/jetty-alpn.xml ## Specifies what protocol to use when negotiation fails. # jetty.alpn.defaultProtocol=http/1.1 -## ALPN debug logging on System.err -# jetty.alpn.debug=false - diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java index f3e5835c33b..83f819f62fb 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java +++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java @@ -24,7 +24,6 @@ import java.util.List; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; -import org.eclipse.jetty.alpn.ALPN; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; @@ -32,24 +31,21 @@ import org.eclipse.jetty.server.NegotiatingServerConnection; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class ALPNServerConnection extends NegotiatingServerConnection implements ALPN.ServerProvider +public class ALPNServerConnection extends NegotiatingServerConnection { private static final Logger LOG = Log.getLogger(ALPNServerConnection.class); public ALPNServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List protocols, String defaultProtocol) { super(connector, endPoint, engine, protocols, defaultProtocol); - ALPN.put(engine, this); } - @Override public void unsupported() { select(Collections.emptyList()); } - - @Override - public String select(List clientProtocols) + + public void select(List clientProtocols) { SSLEngine sslEngine = getSSLEngine(); List serverProtocols = getProtocols(); @@ -70,7 +66,7 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements if (factory instanceof CipherDiscriminator && !((CipherDiscriminator)factory).isAcceptable(serverProtocol, tlsProtocol, tlsCipher)) { if (LOG.isDebugEnabled()) - LOG.debug("{} protocol {} not acceptable to {} for {}/{}", this, serverProtocol, factory, tlsProtocol, tlsCipher); + LOG.debug("Protocol {} not acceptable to {} for {}/{} on {}", serverProtocol, factory, tlsProtocol, tlsCipher, getEndPoint()); continue; } @@ -87,21 +83,12 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements else { if (LOG.isDebugEnabled()) - LOG.debug("{} could not negotiate protocol among client{} and server{}", this, clientProtocols, serverProtocols); + LOG.debug("Could not negotiate protocol from client{} and server{} on {}", clientProtocols, serverProtocols, getEndPoint()); throw new IllegalStateException(); } } if (LOG.isDebugEnabled()) - LOG.debug("{} protocol selected {} among client{} and server{}", this, negotiated, clientProtocols, serverProtocols); + LOG.debug("Protocol selected {} from client{} and server{} on {}", negotiated, clientProtocols, serverProtocols, getEndPoint()); setProtocol(negotiated); - ALPN.remove(sslEngine); - return negotiated; - } - - @Override - public void close() - { - ALPN.remove(getSSLEngine()); - super.close(); } } diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java index ccf886a73ef..56135bd008d 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java +++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.alpn.server; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; @@ -26,52 +28,82 @@ import javax.net.ssl.SSLEngine; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.ssl.ALPNProcessor; -import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.io.ssl.ALPNProcessor.Server; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NegotiatingServerConnectionFactory; import org.eclipse.jetty.util.annotation.Name; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; -public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory implements SslHandshakeListener +public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory { - private final ALPNProcessor.Server alpnProcessor; + private static final Logger LOG = Log.getLogger(ALPNServerConnectionFactory.class); - public ALPNServerConnectionFactory(String protocols) + private final List processors = new ArrayList<>(); + + public ALPNServerConnectionFactory(@Name("protocols") String protocols) { this(protocols.trim().split(",", 0)); } - + public ALPNServerConnectionFactory(@Name("protocols") String... protocols) { super("alpn", protocols); - checkProtocolNegotiationAvailable(); - Iterator processors = ServiceLoader.load(ALPNProcessor.Server.class).iterator(); - alpnProcessor = processors.hasNext() ? processors.next() : ALPNProcessor.Server.NOOP; - } - public ALPNProcessor.Server getALPNProcessor() - { - return alpnProcessor; + IllegalStateException failure = new IllegalStateException("No Server ALPNProcessors!"); + // Use a for loop on iterator so load exceptions can be caught and ignored + for (Iterator i = ServiceLoader.load(Server.class).iterator(); i.hasNext();) + { + Server processor; + try + { + processor = i.next(); + } + catch(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + failure.addSuppressed(x); + continue; + } + + try + { + processor.init(); + processors.add(processor); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Could not initialize " + processor, x); + failure.addSuppressed(x); + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("protocols: {}", Arrays.asList(protocols)); + LOG.debug("processors: {}", processors); + } + + if (processors.isEmpty()) + throw failure; } @Override protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List protocols, String defaultProtocol) { - getALPNProcessor().configure(engine); - return new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol); - } - - @Override - public void handshakeSucceeded(Event event) - { - if (alpnProcessor instanceof SslHandshakeListener) - ((SslHandshakeListener)alpnProcessor).handshakeSucceeded(event); - } - - @Override - public void handshakeFailed(Event event, Throwable failure) - { - if (alpnProcessor instanceof SslHandshakeListener) - ((SslHandshakeListener)alpnProcessor).handshakeFailed(event, failure); + for (Server processor : processors) + { + if (processor.appliesTo(engine)) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} for {} on {}", processor, engine, endPoint); + ALPNServerConnection connection = new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol); + processor.configure(engine, connection); + return connection; + } + } + throw new IllegalStateException("No ALPNProcessor for " + engine); } } diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml index 53e9669cbef..941b382d7e2 100644 --- a/jetty-alpn/pom.xml +++ b/jetty-alpn/pom.xml @@ -13,6 +13,10 @@ jetty-alpn-server jetty-alpn-client + jetty-alpn-openjdk8-server + jetty-alpn-openjdk8-client + jetty-alpn-conscrypt-server + jetty-alpn-conscrypt-client 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 2ec5516e77d..13f9b36b77d 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 @@ -24,16 +24,23 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.JarInputStream; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.MultiReleaseJarFile; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -45,7 +52,6 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; - /** * AnnotationParser *

@@ -68,10 +74,29 @@ public class AnnotationParser { private static final Logger LOG = Log.getLogger(AnnotationParser.class); - protected Set _parsedClassNames = ConcurrentHashMap.newKeySet(); - + private static final int JVM_MAJOR_VER; protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api - + + protected Map> _parsedClassNames = new ConcurrentHashMap<>(); + + static + { + // Determine JVM spec version + // Using guidance from http://openjdk.java.net/jeps/223 + String jvmSpecVer = System.getProperty("java.vm.specification.version"); + + if (jvmSpecVer.indexOf('.') >= 0) + { + // Old spec version (Java 1.8 and older) + String parts[] = jvmSpecVer.split("\\."); + JVM_MAJOR_VER = Integer.parseInt(parts[1]); + } + else + { + // Newer spec version (Java 9+) + JVM_MAJOR_VER = Integer.parseInt(jvmSpecVer); + } + } /** * Convert internal name to simple name @@ -110,12 +135,8 @@ public class AnnotationParser return normalList; } - /** - * ClassInfo - * * Immutable information gathered by parsing class header. - * */ public class ClassInfo { @@ -175,10 +196,7 @@ public class AnnotationParser } } - /** - * MethodInfo - * * Immutable information gathered by parsing a method on a class. */ public class MethodInfo @@ -231,14 +249,9 @@ public class AnnotationParser return _exceptions; } } - - - + /** - * FieldInfo - * * Immutable information gathered by parsing a field on a class. - * */ public class FieldInfo { @@ -292,8 +305,6 @@ public class AnnotationParser } /** - * Handler - * * Signature for all handlers that respond to parsing class files. */ public static interface Handler @@ -307,13 +318,10 @@ public class AnnotationParser } /** - * AbstractHandler - * * Convenience base class to provide no-ops for all Handler methods. */ public static abstract class AbstractHandler implements Handler { - @Override public void handle(ClassInfo classInfo) { @@ -347,15 +355,11 @@ public class AnnotationParser @Override public void handle(FieldInfo info, String annotationName) { - // no-op - } + // no-op + } } - - /** - * MyMethodVisitor - * * ASM Visitor for parsing a method. We are only interested in the annotations on methods. */ public class MyMethodVisitor extends MethodVisitor @@ -376,11 +380,8 @@ public class AnnotationParser _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions); } - /** * We are only interested in finding the annotations on methods. - * - * @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean) */ @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) @@ -392,21 +393,15 @@ public class AnnotationParser } } - - /** - * MyFieldVisitor - * - * An ASM visitor for parsing Fields. + * An ASM visitor for parsing Fields. * We are only interested in visiting annotations on Fields. - * */ public class MyFieldVisitor extends FieldVisitor { final FieldInfo _fieldInfo; final Set _handlers; - - + public MyFieldVisitor(final Set handlers, final ClassInfo classInfo, final int access, @@ -420,11 +415,8 @@ public class AnnotationParser _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value); } - /** * Parse an annotation found on a Field. - * - * @see org.objectweb.asm.FieldVisitor#visitAnnotation(java.lang.String, boolean) */ @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) @@ -437,17 +429,11 @@ public class AnnotationParser } } - - - /** - * MyClassVisitor - * * ASM visitor for a class. */ public class MyClassVisitor extends ClassVisitor { - final Resource _containingResource; final Set _handlers; ClassInfo _ci; @@ -459,7 +445,6 @@ public class AnnotationParser _containingResource = containingResource; } - @Override public void visit (final int version, final int access, @@ -469,18 +454,12 @@ public class AnnotationParser final String[] interfaces) { _ci = new ClassInfo(_containingResource, normalize(name), version, access, signature, normalize(superName), normalize(interfaces)); - - _parsedClassNames.add(_ci.getClassName()); - for (Handler h:_handlers) h.handle(_ci); } - /** * Visit an annotation on a Class - * - * @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String, boolean) */ @Override public AnnotationVisitor visitAnnotation (String desc, boolean visible) @@ -488,15 +467,11 @@ public class AnnotationParser String annotationName = normalize(desc); for (Handler h : _handlers) h.handle(_ci, annotationName); - return null; } - /** * Visit a method to extract its annotations - * - * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) */ @Override public MethodVisitor visitMethod (final int access, @@ -505,14 +480,11 @@ public class AnnotationParser final String signature, final String[] exceptions) { - return new MyMethodVisitor(_handlers, _ci, access, name, methodDesc, signature, exceptions); } /** * Visit a field to extract its annotations - * - * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object) */ @Override public FieldVisitor visitField (final int access, @@ -525,20 +497,41 @@ public class AnnotationParser } } - - /** - * True if the class has already been processed, false otherwise - * @param className the classname - * @return true if class was parsed, false if not + * Add a class as having been parsed. + * + * @param classname the name of the class + * @param location the fully qualified location of the class */ - public boolean isParsed (String className) + public void addParsedClass (String classname, Resource location) { - return _parsedClassNames.contains(className); + List list = new ArrayList<>(1); + if (location != null) + list.add(location.toString()); + + List existing = _parsedClassNames.putIfAbsent(classname, list); + if (existing != null) + { + existing.addAll(list); + LOG.warn("{} scanned from multiple locations: {}", classname, existing); + } + } + + /** + * Get the locations of the given classname. There may be more than one + * location if there are duplicates of the same class. + * + * @param classname the name of the class + * @return an immutable list of locations + */ + public List getParsedLocations (String classname) + { + List list = _parsedClassNames.get(classname); + if (list == null) + return Collections.emptyList(); + return Collections.unmodifiableList(list); } - - /** * Parse a given class * @@ -546,29 +539,25 @@ public class AnnotationParser * @param className the class name to parse * @throws Exception if unable to parse */ - public void parse (Set handlers, String className) - throws Exception + public void parse (Set handlers, String className) throws Exception { if (className == null) return; - if (!isParsed(className)) + String tmp = className; + className = className.replace('.', '/')+".class"; + URL resource = Loader.getResource(className); + if (resource!= null) { - className = className.replace('.', '/')+".class"; - URL resource = Loader.getResource(className); - if (resource!= null) + Resource r = Resource.newResource(resource); + addParsedClass(tmp, r); + try (InputStream is = r.getInputStream()) { - Resource r = Resource.newResource(resource); - try (InputStream is = r.getInputStream()) - { - scanClass(handlers, null, is); - } + scanClass(handlers, null, is); } } } - - /** * Parse the given class, optionally walking its inheritance hierarchy * @@ -577,27 +566,23 @@ public class AnnotationParser * @param visitSuperClasses if true, also visit super classes for parse * @throws Exception if unable to parse class */ - public void parse (Set handlers, Class clazz, boolean visitSuperClasses) - throws Exception + public void parse (Set handlers, Class clazz, boolean visitSuperClasses) throws Exception { Class cz = clazz; while (cz != Object.class) { - if (!isParsed(cz.getName())) + String nameAsResource = cz.getName().replace('.', '/')+".class"; + URL resource = Loader.getResource(nameAsResource); + if (resource!= null) { - String nameAsResource = cz.getName().replace('.', '/')+".class"; - URL resource = Loader.getResource(nameAsResource); - if (resource!= null) + Resource r = Resource.newResource(resource); + addParsedClass(clazz.getName(), r); + try (InputStream is = r.getInputStream()) { - Resource r = Resource.newResource(resource); - try (InputStream is = r.getInputStream()) - { - scanClass(handlers, null, is); - } + scanClass(handlers, null, is); } } - if (visitSuperClasses) cz = cz.getSuperclass(); else @@ -605,8 +590,6 @@ public class AnnotationParser } } - - /** * Parse the given classes * @@ -614,8 +597,7 @@ public class AnnotationParser * @param classNames the class name * @throws Exception if unable to parse */ - public void parse (Set handlers, String[] classNames) - throws Exception + public void parse (Set handlers, String[] classNames) throws Exception { if (classNames == null) return; @@ -623,7 +605,6 @@ public class AnnotationParser parse(handlers, Arrays.asList(classNames)); } - /** * Parse the given classes * @@ -631,26 +612,24 @@ public class AnnotationParser * @param classNames the class names * @throws Exception if unable to parse */ - public void parse (Set handlers, List classNames) - throws Exception + public void parse (Set handlers, List classNames) throws Exception { MultiException me = new MultiException(); - + for (String s:classNames) { try { - if (!isParsed(s)) + String name = s; + s = s.replace('.', '/')+".class"; + URL resource = Loader.getResource(s); + if (resource!= null) { - s = s.replace('.', '/')+".class"; - URL resource = Loader.getResource(s); - if (resource!= null) + Resource r = Resource.newResource(resource); + addParsedClass(name, r); + try (InputStream is = r.getInputStream()) { - Resource r = Resource.newResource(resource); - try (InputStream is = r.getInputStream()) - { - scanClass(handlers, null, is); - } + scanClass(handlers, null, is); } } } @@ -662,67 +641,65 @@ public class AnnotationParser me.ifExceptionThrow(); } - /** * Parse all classes in a directory * * @param handlers the set of handlers to look for classes in - * @param dir the resource directory to look for classes + * @param root the resource directory to look for classes * @throws Exception if unable to parse */ - protected void parseDir (Set handlers, Resource dir) - throws Exception + protected void parseDir (Set handlers, Resource root) throws Exception { - // skip dirs whose name start with . (ie hidden) - if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith(".")) + if (!root.isDirectory() || !root.exists() || root.getName().startsWith(".")) return; - if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);}; - - MultiException me = new MultiException(); + if (LOG.isDebugEnabled()) + LOG.debug("Scanning dir {}", root); - String[] files=dir.list(); - for (int f=0;files!=null && f resources = root.getAllResources(); + if (resources != null) { - Resource res = dir.addPath(files[f]); - if (res.isDirectory()) - parseDir(handlers, res); - else + for (Resource r:resources) { - //we've already verified the directories, so just verify the class file name - File file = res.getFile(); + if (r.isDirectory()) + continue; + + File file = r.getFile(); if (isValidClassFileName((file==null?null:file.getName()))) { + Path classpath = rootFile.toPath().relativize(file.toPath()); + String str = classpath.toString(); + str = str.substring(0, str.lastIndexOf(".class")).replace('/', '.').replace('\\', '.'); + try { - String name = res.getName(); - if (!isParsed(name)) + if (LOG.isDebugEnabled()) + LOG.debug("Scanning class {}", r); + addParsedClass(str, r); + try (InputStream is=r.getInputStream()) { - Resource r = Resource.newResource(res.getURL()); - if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);}; - try (InputStream is=r.getInputStream()) - { - scanClass(handlers, dir, is); - } + scanClass(handlers, Resource.newResource(file.getParentFile()), is); } } catch (Exception ex) { - if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex); - me.add(new RuntimeException("Error scanning file "+files[f],ex)); + if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+file, ex); + me.add(new RuntimeException("Error scanning file "+file,ex)); } } else { - if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res); + if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", file); } } } - + me.ifExceptionThrow(); } - /** * Parse classes in the supplied classloader. * Only class files in jar files will be scanned. @@ -733,39 +710,12 @@ public class AnnotationParser * @param nullInclusive if true, an empty pattern means all names match, if false, none match * @throws Exception if unable to parse */ - public void parse (final Set handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive) - throws Exception + @Deprecated + public void parse (final Set handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive) throws Exception { - if (loader==null) - return; - - if (!(loader instanceof URLClassLoader)) - return; //can't extract classes? - - final MultiException me = new MultiException(); - - JarScanner scanner = new JarScanner() - { - @Override - public void processEntry(URI jarUri, JarEntry entry) - { - try - { - parseJarEntry(handlers, Resource.newResource(jarUri), entry); - } - catch (Exception e) - { - me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e)); - } - } - - }; - - scanner.scan(null, loader, nullInclusive, visitParents); - me.ifExceptionThrow(); + throw new UnsupportedOperationException(); } - /** * Parse classes in the supplied uris. * @@ -773,8 +723,7 @@ public class AnnotationParser * @param uris the uris for the jars * @throws Exception if unable to parse */ - public void parse (final Set handlers, final URI[] uris) - throws Exception + public void parse (final Set handlers, final URI[] uris) throws Exception { if (uris==null) return; @@ -802,8 +751,7 @@ public class AnnotationParser * @param uri the uri for the jar * @throws Exception if unable to parse */ - public void parse (final Set handlers, URI uri) - throws Exception + public void parse (final Set handlers, URI uri) throws Exception { if (uri == null) return; @@ -811,7 +759,6 @@ public class AnnotationParser parse (handlers, Resource.newResource(uri)); } - /** * Parse a resource * @@ -819,8 +766,7 @@ public class AnnotationParser * @param r the resource to parse * @throws Exception if unable to parse */ - public void parse (final Set handlers, Resource r) - throws Exception + public void parse (final Set handlers, Resource r) throws Exception { if (r == null) return; @@ -850,9 +796,6 @@ public class AnnotationParser if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r); } - - - /** * Parse a resource that is a jar file. * @@ -860,47 +803,31 @@ public class AnnotationParser * @param jarResource the jar resource to parse * @throws Exception if unable to parse */ - protected void parseJar (Set handlers, Resource jarResource) - throws Exception + protected void parseJar (Set handlers, Resource jarResource) throws Exception { if (jarResource == null) return; if (jarResource.toString().endsWith(".jar")) { - if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);}; - - //treat it as a jar that we need to open and scan all entries from - InputStream in = jarResource.getInputStream(); - if (in==null) - return; + if (LOG.isDebugEnabled()) + LOG.debug("Scanning jar {}", jarResource); MultiException me = new MultiException(); - JarInputStream jar_in = new JarInputStream(in); - try - { - JarEntry entry = jar_in.getNextJarEntry(); - while (entry!=null) - { - try - { - parseJarEntry(handlers, jarResource, entry); - } - catch (Exception e) - { - me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e)); - } - entry = jar_in.getNextJarEntry(); + // TODO do not force version 8 once ASM can scan 9 + MultiReleaseJarFile jarFile = new MultiReleaseJarFile(jarResource.getFile(),8,false); + jarFile.stream().forEach(e-> + { + try + { + parseJarEntry(handlers, jarResource, e); } - } - catch (Exception e) - { - me.add(new RuntimeException("Error scanning jar "+jarResource, e)); - } - finally - { - jar_in.close(); - } + catch (Exception ex) + { + me.add(new RuntimeException("Error scanning entry " + e.getName() + " from jar " + jarResource, ex)); + } + }); + me.ifExceptionThrow(); } @@ -910,12 +837,10 @@ public class AnnotationParser * Parse a single entry in a jar file * * @param handlers the handlers to look for classes in - * @param jar the jar resource to parse - * @param entry the entry in the jar resource to parse + * @param entry the entry in the potentially MultiRelease jar resource to parse * @throws Exception if unable to parse */ - protected void parseJarEntry (Set handlers, Resource jar, JarEntry entry) - throws Exception + protected void parseJarEntry (Set handlers, Resource jar, MultiReleaseJarFile.VersionedJarEntry entry) throws Exception { if (jar == null || entry == null) return; @@ -930,20 +855,15 @@ public class AnnotationParser if (isValidClassFileName(name) && isValidClassFilePath(name)) { String shortName = name.replace('/', '.').substring(0,name.length()-6); - - if (!isParsed(shortName)) + addParsedClass(shortName, Resource.newResource("jar:"+jar.getURI()+"!/"+entry.getNameInJar())); + if (LOG.isDebugEnabled()) + LOG.debug("Scanning class from jar {}!/{}", jar, entry); + try (InputStream is = entry.getInputStream()) { - Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name); - if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);}; - try (InputStream is = clazz.getInputStream()) - { - scanClass(handlers, jar, is); - } + scanClass(handlers, jar, is); } } } - - /** * Use ASM on a class @@ -953,13 +873,20 @@ public class AnnotationParser * @param is the input stream to parse * @throws IOException if unable to parse */ - protected void scanClass (Set handlers, Resource containingResource, InputStream is) - throws IOException + protected void scanClass (Set handlers, Resource containingResource, InputStream is) throws IOException { ClassReader reader = new ClassReader(is); reader.accept(new MyClassVisitor(handlers, containingResource), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES); } + /** + * Remove any parsed class names. + */ + public void resetParsedClasses () + { + _parsedClassNames.clear(); + } + /** * Check that the given path represents a valid class file name. * The check is fairly cursory, checking that: @@ -968,8 +895,8 @@ public class AnnotationParser *

  • it isn't a dot file or in a hidden directory
  • *
  • the name of the class at least begins with a valid identifier for a class name
  • * - * @param name - * @return + * @param name the class file name + * @return whether the class file name is valid */ private boolean isValidClassFileName (String name) { @@ -1003,13 +930,12 @@ public class AnnotationParser return true; } - - + /** * Check that the given path does not contain hidden directories * - * @param path - * @return + * @param path the class file path + * @return whether the class file path is valid */ private boolean isValidClassFilePath (String path) { @@ -1017,14 +943,13 @@ public class AnnotationParser if (path == null || path.length()==0) return false; - //skip any classfiles that are in a hidden directory + // skip any classfiles that are in a hidden directory if (path.startsWith(".") || path.contains("/.")) { - if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path); + if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: " + path); return false; } return true; } } - diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java index 10c91074302..7f7cc18cd6c 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java @@ -18,11 +18,15 @@ package org.eclipse.jetty.annotations; + import javax.annotation.security.DeclareRoles; import javax.servlet.Servlet; import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler; +import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.webapp.WebAppContext; /** @@ -30,6 +34,7 @@ import org.eclipse.jetty.webapp.WebAppContext; */ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotationHandler { + private static final Logger LOG = Log.getLogger(DeclareRolesAnnotationHandler.class); protected WebAppContext _context; @@ -48,6 +53,12 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat if (!Servlet.class.isAssignableFrom(clazz)) return; //only applicable on javax.servlet.Servlet derivatives + if (!(_context.getSecurityHandler() instanceof ConstraintAware)) + { + LOG.warn("SecurityHandler not ConstraintAware, skipping security annotation processing"); + return; + } + DeclareRoles declareRoles = (DeclareRoles) clazz.getAnnotation(DeclareRoles.class); if (declareRoles == null) return; diff --git a/jetty-annotations/src/test/java/org/acme/ClassOne.java b/jetty-annotations/src/test/java/org/acme/ClassOne.java new file mode 100644 index 00000000000..29ea5c94600 --- /dev/null +++ b/jetty-annotations/src/test/java/org/acme/ClassOne.java @@ -0,0 +1,35 @@ +package org.acme; +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + + + +/** + * ClassOne + * + * + */ +public class ClassOne +{ + + public void one() + { + } + +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java index 8ab3cfe5f9a..590947d3883 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java @@ -29,6 +29,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URI; import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -44,6 +45,7 @@ import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestingDir; +import org.eclipse.jetty.util.resource.Resource; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -163,6 +165,26 @@ public class TestAnnotationParser // only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here } + @Test + public void testModuleInfoClassInJar() throws Exception + { + File badClassesJar = MavenTestingUtils.getTestResourceFile("jdk9/slf4j-api-1.8.0-alpha2.jar"); + AnnotationParser parser = new AnnotationParser(); + Set emptySet = Collections.emptySet(); + parser.parse(emptySet, badClassesJar.toURI()); + // Should throw no exceptions, and happily skip the module-info.class files + } + + @Test + public void testJep238MultiReleaseInJar() throws Exception + { + File badClassesJar = MavenTestingUtils.getTestResourceFile("jdk9/log4j-api-2.9.0.jar"); + AnnotationParser parser = new AnnotationParser(); + Set emptySet = Collections.emptySet(); + parser.parse(emptySet, badClassesJar.toURI()); + // Should throw no exceptions, and skip the META-INF/versions/9/* files + } + @Test public void testBasedirExclusion() throws Exception { @@ -189,6 +211,40 @@ public class TestAnnotationParser // Validate Assert.assertThat("Found Class", tracker.foundClasses, contains(ClassA.class.getName())); } + + + @Test + public void testScanDuplicateClassesInJars() throws Exception + { + Resource testJar = Resource.newResource(MavenTestingUtils.getTestResourceFile("tinytest.jar")); + Resource testJar2 = Resource.newResource(MavenTestingUtils.getTestResourceFile("tinytest_copy.jar")); + AnnotationParser parser = new AnnotationParser(); + Set emptySet = Collections.emptySet(); + parser.parse(emptySet, testJar); + parser.parse(emptySet, testJar2); + List locations = parser.getParsedLocations("org.acme.ClassOne"); + Assert.assertNotNull(locations); + Assert.assertEquals(2, locations.size()); + Assert.assertTrue(!(locations.get(0).equals(locations.get(1)))); + } + + + @Test + public void testScanDuplicateClasses() throws Exception + { + Resource testJar = Resource.newResource(MavenTestingUtils.getTestResourceFile("tinytest.jar")); + File testClasses = new File(MavenTestingUtils.getTargetDir(), "test-classes"); + AnnotationParser parser = new AnnotationParser(); + Set emptySet = Collections.emptySet(); + parser.parse(emptySet, testJar); + parser.parse(emptySet, Resource.newResource(testClasses)); + List locations = parser.getParsedLocations("org.acme.ClassOne"); + Assert.assertNotNull(locations); + Assert.assertEquals(2, locations.size()); + Assert.assertTrue(!(locations.get(0).equals(locations.get(1)))); + } + + private void copyClass(Class clazz, File basedir) throws IOException { diff --git a/jetty-annotations/src/test/resources/jdk9/log4j-api-2.9.0.jar b/jetty-annotations/src/test/resources/jdk9/log4j-api-2.9.0.jar new file mode 100644 index 00000000000..ab98d40e9a2 Binary files /dev/null and b/jetty-annotations/src/test/resources/jdk9/log4j-api-2.9.0.jar differ diff --git a/jetty-annotations/src/test/resources/jdk9/slf4j-api-1.8.0-alpha2.jar b/jetty-annotations/src/test/resources/jdk9/slf4j-api-1.8.0-alpha2.jar new file mode 100644 index 00000000000..7a2a9b2d8e4 Binary files /dev/null and b/jetty-annotations/src/test/resources/jdk9/slf4j-api-1.8.0-alpha2.jar differ diff --git a/jetty-annotations/src/test/resources/tinytest.jar b/jetty-annotations/src/test/resources/tinytest.jar new file mode 100644 index 00000000000..4041f0e8a1b Binary files /dev/null and b/jetty-annotations/src/test/resources/tinytest.jar differ diff --git a/jetty-annotations/src/test/resources/tinytest_copy.jar b/jetty-annotations/src/test/resources/tinytest_copy.jar new file mode 100644 index 00000000000..db720508d0d Binary files /dev/null and b/jetty-annotations/src/test/resources/tinytest_copy.jar differ diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 5238f90728d..50e3f41f506 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -169,12 +169,12 @@
    org.eclipse.jetty.fcgi - jetty-fcgi-client + fcgi-client 10.0.0-SNAPSHOT org.eclipse.jetty.fcgi - jetty-fcgi-server + fcgi-server 10.0.0-SNAPSHOT @@ -262,11 +262,6 @@ jetty-memcached-sessions 10.0.0-SNAPSHOT - - org.eclipse.jetty - jetty-monitor - 10.0.0-SNAPSHOT - org.eclipse.jetty jetty-nosql diff --git a/jetty-cdi/test-cdi-it/pom.xml b/jetty-cdi/test-cdi-it/pom.xml index 2ab623e0b39..9f703d4fba4 100644 --- a/jetty-cdi/test-cdi-it/pom.xml +++ b/jetty-cdi/test-cdi-it/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.cdi jetty-cdi-parent - 9.4.7-SNAPSHOT + 10.0.0-SNAPSHOT 4.0.0 cdi-webapp-it diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 184af9e3fc4..abc94cd0229 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -73,6 +73,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; +import org.eclipse.jetty.util.thread.ThreadPool; /** *

    {@link HttpClient} provides an efficient, asynchronous, non-blocking implementation @@ -118,33 +119,33 @@ public class HttpClient extends ContainerLifeCycle private final ConcurrentMap destinations = new ConcurrentHashMap<>(); private final ProtocolHandlers handlers = new ProtocolHandlers(); private final List requestListeners = new ArrayList<>(); - private final AuthenticationStore authenticationStore = new HttpAuthenticationStore(); private final Set decoderFactories = new ContentDecoderFactorySet(); private final ProxyConfiguration proxyConfig = new ProxyConfiguration(); private final HttpClientTransport transport; private final SslContextFactory sslContextFactory; - private volatile CookieManager cookieManager; - private volatile CookieStore cookieStore; - private volatile Executor executor; - private volatile ByteBufferPool byteBufferPool; - private volatile Scheduler scheduler; - private volatile SocketAddressResolver resolver; - private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION); - private volatile boolean followRedirects = true; - private volatile int maxConnectionsPerDestination = 64; - private volatile int maxRequestsQueuedPerDestination = 1024; - private volatile int requestBufferSize = 4096; - private volatile int responseBufferSize = 16384; - private volatile int maxRedirects = 8; - private volatile SocketAddress bindAddress; - private volatile long connectTimeout = 15000; - private volatile long addressResolutionTimeout = 15000; - private volatile long idleTimeout; - private volatile boolean tcpNoDelay = true; - private volatile boolean strictEventOrdering = false; - private volatile HttpField encodingField; - private volatile boolean removeIdleDestinations = false; - private volatile boolean connectBlocking = false; + private AuthenticationStore authenticationStore = new HttpAuthenticationStore(); + private CookieManager cookieManager; + private CookieStore cookieStore; + private Executor executor; + private ByteBufferPool byteBufferPool; + private Scheduler scheduler; + private SocketAddressResolver resolver; + private HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION); + private boolean followRedirects = true; + private int maxConnectionsPerDestination = 64; + private int maxRequestsQueuedPerDestination = 1024; + private int requestBufferSize = 4096; + private int responseBufferSize = 16384; + private int maxRedirects = 8; + private SocketAddress bindAddress; + private long connectTimeout = 15000; + private long addressResolutionTimeout = 15000; + private long idleTimeout; + private boolean tcpNoDelay = true; + private boolean strictEventOrdering = false; + private HttpField encodingField; + private boolean removeIdleDestinations = false; + private boolean connectBlocking = false; /** * Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only @@ -204,9 +205,12 @@ public class HttpClient extends ContainerLifeCycle executor = threadPool; } addBean(executor); - + if (byteBufferPool == null) - byteBufferPool = new MappedByteBufferPool(); + byteBufferPool = new MappedByteBufferPool(2048, + executor instanceof ThreadPool.SizedThreadPool + ? ((ThreadPool.SizedThreadPool)executor).getMaxThreads()/2 + : Runtime.getRuntime().availableProcessors()*2); addBean(byteBufferPool); if (scheduler == null) @@ -302,6 +306,14 @@ public class HttpClient extends ContainerLifeCycle return authenticationStore; } + /** + * @param authenticationStore the authentication store associated with this instance + */ + public void setAuthenticationStore(AuthenticationStore authenticationStore) + { + this.authenticationStore = authenticationStore; + } + /** * Returns a non thread-safe set of {@link ContentDecoder.Factory}s that can be modified before * performing requests. diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java index cb2fa71af84..1dca5d058f6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java @@ -29,9 +29,7 @@ import javax.servlet.ServletException; 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.http.HttpField; +import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -53,22 +51,11 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseBegin(new Response.BeginListener() + .onResponseBegin(response -> response.abort(new Exception())) + .send(result -> { - @Override - public void onBegin(Response response) - { - response.abort(new Exception()); - } - }) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - Assert.assertTrue(result.isFailed()); - latch.countDown(); - } + Assert.assertTrue(result.isFailed()); + latch.countDown(); }); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -81,23 +68,15 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseHeader(new Response.HeaderListener() + .onResponseHeader((response, field) -> { - @Override - public boolean onHeader(Response response, HttpField field) - { - response.abort(new Exception()); - return true; - } + response.abort(new Exception()); + return true; }) - .send(new Response.CompleteListener() + .send(result -> { - @Override - public void onComplete(Result result) - { - Assert.assertTrue(result.isFailed()); - latch.countDown(); - } + Assert.assertTrue(result.isFailed()); + latch.countDown(); }); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -110,23 +89,11 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseHeaders(new Response.HeadersListener() + .onResponseHeaders(response -> response.abort(new Exception())) + .send(result -> { - @Override - public void onHeaders(Response response) - { - response.abort(new Exception()); - } - }) - .send(new Response.CompleteListener() - { - - @Override - public void onComplete(Result result) - { - Assert.assertTrue(result.isFailed()); - latch.countDown(); - } + Assert.assertTrue(result.isFailed()); + latch.countDown(); }); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -158,22 +125,11 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseContent(new Response.ContentListener() + .onResponseContent((response, content) -> response.abort(new Exception())) + .send(result -> { - @Override - public void onContent(Response response, ByteBuffer content) - { - response.abort(new Exception()); - } - }) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - Assert.assertTrue(result.isFailed()); - latch.countDown(); - } + Assert.assertTrue(result.isFailed()); + latch.countDown(); }); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -202,53 +158,31 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest } }); - final CountDownLatch abortLatch = new CountDownLatch(1); + final DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1)); final AtomicInteger completes = new AtomicInteger(); final CountDownLatch completeLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onRequestSuccess(new org.eclipse.jetty.client.api.Request.SuccessListener() + .content(contentProvider) + .onResponseContent((response, content) -> { - @Override - public void onSuccess(org.eclipse.jetty.client.api.Request request) + try { - try - { - abortLatch.await(5, TimeUnit.SECONDS); - } - catch (InterruptedException x) - { - x.printStackTrace(); - } + response.abort(new Exception()); + contentProvider.close(); + // Delay to let the request side to finish its processing. + Thread.sleep(1000); + } + catch (InterruptedException x) + { + x.printStackTrace(); } }) - .onResponseContent(new Response.ContentListener() + .send(result -> { - @Override - public void onContent(Response response, ByteBuffer content) - { - try - { - response.abort(new Exception()); - abortLatch.countDown(); - // Delay to let the request side to finish its processing. - Thread.sleep(1000); - } - catch (InterruptedException x) - { - x.printStackTrace(); - } - } - }) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - completes.incrementAndGet(); - Assert.assertTrue(result.isFailed()); - completeLatch.countDown(); - } + completes.incrementAndGet(); + Assert.assertTrue(result.isFailed()); + completeLatch.countDown(); }); Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); diff --git a/jetty-documentation/src/main/asciidoc/administration/logging/configuring-logging-modules.adoc b/jetty-documentation/src/main/asciidoc/administration/logging/configuring-logging-modules.adoc index 7f63f690ea1..f633dff41c3 100644 --- a/jetty-documentation/src/main/asciidoc/administration/logging/configuring-logging-modules.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/logging/configuring-logging-modules.adoc @@ -207,7 +207,7 @@ INFO : logging-log4j initialized in ${jetty.base}/start.d/logging-log4j.ini MKDIR : ${jetty.base}/lib/slf4j DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar MKDIR : ${jetty.base}/lib/log4j -COPY : /Users/chris/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar to ${jetty.base}/lib/log4j/log4j-1.2.17.jar +COPY : /Users/admin/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar to ${jetty.base}/lib/log4j/log4j-1.2.17.jar MKDIR : ${jetty.base}/resources COPY : ${jetty.home}/modules/log4j-impl/resources/log4j.xml to ${jetty.base}/resources/log4j.xml DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-log4j12-1.7.21.jar diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/chapter.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/chapter.adoc index 6f36fb4d379..92a8cbde8d3 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/chapter.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/chapter.adoc @@ -25,6 +25,8 @@ Jetty also offers more niche session managers that leverage backends such as Mon include::session-hierarchy.adoc[] include::sessions-details.adoc[] +include::session-configuration-housekeeper.adoc[] +include::session-configuration-sessioncache.adoc[] include::session-configuration-memory.adoc[] include::session-configuration-file-system.adoc[] include::session-configuration-jdbc.adoc[] @@ -32,6 +34,8 @@ include::session-configuration-mongodb.adoc[] include::session-configuration-infinispan.adoc[] include::session-configuration-hazelcast.adoc[] include::session-configuration-gcloud.adoc[] +include::session-configuration-memcachedsessiondatastore.adoc[] +include::sessions-usecases.adoc[] //include::setting-session-characteristics.adoc[] //include::using-persistent-sessions.adoc[] //include::session-clustering-jdbc.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-file-system.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-file-system.adoc index db7d3cf7fed..5f2ae862691 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-file-system.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-file-system.adoc @@ -16,7 +16,9 @@ [[configuring-sessions-file-system]] -=== Non-Clustered Session Management: File System +=== Persistent Sessions: File System + +Note: Persisting sessions to the local file system should *not* be used in a clustered environment. ==== Enabling File System Sessions diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-gcloud.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-gcloud.adoc index a1b95614edd..dc4bbe37bd5 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-gcloud.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-gcloud.adoc @@ -15,7 +15,8 @@ // ======================================================================== [[configuring-sessions-gcloud]] -=== Clustered Session Management: Google Cloud DataStore + +=== Persistent Sessions: Google Cloud DataStore ==== Preparation @@ -122,49 +123,49 @@ COPY : ${jetty.home}/modules/jul-impl/etc/java-util-logging.properties to ${jet MKDIR : ${jetty.base}/lib/slf4j DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar MKDIR : ${jetty.base}/lib/gcloud -COPY : /Users/chris/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar to ${jetty.base}/lib/gcloud/aopalliance-1.0.jar -COPY : /Users/chris/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.1.3/jackson-core-2.1.3.jar to ${jetty.base}/lib/gcloud/jackson-core-2.1.3.jar -COPY : /Users/chris/.m2/repository/com/google/api-client/google-api-client-appengine/1.21.0/google-api-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-appengine-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/api-client/google-api-client/1.20.0/google-api-client-1.20.0.jar to ${jetty.base}/lib/gcloud/google-api-client-1.20.0.jar -COPY : /Users/chris/.m2/repository/com/google/api-client/google-api-client-servlet/1.21.0/google-api-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-servlet-1.21.0.jar +COPY : /Users/admin/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar to ${jetty.base}/lib/gcloud/aopalliance-1.0.jar +COPY : /Users/admin/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.1.3/jackson-core-2.1.3.jar to ${jetty.base}/lib/gcloud/jackson-core-2.1.3.jar +COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client-appengine/1.21.0/google-api-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-appengine-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client/1.20.0/google-api-client-1.20.0.jar to ${jetty.base}/lib/gcloud/google-api-client-1.20.0.jar +COPY : /Users/admin/.m2/repository/com/google/api-client/google-api-client-servlet/1.21.0/google-api-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-api-client-servlet-1.21.0.jar DOWNLD: http://central.maven.org/maven2/com/google/api/gax/0.0.21/gax-0.0.21.jar to ${jetty.base}/lib/gcloud/gax-0.0.21.jar -COPY : /Users/chris/.m2/repository/com/google/api/grpc/grpc-google-common-protos/0.1.0/grpc-google-common-protos-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-common-protos-0.1.0.jar -COPY : /Users/chris/.m2/repository/com/google/api/grpc/grpc-google-iam-v1/0.1.0/grpc-google-iam-v1-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-iam-v1-0.1.0.jar -COPY : /Users/chris/.m2/repository/com/google/auth/google-auth-library-credentials/0.3.1/google-auth-library-credentials-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-credentials-0.3.1.jar -COPY : /Users/chris/.m2/repository/com/google/auth/google-auth-library-oauth2-http/0.3.1/google-auth-library-oauth2-http-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-oauth2-http-0.3.1.jar +COPY : /Users/admin/.m2/repository/com/google/api/grpc/grpc-google-common-protos/0.1.0/grpc-google-common-protos-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-common-protos-0.1.0.jar +COPY : /Users/admin/.m2/repository/com/google/api/grpc/grpc-google-iam-v1/0.1.0/grpc-google-iam-v1-0.1.0.jar to ${jetty.base}/lib/gcloud/grpc-google-iam-v1-0.1.0.jar +COPY : /Users/admin/.m2/repository/com/google/auth/google-auth-library-credentials/0.3.1/google-auth-library-credentials-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-credentials-0.3.1.jar +COPY : /Users/admin/.m2/repository/com/google/auth/google-auth-library-oauth2-http/0.3.1/google-auth-library-oauth2-http-0.3.1.jar to ${jetty.base}/lib/gcloud/google-auth-library-oauth2-http-0.3.1.jar DOWNLD: http://central.maven.org/maven2/com/google/auto/value/auto-value/1.2/auto-value-1.2.jar to ${jetty.base}/lib/gcloud/auto-value-1.2.jar DOWNLD: http://central.maven.org/maven2/com/google/cloud/datastore/datastore-v1-proto-client/1.3.0/datastore-v1-proto-client-1.3.0.jar to ${jetty.base}/lib/gcloud/datastore-v1-proto-client-1.3.0.jar DOWNLD: http://central.maven.org/maven2/com/google/cloud/datastore/datastore-v1-protos/1.3.0/datastore-v1-protos-1.3.0.jar to ${jetty.base}/lib/gcloud/datastore-v1-protos-1.3.0.jar DOWNLD: http://central.maven.org/maven2/com/google/cloud/google-cloud-core/0.5.1/google-cloud-core-0.5.1.jar to ${jetty.base}/lib/gcloud/google-cloud-core-0.5.0.jar DOWNLD: http://central.maven.org/maven2/com/google/cloud/google-cloud-datastore/0.5.1/google-cloud-datastore-0.5.1.jar to ${jetty.base}/lib/gcloud/google-cloud-datastore-0.5.1.jar -COPY : /Users/chris/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar to ${jetty.base}/lib/gcloud/jsr305-1.3.9.jar -COPY : /Users/chris/.m2/repository/com/google/code/gson/gson/2.3/gson-2.3.jar to ${jetty.base}/lib/gcloud/gson-2.3.jar -COPY : /Users/chris/.m2/repository/com/google/guava/guava/19.0/guava-19.0.jar to ${jetty.base}/lib/gcloud/guava-19.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client-appengine/1.21.0/google-http-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-appengine-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client-jackson2/1.19.0/google-http-client-jackson2-1.19.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson2-1.19.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client-jackson/1.21.0/google-http-client-jackson-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client/1.21.0/google-http-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client-jdo/1.21.0/google-http-client-jdo-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jdo-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/http-client/google-http-client-protobuf/1.20.0/google-http-client-protobuf-1.20.0.jar to ${jetty.base}/lib/gcloud/google-http-client-protobuf-1.20.0.jar -COPY : /Users/chris/.m2/repository/com/google/inject/guice/4.0/guice-4.0.jar to ${jetty.base}/lib/gcloud/guice-4.0.jar -COPY : /Users/chris/.m2/repository/com/google/oauth-client/google-oauth-client-appengine/1.21.0/google-oauth-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-appengine-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/oauth-client/google-oauth-client/1.21.0/google-oauth-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/oauth-client/google-oauth-client-servlet/1.21.0/google-oauth-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-servlet-1.21.0.jar -COPY : /Users/chris/.m2/repository/com/google/protobuf/protobuf-java/3.0.0/protobuf-java-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-3.0.0.jar -COPY : /Users/chris/.m2/repository/com/google/protobuf/protobuf-java-util/3.0.0/protobuf-java-util-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-util-3.0.0.jar -COPY : /Users/chris/.m2/repository/commons-codec/commons-codec/1.3/commons-codec-1.3.jar to ${jetty.base}/lib/gcloud/commons-codec-1.3.jar -COPY : /Users/chris/.m2/repository/io/grpc/grpc-context/1.0.1/grpc-context-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-context-1.0.1.jar -COPY : /Users/chris/.m2/repository/io/grpc/grpc-core/1.0.1/grpc-core-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-core-1.0.1.jar -COPY : /Users/chris/.m2/repository/io/grpc/grpc-protobuf/1.0.1/grpc-protobuf-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-1.0.1.jar -COPY : /Users/chris/.m2/repository/io/grpc/grpc-protobuf-lite/1.0.1/grpc-protobuf-lite-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-lite-1.0.1.jar -COPY : /Users/chris/.m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar to ${jetty.base}/lib/gcloud/javax.inject-1.jar -COPY : /Users/chris/.m2/repository/javax/jdo/jdo2-api/2.3-eb/jdo2-api-2.3-eb.jar to ${jetty.base}/lib/gcloud/jdo2-api-2.3-eb.jar -COPY : /Users/chris/.m2/repository/javax/transaction/transaction-api/1.1/transaction-api-1.1.jar to ${jetty.base}/lib/gcloud/transaction-api-1.1.jar -COPY : /Users/chris/.m2/repository/joda-time/joda-time/2.9.2/joda-time-2.9.2.jar to ${jetty.base}/lib/gcloud/joda-time-2.9.2.jar -COPY : /Users/chris/.m2/repository/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar to ${jetty.base}/lib/gcloud/httpclient-4.0.1.jar -COPY : /Users/chris/.m2/repository/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar to ${jetty.base}/lib/gcloud/httpcore-4.0.1.jar -COPY : /Users/chris/.m2/repository/org/codehaus/jackson/jackson-core-asl/1.9.11/jackson-core-asl-1.9.11.jar to ${jetty.base}/lib/gcloud/jackson-core-asl-1.9.11.jar -COPY : /Users/chris/.m2/repository/org/json/json/20151123/json-20151123.jar to ${jetty.base}/lib/gcloud/json-20151123.jar +COPY : /Users/admin/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar to ${jetty.base}/lib/gcloud/jsr305-1.3.9.jar +COPY : /Users/admin/.m2/repository/com/google/code/gson/gson/2.3/gson-2.3.jar to ${jetty.base}/lib/gcloud/gson-2.3.jar +COPY : /Users/admin/.m2/repository/com/google/guava/guava/19.0/guava-19.0.jar to ${jetty.base}/lib/gcloud/guava-19.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-appengine/1.21.0/google-http-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-appengine-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jackson2/1.19.0/google-http-client-jackson2-1.19.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson2-1.19.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jackson/1.21.0/google-http-client-jackson-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jackson-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client/1.21.0/google-http-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-jdo/1.21.0/google-http-client-jdo-1.21.0.jar to ${jetty.base}/lib/gcloud/google-http-client-jdo-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/http-client/google-http-client-protobuf/1.20.0/google-http-client-protobuf-1.20.0.jar to ${jetty.base}/lib/gcloud/google-http-client-protobuf-1.20.0.jar +COPY : /Users/admin/.m2/repository/com/google/inject/guice/4.0/guice-4.0.jar to ${jetty.base}/lib/gcloud/guice-4.0.jar +COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client-appengine/1.21.0/google-oauth-client-appengine-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-appengine-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client/1.21.0/google-oauth-client-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/oauth-client/google-oauth-client-servlet/1.21.0/google-oauth-client-servlet-1.21.0.jar to ${jetty.base}/lib/gcloud/google-oauth-client-servlet-1.21.0.jar +COPY : /Users/admin/.m2/repository/com/google/protobuf/protobuf-java/3.0.0/protobuf-java-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-3.0.0.jar +COPY : /Users/admin/.m2/repository/com/google/protobuf/protobuf-java-util/3.0.0/protobuf-java-util-3.0.0.jar to ${jetty.base}/lib/gcloud/protobuf-java-util-3.0.0.jar +COPY : /Users/admin/.m2/repository/commons-codec/commons-codec/1.3/commons-codec-1.3.jar to ${jetty.base}/lib/gcloud/commons-codec-1.3.jar +COPY : /Users/admin/.m2/repository/io/grpc/grpc-context/1.0.1/grpc-context-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-context-1.0.1.jar +COPY : /Users/admin/.m2/repository/io/grpc/grpc-core/1.0.1/grpc-core-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-core-1.0.1.jar +COPY : /Users/admin/.m2/repository/io/grpc/grpc-protobuf/1.0.1/grpc-protobuf-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-1.0.1.jar +COPY : /Users/admin/.m2/repository/io/grpc/grpc-protobuf-lite/1.0.1/grpc-protobuf-lite-1.0.1.jar to ${jetty.base}/lib/gcloud/grpc-protobuf-lite-1.0.1.jar +COPY : /Users/admin/.m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar to ${jetty.base}/lib/gcloud/javax.inject-1.jar +COPY : /Users/admin/.m2/repository/javax/jdo/jdo2-api/2.3-eb/jdo2-api-2.3-eb.jar to ${jetty.base}/lib/gcloud/jdo2-api-2.3-eb.jar +COPY : /Users/admin/.m2/repository/javax/transaction/transaction-api/1.1/transaction-api-1.1.jar to ${jetty.base}/lib/gcloud/transaction-api-1.1.jar +COPY : /Users/admin/.m2/repository/joda-time/joda-time/2.9.2/joda-time-2.9.2.jar to ${jetty.base}/lib/gcloud/joda-time-2.9.2.jar +COPY : /Users/admin/.m2/repository/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar to ${jetty.base}/lib/gcloud/httpclient-4.0.1.jar +COPY : /Users/admin/.m2/repository/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar to ${jetty.base}/lib/gcloud/httpcore-4.0.1.jar +COPY : /Users/admin/.m2/repository/org/codehaus/jackson/jackson-core-asl/1.9.11/jackson-core-asl-1.9.11.jar to ${jetty.base}/lib/gcloud/jackson-core-asl-1.9.11.jar +COPY : /Users/admin/.m2/repository/org/json/json/20151123/json-20151123.jar to ${jetty.base}/lib/gcloud/json-20151123.jar DOWNLD: http://central.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.21/jcl-over-slf4j-1.7.21.jar to ${jetty.base}/lib/slf4j/jcl-over-slf4j-1.7.21.jar COPY : ${jetty.home}/modules/gcloud/index.yaml to ${jetty.base}/etc/index.yaml INFO : Base directory was modified diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-hazelcast.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-hazelcast.adoc index a424404e149..4f79c24aac8 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-hazelcast.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-hazelcast.adoc @@ -16,7 +16,7 @@ [[configuring-sessions-hazelcast]] -=== Clustered Session Management: Hazelcast +=== Persistent Sessions: Hazelcast ==== Enabling Hazelcast Sessions @@ -24,10 +24,11 @@ When using the Jetty distribution, you will first need to enable the `session-st [source, screen, subs="{sub-order}"] ---- -mb-olamy:tmp-base olamy$ java -jar ../start.jar --create-startd +$ java -jar ../start.jar --create-startd MKDIR : ${jetty.base}/start.d INFO : Base directory was modified -mb-olamy:tmp-base olamy$ java -jar ../start.jar --add-to-start=session-store-hazelcast-remote + +$ java -jar ../start.jar --add-to-start=session-store-hazelcast-remote ALERT: There are enabled module(s) with licenses. The following 1 module(s): @@ -44,11 +45,11 @@ Proceed (y/N)? y INFO : server transitively enabled, ini template available with --add-to-start=server INFO : sessions transitively enabled, ini template available with --add-to-start=sessions INFO : session-store-hazelcast-remote initialized in ${jetty.base}/start.d/session-store-hazelcast-remote.ini -MKDIR : /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2 -DOWNLD: http://central.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar +MKDIR : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2 +DOWNLD: http://central.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar MKDIR : ${jetty.base}/lib/hazelcast -COPY : /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar -COPY : /Users/olamy/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar +COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar +COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar INFO : Base directory was modified ---- @@ -112,10 +113,10 @@ To enable this you will first need to enable the `session-store-hazelcast-embedd [source, screen, subs="{sub-order}"] ---- -mb-olamy:tmp-base olamy$ java -jar ../start.jar --create-startd +$ java -jar ../start.jar --create-startd MKDIR : ${jetty.base}/start.d INFO : Base directory was modified -mb-olamy:tmp-base olamy$ java -jar ../start.jar --add-to-start=session-store-hazelcast-embedded +$ java -jar ../start.jar --add-to-start=session-store-hazelcast-embedded ALERT: There are enabled module(s) with licenses. The following 1 module(s): @@ -132,11 +133,11 @@ Proceed (y/N)? y INFO : server transitively enabled, ini template available with --add-to-start=server INFO : sessions transitively enabled, ini template available with --add-to-start=sessions INFO : session-store-hazelcast-embedded initialized in ${jetty.base}/start.d/session-store-hazelcast-embedded.ini -MKDIR : /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2 -DOWNLD: http://central.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar +MKDIR : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2 +DOWNLD: http://central.maven.org/maven2/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar MKDIR : ${jetty.base}/lib/hazelcast -COPY : /Users/olamy/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar -COPY : /Users/olamy/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar +COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast/3.8.2/hazelcast-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-3.8.2.jar +COPY : /Users/admin/mvn-repo/com/hazelcast/hazelcast-client/3.8.2/hazelcast-client-3.8.2.jar to ${jetty.base}/lib/hazelcast/hazelcast-client-3.8.2.jar ---- Doing this enables the embedded Hazelcast Session module and any dependent modules or files needed for it to run on the server. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-housekeeper.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-housekeeper.adoc new file mode 100644 index 00000000000..587cf65cc2f --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-housekeeper.adoc @@ -0,0 +1,54 @@ +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +[[session-configuration-housekeeper]] +=== The SessionIdManager and the Housekeeper + +==== Default Settings +By default, Jetty will instantiate a single instance of the `DefaultSessionIdManager` and `HouseKeeper` at startup with default settings. + +The default settings are: + +DefaultSessionIdManager: worker name:: +This uniquely identifies the jetty server instance within a cluster. +It is set from the value of the `JETTY_WORKER_INSTANCE` environment variable, or `node0` if the environment value is not set. +If you have more than one Jetty instance, it is *crucial* that you explicitly configure the worker name on each Jetty instance (see link:#session-idmanager-housekeeper-config[below] for how to configure). + +HouseKeeper: scavenge interval:: +This is the period in seconds between runs of the session scavenger, and by default is set to the equivalent of 10 minutes. +As a rule of thumb, you should ensure that the scavenge interval is shorter than the `maxInactiveInterval` of your sessions to ensure that they are promptly scavenged. +See below for instructions on how to configure this. + +[[session-idmanager-housekeeper-config]] +==== Configuration +To change the default values, use the link:#startup-modules[module system] to link:#startup-modules[enable] the `sessions` module. + +This will enable the `$jetty.home/etc/sessions/id-manager.xml` file and generate a `$jetty.base/start.d/sessions.ini` file. + +The `id-manager.xml` file instantiates a single `DefaultSessionIdManager` and `HouseKeeper` and configures them using the properties from the `sessions.ini` file. + +Edit the ini file to change the properties to easily customize the `DefaultSessionIdManager` and `HouseKeeper`: + +jetty.sessionIdManager.workerName:: +By default it is `node1`. +This uniquely identifies the Jetty server instance within a cluster. +If you have more than one Jetty instance, it is crucial that you configure the worker name differently on each jetty instance. + + +jetty.sessionScavengeInterval.seconds:: +This is the period in seconds between runs of the session scavenger. +By default it will run every 600 secs (ie 10 mins). +As a rule of thumb, you should ensure that the scavenge interval is shorter than the maxInactiveInterval of your sessions to ensure that they are promptly scavenged. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-infinispan.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-infinispan.adoc index c4cad1f7669..8dd7827eed6 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-infinispan.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-infinispan.adoc @@ -16,12 +16,17 @@ [[configuring-sessions-infinispan]] -=== Clustered Session Management: Inifinspan +=== Persistent Sessions: Inifinspan ==== Enabling Infinispan Sessions When using the Jetty distribution, you will first need to enable the `session-store-infinispan-remote` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line. +____ +[IMPORTANT] +If you are running Jetty with JDK 9 or greater, enable `session-store-infinispan-remote-910.mod` instead. +____ + [source, screen, subs="{sub-order}"] ---- $ java -jar ../start.jar --create-startd @@ -106,6 +111,11 @@ ____ During testing, it can be helpful to run an in-process instance of Infinispan. To enable this you will first need to enable the `session-store-infinispan-embedded` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line. +____ +[IMPORTANT] +If you are running Jetty with JDK 9 or greater, enable `session-store-infinispan-embedded-910.mod` instead. +____ + [source, screen, subs="{sub-order}"] ---- java -jar ../start.jar --add-to-start=session-store-infinispan-embedded diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-jdbc.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-jdbc.adoc index 4902b880d33..4d095ba88dc 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-jdbc.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-jdbc.adoc @@ -16,7 +16,7 @@ [[configuring-sessions-jdbc]] -=== Clustered Session Management: JDBC +=== Persistent Sessions: JDBC ==== Enabling JDBC Sessions diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memcachedsessiondatastore.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memcachedsessiondatastore.adoc new file mode 100644 index 00000000000..9ccfd52553a --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memcachedsessiondatastore.adoc @@ -0,0 +1,62 @@ +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +[[session-configuration-memcachedsessiondatastore]] + +=== Persistent Sessions: The L2 Session Data Cache + +If your chosen persistence technology is slow, it can be helpful to locally cache the session data. +The `CachingSessionDataStore` is a special type of `SessionDataStore` that locally caches session data, which makes reads faster. It writes-through to your chosen type of `SessionDataStore` when session data changes. + +==== MemcachedSessionDataMap + +The `MemcachedSessionDataMap` uses `memcached` to perform caching. + +To enable it with the Jetty distribution, enable the `session-store-cache` link:#startup-modules[module], along with your chosen `session-store-xxxx` module, and optionally the `session-cache-hash` or `session-cache-null` modules. + +After enabling, the `$jetty.base/start.d/session-store-cache.ini` file will be generated: + +[source, screen, subs="{sub-order}"] +---- +--module=session-store-cache + + +## Session Data Cache type: xmemcached +session-data-cache=xmemcached +#jetty.session.memcached.host=localhost +#jetty.session.memcached.port=11211 +#jetty.session.memcached.expirySec= +#jetty.session.memcached.heartbeats=true +---- + + +The configuration properties are: + +jetty.session.memcached.host:: +Default value is `localhost`. +This is the host on which the memcached server resides. + +jetty.session.memcached.port:: +Default value is `11211`. +This is the port on which the memcached server is listening. + +jetty.session.memcached.expirySec:: +Default value `0`. +This is the length of time in seconds that an item can remain in the memcached cache, where 0 indicates indefinitely. + +jetty.session.memcached.heartbeats:: +Default value `true`. +Whether or not the memcached system should generate heartbeats. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memory.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memory.adoc index 3f72e4bdac9..3fb65192ec8 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memory.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-memory.adoc @@ -16,21 +16,16 @@ [[configuring-sessions-memory]] -=== Non-Clustered Session Management: Memory +=== Non-Persistent Sessions -Non-clustered, in-memory-only is the default style of Session Management. -In previous versions of Jetty this was referred to as "hash" sessions, as they were stored in a `HashMap` in memory. -When using the Jetty distribution, if you do not configure any session module, this will be enabled by default. +Non-clustered, non-persistent, in-memory-only is the default style of session management. +In previous versions of Jetty this was referred to as "hash" sessions, as they were stored in a `HashMap` in memory. -Specifically, Jetty will hook up: +This is delivered by a combination of the `DefaultSessionCache` (to keep sessions in memory) and a `NullSessionDataStore` (to avoid session persistence). -A `DefaultSessionIdManager`:: -* Produces unique session ids and supports cross-context dispatch re-use of session ids -A `HouseKeeper`:: -* Scavenges for expired sessions every 10 mins -A `DefaultSessionCache` per context:: -* Keeps session objects in memory -A `NullSessionDataStore` per context:: -* No persistence of sessions +If you do nothing, Jetty will instantiate one of each of these objects for each context at startup time using hard-coded defaults. -If you wish to change any of the default configuration, enable the `session-cache-hash` module. +To explicitly set up non-persisted sessions using modules, use both the `session-cache-hash` and the `session-store-null` modules. + +Enabling the modules allows you to configure behavior - see link:#session-configuration-sessioncache[the L1 Session Cache] for detailed information on configuration options for the `DefaultSessionCache`. +The `NullSessionDataStore` has no customizable options. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-mongodb.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-mongodb.adoc index 770b90456b1..c038acb96e7 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-mongodb.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-mongodb.adoc @@ -16,7 +16,7 @@ [[configuring-sessions-mongo]] -=== Clustered Session Management: MongoDB +=== Persistent Sessions: MongoDB ==== Enabling MongoDB Sessions diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-sessioncache.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-sessioncache.adoc new file mode 100644 index 00000000000..69b59acb044 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-configuration-sessioncache.adoc @@ -0,0 +1,73 @@ +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +[[session-configuration-sessioncache]] +=== The L1 Session Cache + +==== The DefaultSessionCache + +In the absence of any explicit configuration, Jetty will instantiate an instance of the `DefaultSessionCache` per context. +If you wish to change any of the default values, you need to enable the `session-cache-hash` link:#startup-modules[module]. + +Once the `session-cache-hash` module has been enabled, you can view a list of all the configurable values by opening `start.d/session-cache-hash.ini`: + +[source, screen, subs="{sub-order}"] +---- +--module=session-cache-hash + +#jetty.session.evictionPolicy=-1 +#jetty.session.saveOnInactiveEvict=false +#jetty.session.saveOnCreate=false +#jetty.session.removeUnloadableSessions=false +---- + +jetty.session.evictionPolicy:: +Integer. +Controls whether session objects that are held in memory are subject to eviction from the memory cache. +Evicting sessions can reduce the memory footprint of the cache. +Eviction is usually used in conjunction with a `SessionDataStore` that persists sessions. +Values are: +* -1 : sessions are never evicted from the cache +* 0 : sessions are evicted from the cache as soon as the last active request for it finishes +* >= 1 : any positive number is the time in seconds after which a session that is in the cache but has not experienced any activity will be evicted + +____ +[NOTE] +If you are not using a `SessionDataStore` that persists sessions, be aware that evicted sessions will be lost. +____ + +jetty.session.saveOnInactiveEvict:: +Boolean, default `false`. +Controls whether a session will be saved to the `SessionDataStore` just prior to its eviction. + +jetty.session.saveOnCreate:: +Boolean, default `false`. +Controls whether a session that is newly created will be immediately saved to the `SessionDataStore` or lazily saved as the last request for the session exits. + +jetty.session.removeUnloadableSessions:: +Boolean, default `false`. +Controls whether a session that cannot be restored - for example because it is corrupted - from the `SessionDataStore` is deleted by the `SessionDataStore`. + +For more general information on the uses of these configuration properties, see link:#sessions-details[Session Components]. + + +==== The NullSessionCache + +The `NullSessionCache` is a trivial implementation of the `SessionCache` that does not cache any session information. +You may need to use it if your clustering setup does not have a sticky load balancer, or if you want absolutely minimal support for sessions. +If you use this in conjunction with the `NullSessionDataStore`, then sessions will neither be retained in memory nor persisted. + +To enable the `NullSessionCache`, enable the `sesssion-cache-null` link:#startup-modules[module]. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/session-hierarchy.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/session-hierarchy.adoc index 7552dc01864..d5539736b82 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/session-hierarchy.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/session-hierarchy.adoc @@ -49,8 +49,11 @@ image::images/SessionsHierarchy.png[] ==== Configuring Sessions in the Jetty Distribution -Jetty provides support for several different Session Management technologies. -Both link:#configuring-sessions-file-system[local file storage] and in-memory session management can be implemented for standard implementations. -For implementations using clustered technologies, link:#configuring-sessions-jdbc[JDBC], link:#configuring-sessions-mongo[MongoDB], link:#configuring-sessions-infinispan[Inifinispan] and link:#configuring-sessions-gcloud[Google Cloud Datastore] are all supported. -Setting up these technologies is as easy as enabling it's link:#startup-modules[module] and editing it's associated ini file with any usernames, passwords or changes you need to make for your instance. -The following sections will cover how exactly to enable the required modules as well as an overview of what options are available for customization. +Configuring session management involves selecting a link:#startup-modules[module] for the desired type of link:#session-configuration-sessioncache[session caching] behavior, and a module for the type of session persistence. + +Jetty provides two different session caches: the `DefaultSessionCache` which holds sessions in memory, and the `NullSessionCache` which does not. +There is more information on both of these types of session caching and the circumstances which would lead you to select one or the other in the link:#sessions-details[Session Components] section, and more information on the configuration options of each in link:#session-configuration-sessioncache[the L1 Session Cache] section. + +For session persistence, Jetty provides a number of different implementations from which to choose including link:#configuring-sessions-memory[non-persistence], link:#configuring-sessions-file-system[local file storage], clustered technologies such as link:#configuring-sessions-jdbc[JDBC], link:#configuring-sessions-mongo[MongoDB], link:#configuring-sessions-infinispan[Inifinispan], link:#configuring-sessions-gcloud[Google Cloud Datastore], and link:#configuring-sessions-hazelcast[Hazelcast]. + +Depending on your persistence technology, to enhance performance, you may want to use an L2 cache for session data, in which case Jetty provides the link:#session-configuration-memcachedsessiondatastore[memcached L2 session data cache]. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-details.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-details.adoc index 20cb4a3eb5f..dac104a6f26 100644 --- a/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-details.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-details.adoc @@ -15,40 +15,43 @@ // ======================================================================== [[sessions-details]] -=== Session Configuration and Use Cases +=== Session Components -==== Configuration +==== SessionIdManager -===== SessionIdManager - -There is a maximum of 1 `SessionIdManager` per jetty Server instance. +There is a maximum of one (1) `SessionIdManager` per Jetty Server instance. Its purpose is to generate fresh, unique session ids and to coordinate the re-use of session ids amongst co-operating contexts. Unlike in previous versions of Jetty, the `SessionIdManager` is agnostic with respect to the type of clustering technology chosen. Jetty provides a default implementation - the `DefaultSessionIdManager` - which should meet the needs of most users. -If you do not explicitly enable one of the session modules, or otherwise configure a `SessionIdManager`, the `DefaultSessionIdManager` will be used. +If you do not explicitly enable one of the session modules or otherwise configure a `SessionIdManager`, the `DefaultSessionIdManager` will be used. If the `DefaultSessionIdManager` does not meet your needs, you can extend the `org.eclipse.jetty.server.session.AbstractSessionIdManager` or do a fresh implementation of the `org.eclipse.jetty.server.session.SessionIdManager` interface. -===== HouseKeeper +See link:#session-configuration-housekeeper[Configuring the SessionIdManager and HouseKeeper] for details on configuration. -There is a maximum of 1 `HouseKeeper` per `SessionIdManager`. +==== HouseKeeper + +There is a maximum of one (1) `HouseKeeper` per `SessionIdManager`. Its purpose is to periodically poll the `SessionHandlers` to clean out expired sessions. By default the `HouseKeeper` will poll the `SessionHandlers` every 10 mins to find and delete expired sessions, although this interval is configurable. +See link:#session-configuration-housekeeper[Configuring the SessionIdManager and HouseKeeper] for details on configuration. -===== SessionCache -There is 1 `SessionCache` per context. +==== SessionCache + +There is one (1) `SessionCache` *per context.* Its purpose is to provide an L1 cache of Session objects. Having a working set of Session objects in memory allows multiple simultaneous requests for the same session to share the same Session object. -Jetty provides 2 `SessionCache` implementations: the `DefaultSessionCache` and the `NullSessionCache`. -The `DefaultSessionCache` retains Session objects in memory in a cache and has a number of configuration options to control cache behavior. +Jetty provides two (2) `SessionCache` implementations: the `DefaultSessionCache` and the `NullSessionCache`. +The `DefaultSessionCache` retains Session objects in memory in a cache and has a number of link:#session-configuration-sessioncache[configuration options] to control cache behavior. It is the default that is used if no other `SessionCache` has been configured. It is suitable for non-clustered and clustered deployments with a sticky load balancer, as well as clustered deployments with a non-sticky load balancer, with some caveats. + The `NullSessionCache` does not actually cache any objects: each request uses a fresh Session object. It is suitable for clustered deployments without a sticky load balancer and non-clustered deployments when purely minimal support for sessions is needed. @@ -58,16 +61,19 @@ They can also be configured to do an immediate, eager write of a freshly created This can be useful if you are likely to experience multiple, near simultaneous requests referencing the same session, e.g. with HTTP/2 and you don't have a sticky load balancer. Alternatively, if the eager write is not done, application paths which create and then invalidate a session within a single request never incur the cost of writing to persistent storage. -Additionally, if the `EVICT_ON_INACTIVITY` eviction policy is in use, you can configure the `DefaultSessionCache` to force a write of the Session to the SessionDataStore just before the Session is evicted. +Additionally, if the `EVICT_ON_INACTIVITY` eviction policy is in use, you can link:#session-configuration-sessioncache[configure] the `DefaultSessionCache` to force a write of the Session to the `SessionDataStore` just before the Session is evicted. -===== SessionDataStore +See link:#session-configuration-sessioncache[the L1 Session Cache] for more information. -There is 1 `SessionDataStore` per context. Its purpose is to handle all persistence related operations on sessions. +==== SessionDataStore + +There is one (1) `SessionDataStore` per context. +Its purpose is to handle all persistence related operations on sessions. The common characteristics for all `SessionDataStores` are whether or not they support passivation, and the length of the grace period. Supporting passivation means that session data is serialized. -Some persistence mechanisms serialize, such as JDBC, GCloud Datastore etc, whereas others may store an object in shared memory eg Infinispan when configured with a local cache. +Some persistence mechanisms serialize, such as JDBC, GCloud Datastore etc, whereas others may store an object in shared memory, e.g. Infinispan, when configured with a local cache. Whether or not a clustering technology entails passivation controls whether or not the session passivation/activation listeners will be called. @@ -79,56 +85,17 @@ When `SessionDataStores` search their persistent store to find sessions that hav * The second finds sessions in the store that have expired which were last live on the current node * The third finds sessions that expired a "while" ago, irrespective of on which node they were last used: the definition of "a while" is based on the grace period. +Jetty instantiates the trivial `NullSessionDataStore` - which does not persist sessions - as the default. -===== CachingSessionDataStore +The distribution provides a number of alternative `SessionDataStore` implementations such as link:#configuring-sessions-file-system[FileSessionDataStore], link:#configuring-sessions-gcloud[GCloudSessionDataStore], link:#configuring-sessions-jdbc[JDBCSessionDataStore], link:#configuring-sessions-mongodb[MongoSessionDataStore], link:#configuring-sessions-infinispan[InfinispanSessionDataStore], link:#configuring-sessions-hazelcast[HazelcastSessionDataStore]. -The `CachingSessionDataStore` is a special type of `SessionDataStore` that inserts an L2 cache of SessionData - the `SessionDataMap` - in front of a delegate `SessionDataStore`. + +==== CachingSessionDataStore + +The `CachingSessionDataStore` is a special type of `SessionDataStore` that inserts an L2 cache of Session data - the `SessionDataMap` - in front of a delegate `SessionDataStore`. The `SessionDataMap` is preferentially consulted before the actual SessionDataStore on reads. This can improve the performance of slow stores. Jetty provides one implementation of the this L2 cache based on `Memcached`, the `MemcachedSessionDataMap`. - -==== Use Cases - -===== Clustering with a Sticky Load Balancer - -Preferably, your cluster will utilize a sticky load balancer. -This will route requests for the same session to the same Jetty instance. -In this case, the `DefaultSessionCache` can be used to keep in-use Session objects in memory. -You can fine-tune the cache by controlling how long Session objects remain in memory with the eviction policy settings. - -If you have a large number of Sessions or very large Session objects, then you might want to manage your memory allocation by controlling the amount of time Session objects spend in the cache. -The `EVICT_ON_SESSION_EXIT` eviction policy will remove a Session object from the cache as soon as the last simultaneous request referencing it exits. -Alternatively, the `EVICT_ON_INACTIVITY` policy will remove a Session object from the cache after a configurable amount of time has passed without a request referencing it. - -If your Sessions are very long lived and infrequently referenced, you might use the `EVICT_ON_INACTIVITY_POLICY` to control the size of the cache. - -If your Sessions are small, or relatively few or stable in number or they are read-mostly, then you might select the `NEVER_EVICT` policy. -With this policy, Session objects will remain in the cache until they either expire or are explicitly invalidated. - -If you have a high likelihood of simultaneous requests for the same session object, then the `EVICT_ON_SESSION_EXIT` policy will ensure the Session object stays in the cache as long as it is needed. - - -===== Clustering without a Sticky Load Balancer - -Without a sticky load balancer requests for the same session may arrive on any node in the cluster. -This means it is likely that the copy of the Session object in any `SessionCache` is likely to be out-of-date, as the Session was probably last accessed on a different node. -In this case, your `choices` are to use either the `NullSessionCache` or to de-tuned the `DefaultSessionCache`. -If you use the NullSessionCache all Session object caching is avoided. -This means that every time a request references a session it must be brought in from persistent storage. -It also means that there can be no sharing of Session objects for multiple requests for the same session: each will have their own Session object. -Furthermore, the outcome of session writes are indeterminate because the Servlet Specification does not mandate ACID transactions for sessions. - -If you use the `DefaultSessionCache`, there is a risk that the caches on some nodes will contain out-of-date session information as simultaneous requests for the same session are scattered over the cluster. -To mitigate this somewhat you can use the `EVICT_ON_SESSION_EXIT` eviction policy: this will ensure that the Session is removed from the cache as soon as the last simultaneous request for it exits. -Again, due to the lack of session transactionality, the ordering outcome of write operations cannot be guaranteed. -As the Session is cached while at least one request is accessing it, it is possible for multiple simultaneous requests to share the same Session object. - - -===== Handling corrupted or unloadable session data - -For various reasons it might not be possible for the SessionDataStore to re-read a stored session. -One scenario is that the session stores a serialized object in it's attributes, and after a redeployment there in an incompatible class change. -Using the setter `SessionCache.setRemoveUnloadableSessions(true)` will allow the `SessionDataStore` to delete the unreadable session from persistent storage. -This can be useful from preventing the scavenger from continually generating errors on the same expired, but un-restorable, session. +See link:#session-configuration-memcachedsessiondatastore[the L2 SessionData Cache]for additional information. diff --git a/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-usecases.adoc b/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-usecases.adoc new file mode 100644 index 00000000000..57600be6ad5 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/administration/sessions/sessions-usecases.adoc @@ -0,0 +1,60 @@ +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +[[sessions-usecases]] +=== Use Cases + +==== Clustering with a Sticky Load Balancer + +Preferably, your cluster will utilize a sticky load balancer. +This will route requests for the same Session to the same Jetty instance. +In this case, the `DefaultSessionCache` can be used to keep in-use Session objects in memory. +You can fine-tune the cache by controlling how long Session objects remain in memory with the eviction policy settings. + +If you have a large number of Sessions or very large Session objects, then you may want to manage your memory allocation by controlling the amount of time Session objects spend in the cache. +The `EVICT_ON_SESSION_EXIT` eviction policy will remove a Session object from the cache as soon as the last simultaneous request referencing it exits. +Alternatively, the `EVICT_ON_INACTIVITY` policy will remove a Session object from the cache after a configurable amount of time has passed without a request referencing it. + +If your Sessions are very long lived and infrequently referenced, you might use the `EVICT_ON_INACTIVITY_POLICY` to control the size of the cache. + +If your Sessions are small, or relatively few or stable in number or they are read-mostly, then you might select the `NEVER_EVICT` policy. +With this policy, Session objects will remain in the cache until they either expire or are explicitly invalidated. + +If you have a high likelihood of simultaneous requests for the same session object, then the `EVICT_ON_SESSION_EXIT` policy will ensure the Session object stays in the cache as long as it is needed. + + +==== Clustering Without a Sticky Load Balancer + +Without a sticky load balancer requests for the same session may arrive on any node in the cluster. +This means it is likely that the copy of the Session object in any `SessionCache` is likely to be out-of-date, as the Session was probably last accessed on a different node. +In this case, your `choices` are to use either the `NullSessionCache` or to de-tune the `DefaultSessionCache`. +If you use the NullSessionCache all Session object caching is avoided. +This means that every time a request references a session it must be brought in from persistent storage. +It also means that there can be no sharing of Session objects for multiple requests for the same session: each will have their own Session object. +Furthermore, the outcome of session writes are indeterminate because the Servlet Specification does not mandate ACID transactions for sessions. + +If you use the `DefaultSessionCache`, there is a risk that the caches on some nodes will contain out-of-date Session information as simultaneous requests for the same session are scattered over the cluster. +To mitigate this somewhat you can use the `EVICT_ON_SESSION_EXIT` eviction policy: this will ensure that the Session is removed from the cache as soon as the last simultaneous request for it exits. +Again, due to the lack of Session transactionality, the ordering outcome of write operations cannot be guaranteed. +As the Session is cached while at least one request is accessing it, it is possible for multiple simultaneous requests to share the same Session object. + + +==== Handling corrupted or unloadable session data + +For various reasons it might not be possible for the `SessionDataStore` to re-read a stored session. +One scenario is that the session stores a serialized object in it's attributes, and after a redeployment there in an incompatible class change. +Using the setter `SessionCache.setRemoveUnloadableSessions(true)` will allow the `SessionDataStore` to delete the unreadable session from persistent storage. +This can be useful from preventing the scavenger from continually generating errors on the same expired, but un-restorable, session. diff --git a/jetty-documentation/src/main/asciidoc/administration/startup/startup-base-vs-home.adoc b/jetty-documentation/src/main/asciidoc/administration/startup/startup-base-vs-home.adoc index 496c50c99c7..a5ae3e9784e 100644 --- a/jetty-documentation/src/main/asciidoc/administration/startup/startup-base-vs-home.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/startup/startup-base-vs-home.adoc @@ -115,8 +115,8 @@ Jetty Environment: Config Search Order: -------------------- - ${jetty.base} -> /home/user/jetty-distribution-9.4.1.v20170120/demo-base - ${jetty.home} -> /home/user/Desktop/jetty-distribution-9.4.1.v20170120 + ${jetty.base} -> /home/user/jetty-distribution-{VERSION}/demo-base + ${jetty.home} -> /home/user/Desktop/jetty-distribution-{VERSION} JVM Arguments: -------------- @@ -152,26 +152,26 @@ Jetty Server Classpath: Version Information on 42 entries in the classpath. Note: order presented here is how they would appear on the classpath. changes to the --module=name command line options will be reflected here. - 0: {VERSION} | ${jetty.home}/lib/jetty-client-{VERSION}.jar + 0: {VERSION} | ${jetty.home}/lib/jetty-client-{VERSION}.jar 1: 1.4.1.v201005082020 | ${jetty.base}/lib/ext/javax.mail.glassfish-1.4.1.v201005082020.jar - 2: {VERSION} | ${jetty.base}/lib/ext/test-mock-resources-{VERSION}.jar + 2: {VERSION} | ${jetty.base}/lib/ext/test-mock-resources-{VERSION}.jar 3: (dir) | ${jetty.home}/resources 4: 3.1.0 | ${jetty.home}/lib/servlet-api-3.1.jar 5: 3.1.RC0 | ${jetty.home}/lib/jetty-schemas-3.1.jar - 6: {VERSION} | ${jetty.home}/lib/jetty-http-{VERSION}.jar - 7: {VERSION} | ${jetty.home}/lib/jetty-continuation-{VERSION}.jar - 8: {VERSION} | ${jetty.home}/lib/jetty-server-{VERSION}.jar - 9: {VERSION} | ${jetty.home}/lib/jetty-xml-{VERSION}.jar -10: {VERSION} | ${jetty.home}/lib/jetty-util-{VERSION}.jar -11: {VERSION} | ${jetty.home}/lib/jetty-io-{VERSION}.jar -12: {VERSION} | ${jetty.home}/lib/jetty-jaas-{VERSION}.jar -13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar + 6: {VERSION} | ${jetty.home}/lib/jetty-http-{VERSION}.jar + 7: {VERSION} | ${jetty.home}/lib/jetty-continuation-{VERSION}.jar + 8: {VERSION} | ${jetty.home}/lib/jetty-server-{VERSION}.jar + 9: {VERSION} | ${jetty.home}/lib/jetty-xml-{VERSION}.jar +10: {VERSION} | ${jetty.home}/lib/jetty-util-{VERSION}.jar +11: {VERSION} | ${jetty.home}/lib/jetty-io-{VERSION}.jar +12: {VERSION} | ${jetty.home}/lib/jetty-jaas-{VERSION}.jar +13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar 14: 1.1.0.v201105071233 | ${jetty.home}/lib/jndi/javax.activation-1.1.0.v201105071233.jar 15: 1.4.1.v201005082020 | ${jetty.home}/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar 16: 1.2 | ${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar -17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar -18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar -19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar +17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar +18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar +19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar 20: 3.0.0 | ${jetty.home}/lib/jsp/javax.el-3.0.0.jar 21: 1.2.0.v201105211821 | ${jetty.home}/lib/jsp/javax.servlet.jsp.jstl-1.2.0.v201105211821.jar 22: 2.3.2 | ${jetty.home}/lib/jsp/javax.servlet.jsp-2.3.2.jar @@ -179,21 +179,21 @@ Note: order presented here is how they would appear on the classpath. 24: 2.3.3 | ${jetty.home}/lib/jsp/jetty-jsp-jdt-2.3.3.jar 25: 1.2.0.v201112081803 | ${jetty.home}/lib/jsp/org.apache.taglibs.standard.glassfish-1.2.0.v201112081803.jar 26: 3.8.2.v20130121-145325 | ${jetty.home}/lib/jsp/org.eclipse.jdt.core-3.8.2.v20130121.jar -27: {VERSION} | ${jetty.home}/lib/jetty-plus-{VERSION}.jar -28: {VERSION} | ${jetty.home}/lib/jetty-webapp-{VERSION}.jar -29: {VERSION} | ${jetty.home}/lib/jetty-annotations-{VERSION}.jar +27: {VERSION} | ${jetty.home}/lib/jetty-plus-{VERSION}.jar +28: {VERSION} | ${jetty.home}/lib/jetty-webapp-{VERSION}.jar +29: {VERSION} | ${jetty.home}/lib/jetty-annotations-{VERSION}.jar 30: 4.1 | ${jetty.home}/lib/annotations/asm-4.1.jar 31: 4.1 | ${jetty.home}/lib/annotations/asm-commons-4.1.jar 32: 1.2 | ${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar -33: {VERSION} | ${jetty.home}/lib/jetty-deploy-{VERSION}.jar +33: {VERSION} | ${jetty.home}/lib/jetty-deploy-{VERSION}.jar 34: 1.0 | ${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar -35: {VERSION} | ${jetty.home}/lib/websocket/javax-websocket-client-impl-{VERSION}.jar -36: {VERSION} | ${jetty.home}/lib/websocket/javax-websocket-server-impl-{VERSION}.jar -37: {VERSION} | ${jetty.home}/lib/websocket/websocket-api-{VERSION}.jar -38: {VERSION} | ${jetty.home}/lib/websocket/websocket-client-{VERSION}.jar -39: {VERSION} | ${jetty.home}/lib/websocket/websocket-common-{VERSION}.jar -40: {VERSION} | ${jetty.home}/lib/websocket/websocket-server-{VERSION}.jar -41: {VERSION} | ${jetty.home}/lib/websocket/websocket-servlet-{VERSION}.jar +35: {VERSION} | ${jetty.home}/lib/websocket/javax-websocket-client-impl-{VERSION}.jar +36: {VERSION} | ${jetty.home}/lib/websocket/javax-websocket-server-impl-{VERSION}.jar +37: {VERSION} | ${jetty.home}/lib/websocket/websocket-api-{VERSION}.jar +38: {VERSION} | ${jetty.home}/lib/websocket/websocket-client-{VERSION}.jar +39: {VERSION} | ${jetty.home}/lib/websocket/websocket-common-{VERSION}.jar +40: {VERSION} | ${jetty.home}/lib/websocket/websocket-server-{VERSION}.jar +41: {VERSION} | ${jetty.home}/lib/websocket/websocket-servlet-{VERSION}.jar Jetty Active XMLs: ------------------ diff --git a/jetty-documentation/src/main/asciidoc/administration/tuning/garbage-collection.adoc b/jetty-documentation/src/main/asciidoc/administration/tuning/garbage-collection.adoc index d43818107e5..a5fcf49f10f 100644 --- a/jetty-documentation/src/main/asciidoc/administration/tuning/garbage-collection.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/tuning/garbage-collection.adoc @@ -17,57 +17,39 @@ [[garbage-collection]] === Garbage Collection -Tuning the JVM garbage collection (GC) can greatly improve Jetty performance. -Specifically, you can avoid pauses while the system performs full garbage collections. -Optimal tuning of the GC depends on the behavior of the application and requires detailed analysis, however there are general recommendations. +Tuning the JVM garbage collection (GC) can greatly improve the performance of the JVM where Jetty and your application are running. +Optimal tuning of the GC depends on the behavior of the application(s) and requires detailed analysis, but there are general recommendations to follow to at least obtain comprehensive GC logs that can be later analyzed. -See official https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/[Java 8 Garbage Collection documentation] for further assistance. +See official https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/[Java 8] and https://docs.oracle.com/javase/9/gctuning/introduction-garbage-collection-tuning.htm[Java 9] Garbage Collection documentation for further assistance. -[[tuning-examples]] -==== Tuning Examples +[[garbage-collection-logging-configuration]] +==== Garbage Collection Logging Configuration -These options are general to the Oracle JVM, and work in a Java 8 installation. -They provide good information about how your JVM is running; based on that initial information, you can then tune more finely. - -The most important thing you can do for yourself when considering GC is to capture the GC activity so that you can analyze what is happening and how often it happens. +These options are general to OpenJDK (and therefore also for the Oracle JVM). +They provide good information about the GC activity of your JVM, producing logs that can later be analyzed to perform finer tuning. +.JDK 8 Garbage Collection Logging Configuration [source,screen, subs="{sub-order}"] .... --verbose:gc -Xloggc:/path/to/myjettybase/logs/gc.log -XX:+PrintGCDateStamps --XX:+PrintGCTimeStamps -XX:+PrintGCDetails --XX:+PrintTenuringDistribution --XX:+PrintCommandLineFlags +-XX:+ParallelRefProcEnabled -XX:+PrintReferenceGC +-XX:+PrintTenuringDistribution -XX:+PrintAdaptiveSizePolicy .... -There are not many recommended options for GC that can apply to nearly all users. +.JDK 9 Garbage Collection Logging Configuration +[source,screen, subs="{sub-order}"] +.... +Xlog:gc*,ergo*=trace,ref*=debug,age*=trace:file=/path/to/myjettybase/logs/gc.log:time,level,tags +.... +There are not many recommended options for GC that can apply to all users. However, the most obvious one is to disable explicit GC (this is performed regularly by RMI and can introduce an abnormal amount of GC pauses). [source,screen, subs="{sub-order}"] .... -XX:+DisableExplicitGC .... - -Before you apply any other GC tuning options, monitor your GC logs to see if https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html[tuning the CMS] makes sense for your environment. - -A common setup for those just starting out with GC tuning is included below for reference. - -____ -[CAUTION] -This example configuration below could have a negative effect on your application performance, so continued monitoring of your GC log before and after is highly recommended to know if the configuration was beneficial or not. -____ - -[source,screen, subs="{sub-order}"] -.... --XX:MaxGCPauseMillis=250 --XX:+UseConcMarkSweepGC --XX:ParallelCMSThreads=2 --XX:+CMSClassUnloadingEnabled --XX:+UseCMSCompactAtFullCollection --XX:CMSInitiatingOccupancyFraction=80 -.... diff --git a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc index fd749015d61..c2ecdc26aef 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc @@ -20,7 +20,7 @@ This document provides an overview of how to configure SSL and TLS for Jetty. [[configuring-jetty-for-ssl]] -===== Configuring Jetty for SSL +==== Configuring Jetty for SSL To configure Jetty for SSL, complete the tasks in the following sections: diff --git a/jetty-documentation/src/main/asciidoc/configuring/security/serving-aliased-files.adoc b/jetty-documentation/src/main/asciidoc/configuring/security/serving-aliased-files.adoc index 35030473996..3fb6e507d7c 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/security/serving-aliased-files.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/security/serving-aliased-files.adoc @@ -60,8 +60,8 @@ Unfortunately this approach denies all aliases, including symbolic links, which ==== Serving Aliases and Symbolic Links Not all aliases are bad nor should be seen as attempts to subvert security constraints. -Specifically symbolic links can be very useful when assembling complex web applications, yet by default Jetty will not serve them. -Thus Jetty contexts support an extensible `AliasCheck` mechanism to allow aliases resources to be inspected an conditionally served. +Specifically, symbolic links can be very useful when assembling complex web applications. +As such, Jetty contexts support an extensible `AliasCheck` mechanism to allow aliases resources to be inspected and conditionally served. In this way, "good" aliases can be detected and served. Jetty provides several utility implementations of the `AliasCheck` interface as nested classes with `ContextHandler`: @@ -70,6 +70,11 @@ ApproveAliases:: AllowSymLinkAliasChecker:: Approve Aliases using the java-7 `Files.readSymbolicLink(path)` and `Path.toRealPath(...)` APIs to check that aliases are valid symbolic links. +____ +[NOTE] +By default, Jetty serves aliased files for implementations running on UNIX as Contexts are created with both the {JDURL}/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.html[`AllowSymLinkAliasChecker`] and {JDURL}/org/eclipse/jetty/server/handler/ContextHandler.ApproveNonExistentDirectoryAliases.html[`ApproveNonExistentDirectoryAliases`] alias checkers. +____ + An application is free to implement its own Alias checking. Alias Checkers can be installed in a context via the following XML used in a context deployer file or `WEB-INF/jetty-web.xml`: diff --git a/jetty-documentation/src/main/asciidoc/quick-start/getting-started/jetty-running.adoc b/jetty-documentation/src/main/asciidoc/quick-start/getting-started/jetty-running.adoc index 99694c15ef0..3d579bc0bf8 100644 --- a/jetty-documentation/src/main/asciidoc/quick-start/getting-started/jetty-running.adoc +++ b/jetty-documentation/src/main/asciidoc/quick-start/getting-started/jetty-running.adoc @@ -26,8 +26,8 @@ To start Jetty on the default port of 8080, run the following command: 2015-06-04 10:50:44.806:INFO::main: Logging initialized @334ms 2015-06-04 10:50:44.858:WARN:oejs.HomeBaseWarning:main: This instance of Jetty is not running from a separate {jetty.base} directory, this is not recommended. See documentation at http://www.eclipse.org/jetty/documentation/current/startup.html -2015-06-04 10:50:44.995:INFO:oejs.Server:main: jetty-9.3.0.v20150601 -2015-06-04 10:50:45.012:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///opt/jetty-distribution-9.3.0.v20150601/webapps/] at interval 1 +2015-06-04 10:50:44.995:INFO:oejs.Server:main: jetty-{VERSION} +2015-06-04 10:50:45.012:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///opt/jetty-distribution-{VERSION}/webapps/] at interval 1 2015-06-04 10:50:45.030:INFO:oejs.ServerConnector:main: Started ServerConnector@19dfb72a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} 2015-06-04 10:50:45.030:INFO:oejs.Server:main: Started @558ms ---- @@ -57,13 +57,13 @@ Within the standard Jetty distribution there is the `demo-base` directory, which 2017-08-16 16:55:15.571:INFO::main: Logging initialized @521ms to org.eclipse.jetty.util.log.StdErrLog 2017-08-16 16:55:15.907:WARN::main: demo test-realm is deployed. DO NOT USE IN PRODUCTION! -2017-08-16 16:55:15.910:INFO:oejs.Server:main: jetty-9.4.7-SNAPSHOT -2017-08-16 16:55:15.931:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/webapps/] at interval 1 +2017-08-16 16:55:15.910:INFO:oejs.Server:main: jetty-{VERSION} +2017-08-16 16:55:15.931:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/jetty-distribution-{VERSION}/demo-base/webapps/] at interval 1 2017-08-16 16:55:16.151:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=50ms 2017-08-16 16:55:16.369:INFO:oejs.session:main: DefaultSessionIdManager workerName=node0 2017-08-16 16:55:16.369:INFO:oejs.session:main: No SessionScavenger set, using defaults 2017-08-16 16:55:16.370:INFO:oejs.session:main: Scavenging every 660000ms -2017-08-16 16:55:16.416:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@7113b13f{/,file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/webapps/ROOT/,AVAILABLE}{/ROOT} +2017-08-16 16:55:16.416:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@7113b13f{/,file:///tmp/jetty-distribution-{VERSION}/demo-base/webapps/ROOT/,AVAILABLE}{/ROOT} 2017-08-16 16:55:16.625:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=82ms 2017-08-16 16:55:16.631:WARN::main: test webapp is deployed. DO NOT USE IN PRODUCTION! 2017-08-16 16:55:16.751:INFO:oejsh.ManagedAttributeListener:main: update PushFilter null->org.eclipse.jetty.servlets.PushCacheFilter@1a677343 on o.e.j.w.WebAppContext@2d7275fc{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-7157753932050220016.dir/webapp/,STARTING}{/test.war} @@ -72,7 +72,7 @@ Within the standard Jetty distribution there is the `demo-base` directory, which 2017-08-16 16:55:16.809:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@2d7275fc{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-7157753932050220016.dir/webapp/,AVAILABLE}{/test.war} 2017-08-16 16:55:16.816:INFO:oejsh.ContextHandler:main: Started o.e.j.s.h.MovedContextHandler@7c9d8e2{/oldContextPath,null,AVAILABLE} 2017-08-16 16:55:16.854:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=23ms -2017-08-16 16:55:16.891:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@69453e37{/doc,file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/webapps/doc/,AVAILABLE}{/doc} +2017-08-16 16:55:16.891:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@69453e37{/doc,file:///tmp/jetty-distribution-{VERSION}/demo-base/webapps/doc/,AVAILABLE}{/doc} 2017-08-16 16:55:16.942:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=25ms 2017-08-16 16:55:16.945:WARN::main: test-jaas webapp is deployed. DO NOT USE IN PRODUCTION! 2017-08-16 16:55:16.983:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@4e3958e7{/test-jaas,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-jaas.war-_test-jaas-any-6953571893682159674.dir/webapp/,AVAILABLE}{/test-jaas.war} @@ -81,15 +81,15 @@ Within the standard Jetty distribution there is the `demo-base` directory, which 2017-08-16 16:55:17.192:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@1d8bd0de{/test-jndi,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-jndi.war-_test-jndi-any-1246461885510956986.dir/webapp/,AVAILABLE}{/test-jndi.war} 2017-08-16 16:55:17.307:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=53ms 2017-08-16 16:55:17.310:WARN::main: test-spec webapp is deployed. DO NOT USE IN PRODUCTION! -2017-08-16 16:55:17.388:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@51dcb805{/test-spec,[file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-spec.war-_test-spec-any-3750193079644252256.dir/webapp/, jar:file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-spec.war-_test-spec-any-3750193079644252256.dir/webapp/WEB-INF/lib/test-web-fragment-9.4.7-SNAPSHOT.jar!/META-INF/resources],AVAILABLE}{/test-spec.war} +2017-08-16 16:55:17.388:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@51dcb805{/test-spec,[file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-spec.war-_test-spec-any-3750193079644252256.dir/webapp/, jar:file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test-spec.war-_test-spec-any-3750193079644252256.dir/webapp/WEB-INF/lib/test-web-fragment-{VERSION}.jar!/META-INF/resources],AVAILABLE}{/test-spec.war} 2017-08-16 16:55:17.490:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=53ms 2017-08-16 16:55:17.493:WARN::main: async-rest webapp is deployed. DO NOT USE IN PRODUCTION! -2017-08-16 16:55:17.516:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@1de76cc7{/async-rest,[file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-8972552397332323832.dir/webapp/, jar:file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-8972552397332323832.dir/webapp/WEB-INF/lib/example-async-rest-jar-9.4.7-SNAPSHOT.jar!/META-INF/resources],AVAILABLE}{/async-rest.war} +2017-08-16 16:55:17.516:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@1de76cc7{/async-rest,[file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-8972552397332323832.dir/webapp/, jar:file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-8972552397332323832.dir/webapp/WEB-INF/lib/example-async-rest-jar-{VERSION}.jar!/META-INF/resources],AVAILABLE}{/async-rest.war} 2017-08-16 16:55:17.643:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=83ms 2017-08-16 16:55:17.921:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@242b836{/proxy,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-javadoc-proxy.war-_javadoc-proxy-any-4521643038409884891.dir/webapp/,AVAILABLE}{/javadoc-proxy.war} 2017-08-16 16:55:17.936:INFO:oejs.AbstractConnector:main: Started ServerConnector@6f15d60e{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} -2017-08-16 16:55:17.944:INFO:oejus.SslContextFactory:main: x509=X509@58e1d9d(jetty,h=[jetty.eclipse.org],w=[]) for SslContextFactory@446a1e84(file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/etc/keystore,file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/etc/keystore) -2017-08-16 16:55:17.944:INFO:oejus.SslContextFactory:main: x509=X509@4f0f2942(mykey,h=[],w=[]) for SslContextFactory@446a1e84(file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/etc/keystore,file:///tmp/jetty-distribution-9.4.7-SNAPSHOT/demo-base/etc/keystore) +2017-08-16 16:55:17.944:INFO:oejus.SslContextFactory:main: x509=X509@58e1d9d(jetty,h=[jetty.eclipse.org],w=[]) for SslContextFactory@446a1e84(file:///tmp/jetty-distribution-{VERSION}/demo-base/etc/keystore,file:///tmp/jetty-distribution-{VERSION}/demo-base/etc/keystore) +2017-08-16 16:55:17.944:INFO:oejus.SslContextFactory:main: x509=X509@4f0f2942(mykey,h=[],w=[]) for SslContextFactory@446a1e84(file:///tmp/jetty-distribution-{VERSION}/demo-base/etc/keystore,file:///tmp/jetty-distribution-{VERSION}/demo-base/etc/keystore) 2017-08-16 16:55:18.071:INFO:oejs.AbstractConnector:main: Started ServerConnector@41488b16{SSL,[ssl, http/1.1]}{0.0.0.0:8443} 2017-08-16 16:55:18.072:INFO:oejs.Server:main: Started @3022ms ---- @@ -179,7 +179,7 @@ INFO: Base directory was modified 2015-06-04 11:10:16.460:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/mybase/webapps/] at interval 1 2015-06-04 11:10:16.581:WARN::main: async-rest webapp is deployed. DO NOT USE IN PRODUCTION! 2015-06-04 11:10:16.589:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet -2015-06-04 11:10:16.628:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@1a407d53{/,[file:///tmp/jetty-0.0.0.0-8080-ROOT.war-_-any-4510228025526425427.dir/webapp/, jar:file:///tmp/jetty-0.0.0.0-8080-ROOT.war-_-any-4510228025526425427.dir/webapp/WEB-INF/lib/example-async-rest-jar-9.3.0.v20150601.jar!/META-INF/resources],AVAILABLE}{/ROOT.war} +2015-06-04 11:10:16.628:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@1a407d53{/,[file:///tmp/jetty-0.0.0.0-8080-ROOT.war-_-any-4510228025526425427.dir/webapp/, jar:file:///tmp/jetty-0.0.0.0-8080-ROOT.war-_-any-4510228025526425427.dir/webapp/WEB-INF/lib/example-async-rest-jar-{VERSION}.jar!/META-INF/resources],AVAILABLE}{/ROOT.war} 2015-06-04 11:10:16.645:INFO:oejs.ServerConnector:main: Started ServerConnector@3abbfa04{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} 2015-06-04 11:10:16.646:INFO:oejs.Server:main: Started @634ms ---- diff --git a/jetty-documentation/src/main/asciidoc/reference/architecture/jetty-classloading.adoc b/jetty-documentation/src/main/asciidoc/reference/architecture/jetty-classloading.adoc index 5fcec472e60..8e66e7cc8a0 100644 --- a/jetty-documentation/src/main/asciidoc/reference/architecture/jetty-classloading.adoc +++ b/jetty-documentation/src/main/asciidoc/reference/architecture/jetty-classloading.adoc @@ -86,7 +86,7 @@ The default system classes are: .Default System Classes [width="100%",cols="8%,92%",options="header",] |======================================================================= -|System Classes +|System Classes | Note |java. |Java SE classes (per servlet spec v2.5 / SRV.9.7.2). |javax. |Java SE classes (per servlet spec v2.5 / SRV.9.7.2). |org.xml. |Needed by javax.xml. @@ -98,7 +98,7 @@ The default system classes are: |org.eclipse.jetty.servlet.DefaultServlet |Webapp can see and not change default servlet. |======================================================================= -Absolute classname can be passed, names ending with . are treated as packages names, and names starting with - are treated as negative matches and must be listed before any enclosing packages. +Absolute classname can be passed, names ending with `.` are treated as packages names, and names starting with `-` are treated as negative matches and must be listed before any enclosing packages. [[setting-server-classes]] ===== Setting Server Classes diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index 7cfb6decb80..ad6d81adb8a 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -1,5 +1,7 @@ - + org.eclipse.jetty.gcloud gcloud-parent @@ -22,12 +24,12 @@ ${gcloud.version} - javax.servlet - servlet-api + javax.servlet + servlet-api - javax.servlet - javax.servlet-api + javax.servlet + javax.servlet-api @@ -57,50 +59,51 @@ - - ${project.groupId}.session - + + ${project.groupId}.session + - org.apache.felix - maven-bundle-plugin - true - - - - manifest - - - - org.eclipse.jetty.gcloud.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"; - - - - + org.apache.felix + maven-bundle-plugin + true + + + + manifest + + + + + org.eclipse.jetty.gcloud.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"; + + + + + - org.apache.maven.plugins - maven-dependency-plugin - 3.0.0 - - - build-deps-file - generate-resources - - list - - - false - ${project.build.directory}/deps.txt - true - org.eclipse.jetty - true - runtime - - - + org.apache.maven.plugins + maven-dependency-plugin + + + build-deps-file + generate-resources + + list + + + false + ${project.build.directory}/deps.txt + true + org.eclipse.jetty + true + runtime + + + org.apache.maven.plugins @@ -118,15 +121,15 @@ match=" *(.*):(.*):jar:(.*):[a-z]*" replace="maven://\1/\2/\3|lib/gcloud/\2-\3.jar" byline="true" - /> + /> - - + + process-mod process-resources @@ -141,8 +144,7 @@ - - + org.apache.maven.plugins @@ -161,7 +163,7 @@ - + diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index d31c3d46106..305a607eafc 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -486,6 +486,16 @@ jetty-alpn-server ${project.version} + + org.eclipse.jetty + jetty-alpn-openjdk8-server + ${project.version} + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${project.version} + org.eclipse.jetty jetty-jaspi diff --git a/jetty-home/src/main/resources/modules/conscrypt.mod b/jetty-home/src/main/resources/modules/conscrypt.mod index 9d2158ea66e..b4ac145a033 100644 --- a/jetty-home/src/main/resources/modules/conscrypt.mod +++ b/jetty-home/src/main/resources/modules/conscrypt.mod @@ -7,21 +7,26 @@ Installs the Conscrypt JSSE provider [depend] ssl +[provides] +alpn-impl + [files] maven://org.conscrypt/conscrypt-openjdk-uber/${conscrypt.version}|lib/conscrypt/conscrypt-uber-${conscrypt.version}.jar +#maven://org.conscrypt/conscrypt-openjdk/${conscrypt.version}/jar/linux-x86_64|lib/conscrypt/conscrypt-${conscrypt.version}-linux-x86_64.jar basehome:modules/conscrypt/conscrypt.xml|etc/conscrypt.xml -[lib] -lib/conscrypt/**.jar - [xml] etc/conscrypt.xml +[lib] +lib/conscrypt/**.jar +lib/jetty-alpn-conscrypt-server-${jetty.version}.jar + [license] Conscrypt is distributed under the Apache Licence 2.0 https://github.com/google/conscrypt/blob/master/LICENSE [ini] -conscrypt.version?=1.0.0.RC9 -jetty.sslContext.provider?=AndroidOpenSSL +conscrypt.version?=1.0.0.RC10 +jetty.sslContext.provider?=Conscrypt diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 012139f42c3..914880d2dab 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -98,6 +99,12 @@ public class HttpFields implements Iterable { return new Itr(); } + + public ListIterator listIterator() + { + return new Itr(); + } + public Stream stream() { @@ -935,7 +942,7 @@ public class HttpFields implements Iterable return value.substring(0, i).trim(); } - private class Itr implements Iterator + private class Itr implements ListIterator { int _cursor; // index of next element to return int _last=-1; @@ -963,6 +970,49 @@ public class HttpFields implements Iterable _cursor=_last; _last=-1; } + + @Override + public boolean hasPrevious() + { + return _cursor>0; + } + + @Override + public HttpField previous() + { + if (_cursor == 0) + throw new NoSuchElementException(); + return _fields[_last=--_cursor]; + } + + @Override + public int nextIndex() + { + return _cursor+1; + } + + @Override + public int previousIndex() + { + return _cursor-1; + } + + @Override + public void set(HttpField field) + { + if (_last<0) + throw new IllegalStateException(); + _fields[_last] = field; + } + + @Override + public void add(HttpField field) + { + _fields = Arrays.copyOf(_fields,_fields.length+1); + System.arraycopy(_fields,_cursor,_fields,_cursor+1,_size++); + _fields[_cursor++] = field; + _last=-1; + } } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index f16cdd43869..af0054952b1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.ArrayTrie; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -601,8 +602,8 @@ public class HttpParser while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes) @@ -625,7 +626,7 @@ public class HttpParser switch (_state) { case METHOD: - if (ch == SPACE) + if (b == SPACE) { _length=_string.length(); _methodString=takeString(); @@ -634,19 +635,19 @@ public class HttpParser _methodString=legacyString(_methodString,method.asString()); setState(State.SPACE1); } - else if (ch < SPACE) + else if (b < SPACE) { - if (ch==LINE_FEED) + if (b==LINE_FEED) throw new BadMessageException("No URI"); else - throw new IllegalCharacterException(_state,ch,buffer); + throw new IllegalCharacterException(_state,b,buffer); } else - _string.append((char)ch); + _string.append((char)b); break; case RESPONSE_VERSION: - if (ch == HttpTokens.SPACE) + if (b == HttpTokens.SPACE) { _length=_string.length(); String version=takeString(); @@ -655,19 +656,19 @@ public class HttpParser throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version"); setState(State.SPACE1); } - else if (ch < HttpTokens.SPACE) - throw new IllegalCharacterException(_state,ch,buffer); + else if (b < HttpTokens.SPACE) + throw new IllegalCharacterException(_state,b,buffer); else - _string.append((char)ch); + _string.append((char)b); break; case SPACE1: - if (ch > HttpTokens.SPACE || ch<0) + if (b > HttpTokens.SPACE || b<0) { if (_responseHandler!=null) { setState(State.STATUS); - setResponseStatus(ch-'0'); + setResponseStatus(b-'0'); } else { @@ -695,25 +696,25 @@ public class HttpParser buffer.position(i-buffer.arrayOffset()); } else - _uri.append(ch); + _uri.append(b); } } - else if (ch < HttpTokens.SPACE) + else if (b < HttpTokens.SPACE) { throw new BadMessageException(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status"); } break; case STATUS: - if (ch == HttpTokens.SPACE) + if (b == HttpTokens.SPACE) { setState(State.SPACE2); } - else if (ch>='0' && ch<='9') + else if (b>='0' && b<='9') { - _responseStatus=_responseStatus*10+(ch-'0'); + _responseStatus=_responseStatus*10+(b-'0'); } - else if (ch < HttpTokens.SPACE && ch>=0) + else if (b < HttpTokens.SPACE && b>=0) { setState(State.HEADER); handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle; @@ -725,11 +726,11 @@ public class HttpParser break; case URI: - if (ch == HttpTokens.SPACE) + if (b == HttpTokens.SPACE) { setState(State.SPACE2); } - else if (ch < HttpTokens.SPACE && ch>=0) + else if (b < HttpTokens.SPACE && b>=0) { // HTTP/0.9 if (complianceViolation(RFC7230,"HTTP/0.9")) @@ -741,15 +742,15 @@ public class HttpParser } else { - _uri.append(ch); + _uri.append(b); } break; case SPACE2: - if (ch > HttpTokens.SPACE) + if (b > HttpTokens.SPACE) { _string.setLength(0); - _string.append((char)ch); + _string.append((char)b); if (_responseHandler!=null) { _length=1; @@ -789,7 +790,7 @@ public class HttpParser } } } - else if (ch == HttpTokens.LINE_FEED) + else if (b == HttpTokens.LINE_FEED) { if (_responseHandler!=null) { @@ -808,12 +809,12 @@ public class HttpParser handle= handleHeaderContentMessage() || handle; } } - else if (ch<0) + else if (b<0) throw new BadMessageException(); break; case REQUEST_VERSION: - if (ch == HttpTokens.LINE_FEED) + if (b == HttpTokens.LINE_FEED) { if (_version==null) { @@ -835,25 +836,25 @@ public class HttpParser handle=_requestHandler.startRequest(_methodString,_uri.toString(), _version)||handle; continue; } - else if (ch>=HttpTokens.SPACE) - _string.append((char)ch); + else if (b>=HttpTokens.SPACE) + _string.append((char)b); else throw new BadMessageException(); break; case REASON: - if (ch == HttpTokens.LINE_FEED) + if (b == HttpTokens.LINE_FEED) { String reason=takeString(); setState(State.HEADER); handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle; continue; } - else if (ch>=HttpTokens.SPACE) + else if (b>=HttpTokens.SPACE || ((b<0) && (b>=-96))) { - _string.append((char)ch); - if (ch!=' '&&ch!='\t') + _string.append((char)(0xff&b)); + if (b!=' '&&b!='\t') _length=_string.length(); } else @@ -991,8 +992,8 @@ public class HttpParser while ((_state==State.HEADER || _state==State.TRAILER) && buffer.hasRemaining()) { // process each character - byte ch=next(buffer); - if (ch==0) + byte b=next(buffer); + if (b==0) break; if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes) @@ -1007,7 +1008,7 @@ public class HttpParser switch (_fieldState) { case FIELD: - switch(ch) + switch(b) { case HttpTokens.COLON: case HttpTokens.SPACE: @@ -1110,7 +1111,7 @@ public class HttpParser default: { // now handle the ch - if (chHttpTokens.SPACE) + if (b>HttpTokens.SPACE) { if (_header!=null) { @@ -1229,13 +1230,13 @@ public class HttpParser _headerString=null; } - _string.append((char)ch); - if (ch>HttpTokens.SPACE) + _string.append((char)b); + if (b>HttpTokens.SPACE) _length=_string.length(); break; } - if (ch==HttpTokens.LINE_FEED && !complianceViolation(RFC7230,"name only header")) + if (b==HttpTokens.LINE_FEED && !complianceViolation(RFC7230,"name only header")) { if (_headerString==null) { @@ -1251,21 +1252,21 @@ public class HttpParser break; } - throw new IllegalCharacterException(_state,ch,buffer); + throw new IllegalCharacterException(_state,b,buffer); case VALUE: - if (ch>HttpTokens.SPACE || ch<0) + if (b>HttpTokens.SPACE || b<0) { - _string.append((char)(0xff&ch)); + _string.append((char)(0xff&b)); _length=_string.length(); setState(FieldState.IN_VALUE); break; } - if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB) + if (b==HttpTokens.SPACE || b==HttpTokens.TAB) break; - if (ch==HttpTokens.LINE_FEED) + if (b==HttpTokens.LINE_FEED) { _value=null; _string.setLength(0); @@ -1275,10 +1276,10 @@ public class HttpParser setState(FieldState.FIELD); break; } - throw new IllegalCharacterException(_state,ch,buffer); + throw new IllegalCharacterException(_state,b,buffer); case IN_VALUE: - if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB) + if (b>=HttpTokens.SPACE || b<0 || b==HttpTokens.TAB) { if (_valueString!=null) { @@ -1286,13 +1287,13 @@ public class HttpParser _valueString=null; _field=null; } - _string.append((char)(0xff&ch)); - if (ch>HttpTokens.SPACE || ch<0) + _string.append((char)(0xff&b)); + if (b>HttpTokens.SPACE || b<0) _length=_string.length(); break; } - if (ch==HttpTokens.LINE_FEED) + if (b==HttpTokens.LINE_FEED) { if (_length > 0) { @@ -1304,7 +1305,7 @@ public class HttpParser break; } - throw new IllegalCharacterException(_state,ch,buffer); + throw new IllegalCharacterException(_state,b,buffer); default: throw new IllegalStateException(_state.toString()); diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties index cfdad688c7c..b4ad431489b 100644 --- a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties @@ -76,6 +76,7 @@ mov=video/quicktime movie=video/x-sgi-movie mp2=audio/mpeg mp3=audio/mpeg +mp4=video/mp4 mpe=video/mpeg mpeg=video/mpeg mpg=video/mpeg diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterTest.java index c3fb6be2a0d..3568800bcc9 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterTest.java @@ -144,7 +144,7 @@ public class CookieCutterTest * Example from RFC2965 */ @Test - @Ignore("comma separation no longer supported by RFC6265") + @Ignore("comma separation no longer supported by new RFC6265") public void testRFC2965_CookieSpoofingExample() { String rawCookie = "$Version=\"1\"; session_id=\"1234\", " + diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index cfa40814ee2..80c90980e2e 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -21,7 +21,9 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Enumeration; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.NoSuchElementException; @@ -31,6 +33,7 @@ import org.junit.Assert; import org.junit.Test; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -628,4 +631,68 @@ public class HttpFieldsTest assertFalse(header.containsKey("n11")); } + + + @Test + public void testIteration() throws Exception + { + HttpFields header = new HttpFields(); + Iterator i = header.iterator(); + assertThat(i.hasNext(),is(false)); + + header.put("name1", "valueA"); + header.put("name2", "valueB"); + header.add("name3", "valueC"); + + i = header.iterator(); + assertThat(i.hasNext(),is(true)); + assertThat(i.next().getName(),is("name1")); + assertThat(i.next().getName(),is("name2")); + i.remove(); + assertThat(i.next().getName(),is("name3")); + assertThat(i.hasNext(),is(false)); + + i = header.iterator(); + assertThat(i.hasNext(),is(true)); + assertThat(i.next().getName(),is("name1")); + assertThat(i.next().getName(),is("name3")); + assertThat(i.hasNext(),is(false)); + + + ListIterator l = header.listIterator(); + assertThat(l.hasNext(),is(true)); + l.add(new HttpField("name0","value")); + assertThat(l.hasNext(),is(true)); + assertThat(l.next().getName(),is("name1")); + l.set(new HttpField("NAME1","value")); + assertThat(l.hasNext(),is(true)); + assertThat(l.hasPrevious(),is(true)); + assertThat(l.previous().getName(),is("NAME1")); + assertThat(l.hasNext(),is(true)); + assertThat(l.hasPrevious(),is(true)); + assertThat(l.previous().getName(),is("name0")); + assertThat(l.hasNext(),is(true)); + assertThat(l.hasPrevious(),is(false)); + assertThat(l.next().getName(),is("name0")); + assertThat(l.hasNext(),is(true)); + assertThat(l.hasPrevious(),is(true)); + assertThat(l.next().getName(),is("NAME1")); + l.add(new HttpField("name2","value")); + assertThat(l.next().getName(),is("name3")); + assertThat(l.hasNext(),is(false)); + assertThat(l.hasPrevious(),is(true)); + l.add(new HttpField("name4","value")); + assertThat(l.hasNext(),is(false)); + assertThat(l.hasPrevious(),is(true)); + assertThat(l.previous().getName(),is("name4")); + + i = header.iterator(); + assertThat(i.hasNext(),is(true)); + assertThat(i.next().getName(),is("name0")); + assertThat(i.next().getName(),is("NAME1")); + assertThat(i.next().getName(),is("name2")); + assertThat(i.next().getName(),is("name3")); + assertThat(i.next().getName(),is("name4")); + assertThat(i.hasNext(),is(false)); + } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java index b363c29c579..60249925c4b 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java @@ -155,7 +155,7 @@ public class HttpGeneratorServerTest assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10); + MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, "ØÆ", new HttpFields(), 10); info.getFields().add("Content-Type", "test/data;\r\nextra=value"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); @@ -176,7 +176,7 @@ public class HttpGeneratorServerTest assertEquals(10, gen.getContentPrepared()); - assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("HTTP/1.1 200 ØÆ")); assertThat(response, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); assertThat(response, containsString("Content-Type: test/data; extra=value")); assertThat(response, containsString("Content-Length: 10")); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index a47bcb23b3d..de59d283ef9 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -573,7 +573,9 @@ public class HttpParserTest BufferUtil.put(BufferUtil.toBuffer(" HTTP/1.0\r\n"), buffer); BufferUtil.put(BufferUtil.toBuffer("Header1: "), buffer); buffer.put("\u00e6 \u00e6".getBytes(StandardCharsets.ISO_8859_1)); - BufferUtil.put(BufferUtil.toBuffer(" \r\n\r\n"), buffer); + BufferUtil.put(BufferUtil.toBuffer(" \r\nHeader2: "), buffer); + buffer.put((byte)-1); + BufferUtil.put(BufferUtil.toBuffer("\r\n\r\n"), buffer); BufferUtil.flipToFlush(buffer, 0); HttpParser.RequestHandler handler = new Handler(); @@ -585,7 +587,9 @@ public class HttpParserTest Assert.assertEquals("HTTP/1.0", _versionOrReason); Assert.assertEquals("Header1", _hdr[0]); Assert.assertEquals("\u00e6 \u00e6", _val[0]); - Assert.assertEquals(0, _headers); + Assert.assertEquals("Header2", _hdr[1]); + Assert.assertEquals(""+(char)255, _val[1]); + Assert.assertEquals(1, _headers); Assert.assertEquals(null, _bad); } @@ -1304,6 +1308,22 @@ public class HttpParserTest Assert.assertTrue(_messageCompleted); } + @Test + public void testResponseReasonIso8859_1() throws Exception + { + ByteBuffer buffer = BufferUtil.toBuffer( + "HTTP/1.1 302 déplacé temporairement\r\n" + + "Content-Length: 0\r\n" + + "\r\n",StandardCharsets.ISO_8859_1); + + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler); + parser.parseNext(buffer); + Assert.assertEquals("HTTP/1.1", _methodOrVersion); + Assert.assertEquals("302", _uriOrStatus); + Assert.assertEquals("déplacé temporairement", _versionOrReason); + } + @Test public void testSeekEOF() throws Exception { diff --git a/jetty-http2/http2-alpn-tests/pom.xml b/jetty-http2/http2-alpn-tests/pom.xml index a1ed1e96811..fac18b98d19 100644 --- a/jetty-http2/http2-alpn-tests/pom.xml +++ b/jetty-http2/http2-alpn-tests/pom.xml @@ -50,15 +50,15 @@ - - org.eclipse.jetty.alpn - alpn-api - ${alpn.api.version} - provided - + + org.eclipse.jetty.alpn + alpn-api + ${alpn.api.version} + provided + org.eclipse.jetty - jetty-alpn-server + jetty-alpn-openjdk8-server ${project.version} test @@ -86,4 +86,21 @@ + + + jdk9 + + [1.9,) + + + + + org.eclipse.jetty + jetty-alpn-java-server + ${project.version} + test + + + + diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index 3a356bc7445..cbeac2edc4b 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -47,7 +47,6 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory public static final String CLIENT_CONTEXT_KEY = "http2.client"; public static final String BYTE_BUFFER_POOL_CONTEXT_KEY = "http2.client.byteBufferPool"; public static final String EXECUTOR_CONTEXT_KEY = "http2.client.executor"; - public static final String PREALLOCATED_EXECUTOR_CONTEXT_KEY = "http2.client.preallocatedExecutor"; public static final String SCHEDULER_CONTEXT_KEY = "http2.client.scheduler"; public static final String SESSION_LISTENER_CONTEXT_KEY = "http2.client.sessionListener"; public static final String SESSION_PROMISE_CONTEXT_KEY = "http2.client.sessionPromise"; @@ -60,7 +59,6 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY); ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY); Executor executor = (Executor)context.get(EXECUTOR_CONTEXT_KEY); - ReservedThreadExecutor preallocatedExecutor = (ReservedThreadExecutor)context.get(PREALLOCATED_EXECUTOR_CONTEXT_KEY); Scheduler scheduler = (Scheduler)context.get(SCHEDULER_CONTEXT_KEY); Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY); @SuppressWarnings("unchecked") @@ -71,37 +69,29 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, flowControl); Parser parser = new Parser(byteBufferPool, session, 4096, 8192); - if (preallocatedExecutor==null) - { - // TODO move this to non lazy construction - preallocatedExecutor=client.getBean(ReservedThreadExecutor.class); - if (preallocatedExecutor==null) - { - synchronized (this) - { - if (preallocatedExecutor==null) - { - try - { - preallocatedExecutor = new ReservedThreadExecutor(executor,1); // TODO configure size - preallocatedExecutor.start(); - client.addBean(preallocatedExecutor,true); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - } - } - } - - HTTP2ClientConnection connection = new HTTP2ClientConnection(client, byteBufferPool, preallocatedExecutor, endPoint, + ReservedThreadExecutor reservedExecutor = provideReservedThreadExecutor(client, executor); + + HTTP2ClientConnection connection = new HTTP2ClientConnection(client, byteBufferPool, reservedExecutor, endPoint, parser, session, client.getInputBufferSize(), promise, listener); connection.addListener(connectionListener); return customize(connection, context); } + protected ReservedThreadExecutor provideReservedThreadExecutor(HTTP2Client client, Executor executor) + { + synchronized (this) + { + ReservedThreadExecutor reservedExecutor = client.getBean(ReservedThreadExecutor.class); + if (reservedExecutor == null) + { + // TODO: see HTTP2Connection.FillableCallback + reservedExecutor = new ReservedThreadExecutor(executor, 0); + client.addManaged(reservedExecutor); + } + return reservedExecutor; + } + } + private class HTTP2ClientConnection extends HTTP2Connection implements Callback { private final HTTP2Client client; 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 504da0c8ef6..55572146c21 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 @@ -58,7 +58,6 @@ public class HTTP2Connection extends AbstractConnection this.session = session; this.bufferSize = bufferSize; this.strategy = new EatWhatYouKill(producer, executor.getExecutor(), executor); - LifeCycle.start(strategy); } @@ -274,6 +273,8 @@ public class HTTP2Connection extends AbstractConnection @Override public InvocationType getInvocationType() { + // TODO: see also AbstractHTTP2ServerConnectionFactory.reservedThreads. + // TODO: it's non blocking here because reservedThreads=0. return InvocationType.NON_BLOCKING; } } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index 8596c7e8720..b5bf5738703 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -154,6 +154,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne public void setReservedThreads(int threads) { + // TODO: see also HTTP2Connection.FillableCallback. // TODO: currently disabled since the only value that works is 0. // this.reservedThreads = threads; } @@ -182,29 +183,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne streamIdleTimeout = endPoint.getIdleTimeout(); session.setStreamIdleTimeout(streamIdleTimeout); session.setInitialSessionRecvWindow(getInitialSessionRecvWindow()); - - ReservedThreadExecutor executor = connector.getBean(ReservedThreadExecutor.class); - if (executor==null) - { - synchronized (this) - { - executor = connector.getBean(ReservedThreadExecutor.class); - if (executor==null) - { - try - { - executor = new ReservedThreadExecutor(connector.getExecutor(), getReservedThreads()); - executor.start(); - connector.addBean(executor,true); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - } - } + ReservedThreadExecutor executor = provideReservedThreadExecutor(connector); ServerParser parser = newServerParser(connector, session); HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), executor, @@ -213,6 +193,20 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne return configure(connection, connector, endPoint); } + protected ReservedThreadExecutor provideReservedThreadExecutor(Connector connector) + { + synchronized (this) + { + ReservedThreadExecutor executor = getBean(ReservedThreadExecutor.class); + if (executor == null) + { + executor = new ReservedThreadExecutor(connector.getExecutor(), getReservedThreads()); + addManaged(executor); + } + return executor; + } + } + protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint); protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener) diff --git a/jetty-infinispan/src/main/config/etc/sessions/infinispan/default.xml b/jetty-infinispan/src/main/config/etc/sessions/infinispan/default.xml index cdd7870b8e0..efc565e2fad 100644 --- a/jetty-infinispan/src/main/config/etc/sessions/infinispan/default.xml +++ b/jetty-infinispan/src/main/config/etc/sessions/infinispan/default.xml @@ -9,6 +9,7 @@ + /etc/infinispan-embedded.xml diff --git a/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded-910.mod b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded-910.mod new file mode 100644 index 00000000000..96b6acb23e1 --- /dev/null +++ b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded-910.mod @@ -0,0 +1,33 @@ +[description] +Enables session data store in a local Infinispan cache + +[tags] +session + +[provides] +session-store +session-store-infinispan-embedded + +[depend] +sessions + +[files] +maven://org.infinispan/infinispan-embedded/9.1.0.Final|lib/infinispan/infinispan-embedded-9.1.0.Final.jar +basehome:modules/session-store-infinispan-embedded/infinispan-embedded.xml|etc/infinispan-embedded.xml + + +[xml] +etc/sessions/infinispan/default.xml + +[lib] +lib/jetty-infinispan-${jetty.version}.jar +lib/infinispan/*.jar + +[license] +Infinispan is an open source project hosted on Github and released under the Apache 2.0 license. +http://infinispan.org/ +http://www.apache.org/licenses/LICENSE-2.0.html + +[ini-template] +#jetty.session.gracePeriod.seconds=3600 +#jetty.session.savePeriod.seconds=0 diff --git a/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded.mod b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded.mod index 4addf799a61..1110aaadbd3 100644 --- a/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded.mod +++ b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded.mod @@ -6,12 +6,15 @@ session [provides] session-store +session-store-infnispan-embedded [depend] sessions [files] maven://org.infinispan/infinispan-embedded/7.1.1.Final|lib/infinispan/infinispan-embedded-7.1.1.Final.jar +basehome:modules/session-store-infinispan-embedded/infinispan-embedded.xml|etc/infinispan-embedded.xml + [xml] etc/sessions/infinispan/default.xml diff --git a/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded/infinispan-embedded.xml b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded/infinispan-embedded.xml new file mode 100644 index 00000000000..5adc356b1f6 --- /dev/null +++ b/jetty-infinispan/src/main/config/modules/session-store-infinispan-embedded/infinispan-embedded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/jetty-infinispan/src/main/config/modules/session-store-infinispan-remote-910.mod b/jetty-infinispan/src/main/config/modules/session-store-infinispan-remote-910.mod new file mode 100644 index 00000000000..36493531d38 --- /dev/null +++ b/jetty-infinispan/src/main/config/modules/session-store-infinispan-remote-910.mod @@ -0,0 +1,35 @@ +[description] +Enables session data store in a remote Infinispan cache + +[tags] +session + +[provides] +session-store +session-store-infinispan-remote + +[depend] +sessions + +[files] +maven://org.infinispan/infinispan-remote/9.1.0.Final|lib/infinispan/infinispan-remote-9.1.0.Final.jar +basehome:modules/session-store-infinispan-remote/ + +[xml] +etc/sessions/infinispan/remote.xml + +[lib] +lib/jetty-infinispan-${jetty.version}.jar +lib/infinispan/*.jar + +[license] +Infinispan is an open source project hosted on Github and released under the Apache 2.0 license. +http://infinispan.org/ +http://www.apache.org/licenses/LICENSE-2.0.html + + +[ini-template] +#jetty.session.infinispan.remoteCacheName=sessions +#jetty.session.infinispan.idleTimeout.seconds=0 +#jetty.session.gracePeriod.seconds=3600 +#jetty.session.savePeriod.seconds=0 diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index 07d3b71bdc0..7f1db1f16d4 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -79,6 +79,11 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable setStopTimeout(5000); } + public Selector getSelector() + { + return _selector; + } + @Override protected void doStart() throws Exception { @@ -140,6 +145,116 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable selector.wakeup(); } + private Runnable processConnect(SelectionKey key, final Connect connect) + { + SelectableChannel channel = key.channel(); + try + { + key.attach(connect.attachment); + boolean connected = _selectorManager.doFinishConnect(channel); + if (LOG.isDebugEnabled()) + LOG.debug("Connected {} {}", connected, channel); + if (connected) + { + if (connect.timeout.cancel()) + { + key.interestOps(0); + return new CreateEndPoint(channel, key) + { + @Override + protected void failed(Throwable failure) + { + super.failed(failure); + connect.failed(failure); + } + }; + } + else + { + throw new SocketTimeoutException("Concurrent Connect Timeout"); + } + } + else + { + throw new ConnectException(); + } + } + catch (Throwable x) + { + connect.failed(x); + return null; + } + } + + private void closeNoExceptions(Closeable closeable) + { + try + { + if (closeable != null) + closeable.close(); + } + catch (Throwable x) + { + LOG.ignore(x); + } + } + + private EndPoint createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException + { + EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey); + endPoint.onOpen(); + _selectorManager.endPointOpened(endPoint); + Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment()); + endPoint.setConnection(connection); + selectionKey.attach(endPoint); + _selectorManager.connectionOpened(connection); + if (LOG.isDebugEnabled()) + LOG.debug("Created {}", endPoint); + return endPoint; + } + + public void destroyEndPoint(final EndPoint endPoint) + { + submit(new DestroyEndPoint(endPoint)); + } + + @Override + public String dump() + { + super.dump(); + return ContainerLifeCycle.dump(this); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + Selector selector = _selector; + if (selector == null || !selector.isOpen()) + dumpBeans(out, indent); + else + { + final ArrayList dump = new ArrayList<>(selector.keys().size() * 2); + DumpKeys dumpKeys = new DumpKeys(dump); + submit(dumpKeys); + dumpKeys.await(5, TimeUnit.SECONDS); + if (dump.isEmpty()) + dumpBeans(out, indent); + else + dumpBeans(out, indent, dump); + } + } + + @Override + public String toString() + { + Selector selector = _selector; + return String.format("%s id=%s keys=%d selected=%d", + super.toString(), + _id, + selector != null && selector.isOpen() ? selector.keys().size() : -1, + selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1); + } + /** * A {@link Selectable} is an {@link EndPoint} that wish to be * notified of non-blocking events by the {@link ManagedSelector}. @@ -161,7 +276,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable void updateKey(); } - private class SelectorProducer implements ExecutionStrategy.Producer { private Set _keys = Collections.emptySet(); @@ -331,7 +445,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable return String.format("%s@%x", getClass().getSimpleName(), hashCode()); } } - + private abstract static class NonBlockingAction implements Runnable, Invocable { @Override @@ -341,124 +455,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } } - private Runnable processConnect(SelectionKey key, final Connect connect) - { - SelectableChannel channel = key.channel(); - try - { - key.attach(connect.attachment); - boolean connected = _selectorManager.doFinishConnect(channel); - if (LOG.isDebugEnabled()) - LOG.debug("Connected {} {}", connected, channel); - if (connected) - { - if (connect.timeout.cancel()) - { - key.interestOps(0); - return new CreateEndPoint(channel, key) - { - @Override - protected void failed(Throwable failure) - { - super.failed(failure); - connect.failed(failure); - } - }; - } - else - { - throw new SocketTimeoutException("Concurrent Connect Timeout"); - } - } - else - { - throw new ConnectException(); - } - } - catch (Throwable x) - { - connect.failed(x); - return null; - } - } - - private void closeNoExceptions(Closeable closeable) - { - try - { - if (closeable != null) - closeable.close(); - } - catch (Throwable x) - { - LOG.ignore(x); - } - } - - private EndPoint createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException - { - EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey); - endPoint.onOpen(); - _selectorManager.endPointOpened(endPoint); - Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment()); - endPoint.setConnection(connection); - selectionKey.attach(endPoint); - _selectorManager.connectionOpened(connection); - if (LOG.isDebugEnabled()) - LOG.debug("Created {}", endPoint); - return endPoint; - } - - public void destroyEndPoint(final EndPoint endPoint) - { - final Connection connection = endPoint.getConnection(); - submit(() -> - { - if (LOG.isDebugEnabled()) - LOG.debug("Destroyed {}", endPoint); - if (connection != null) - _selectorManager.connectionClosed(connection); - _selectorManager.endPointClosed(endPoint); - }); - } - - @Override - public String dump() - { - super.dump(); - return ContainerLifeCycle.dump(this); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - Selector selector = _selector; - if (selector == null || !selector.isOpen()) - dumpBeans(out, indent); - else - { - final ArrayList dump = new ArrayList<>(selector.keys().size() * 2); - DumpKeys dumpKeys = new DumpKeys(dump); - submit(dumpKeys); - dumpKeys.await(5, TimeUnit.SECONDS); - if (dump.isEmpty()) - dumpBeans(out, indent); - else - dumpBeans(out, indent, dump); - } - } - - @Override - public String toString() - { - Selector selector = _selector; - return String.format("%s id=%s keys=%d selected=%d", - super.toString(), - _id, - selector != null && selector.isOpen() ? selector.keys().size() : -1, - selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1); - } - private class DumpKeys implements Runnable { private final CountDownLatch latch = new CountDownLatch(1); @@ -523,8 +519,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable if (_key==null) { _key = _channel.register(_selector, SelectionKey.OP_ACCEPT, this); - } - + } if (LOG.isDebugEnabled()) LOG.debug("{} acceptor={}", this, _key); @@ -609,7 +604,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } } - private class CreateEndPoint implements Runnable, Closeable + private class CreateEndPoint extends NonBlockingAction implements Closeable { private final SelectableChannel channel; private final SelectionKey key; @@ -800,8 +795,24 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } } - public Selector getSelector() + private class DestroyEndPoint extends NonBlockingAction { - return _selector; + private final EndPoint endPoint; + + public DestroyEndPoint(EndPoint endPoint) + { + this.endPoint = endPoint; + } + + @Override + public void run() + { + if (LOG.isDebugEnabled()) + LOG.debug("Destroyed {}", endPoint); + Connection connection = endPoint.getConnection(); + if (connection != null) + _selectorManager.connectionClosed(connection); + _selectorManager.endPointClosed(endPoint); + } } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java index a4202dfc906..f79a1f428bb 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java @@ -45,7 +45,7 @@ public abstract class NegotiatingClientConnection extends AbstractConnection this.context = context; } - protected SSLEngine getSSLEngine() + public SSLEngine getSSLEngine() { return engine; } @@ -80,13 +80,17 @@ public abstract class NegotiatingClientConnection extends AbstractConnection while (true) { int filled = fill(); - if (filled == 0 && !completed) - fillInterested(); - if (filled <= 0 || completed) + if (completed || filled < 0) + { + replaceConnection(); break; + } + if (filled == 0) + { + fillInterested(); + break; + } } - if (completed) - replaceConnection(); } private int fill() diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/ALPNProcessor.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/ALPNProcessor.java index f8fddc892f0..5e053fb302f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/ALPNProcessor.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/ALPNProcessor.java @@ -18,35 +18,54 @@ package org.eclipse.jetty.io.ssl; -import java.util.List; - import javax.net.ssl.SSLEngine; +import org.eclipse.jetty.io.Connection; + public interface ALPNProcessor { - public interface Server + /** + * Initializes this ALPNProcessor + * + * @throws RuntimeException if this processor is unavailable (e.g. missing dependencies or wrong JVM) + */ + public default void init() { - public static final ALPNProcessor.Server NOOP = new ALPNProcessor.Server() - { - }; - - public default void configure(SSLEngine sslEngine) - { - } } - public interface Client + /** + * Tests if this processor can be applied to the given SSLEngine. + * + * @param sslEngine the SSLEngine to check + * @return true if the processor can be applied to the given SSLEngine + */ + public default boolean appliesTo(SSLEngine sslEngine) { - public static final Client NOOP = new Client() - { - }; + return false; + } - public default void configure(SSLEngine sslEngine, List protocols) - { - } + /** + * Configures the given SSLEngine and the given Connection for ALPN. + * + * @param sslEngine the SSLEngine to configure + * @param connection the Connection to configure + * @throws RuntimeException if this processor cannot be configured + */ + public default void configure(SSLEngine sslEngine, Connection connection) + { + } - public default void process(SSLEngine sslEngine) - { - } + /** + * Server-side interface used by ServiceLoader. + */ + public interface Server extends ALPNProcessor + { + } + + /** + * Client-side interface used by ServiceLoader. + */ + public interface Client extends ALPNProcessor + { } } diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java index 15ecea9976f..16f45d43ede 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java @@ -178,11 +178,11 @@ public class LdapLoginModule extends AbstractLoginModule private DirContext _rootContext; - + public class LDAPUserInfo extends UserInfo { - Attributes attributes; - + Attributes attributes; + /** * @param userName * @param credential @@ -198,10 +198,10 @@ public class LdapLoginModule extends AbstractLoginModule { return getUserRoles(_rootContext, getUserName(), attributes); } - + } - - + + /** * get the available information about the user *

    @@ -216,7 +216,7 @@ public class LdapLoginModule extends AbstractLoginModule */ public UserInfo getUserInfo(String username) throws Exception { - Attributes attributes = getUserAttributes(username); + Attributes attributes = getUserAttributes(username); String pwdCredential = getUserCredentials(attributes); if (pwdCredential == null) @@ -271,30 +271,32 @@ public class LdapLoginModule extends AbstractLoginModule */ private Attributes getUserAttributes(String username) throws LoginException { - Attributes attributes = null; + Attributes attributes = null; - SearchResult result; - try { - result = findUser(username); - attributes = result.getAttributes(); - } - catch (NamingException e) { + SearchResult result; + try + { + result = findUser(username); + attributes = result.getAttributes(); + } + catch (NamingException e) + { throw new LoginException("Root context binding failure."); - } - - return attributes; - } - + } + + return attributes; + } + private String getUserCredentials(Attributes attributes) throws LoginException { - String ldapCredential = null; + String ldapCredential = null; Attribute attribute = attributes.get(_userPasswordAttribute); if (attribute != null) { try { - byte[] value = (byte[]) attribute.get(); + byte[] value = (byte[])attribute.get(); ldapCredential = new String(value); } @@ -323,16 +325,16 @@ public class LdapLoginModule extends AbstractLoginModule { String rdnValue = username; Attribute attribute = attributes.get(_userRdnAttribute); - if (attribute != null) - { - try - { - rdnValue = (String) attribute.get(); // switch to the value stored in the _userRdnAttribute if we can - } - catch (NamingException e) - { - } - } + if (attribute != null) + { + try + { + rdnValue = (String)attribute.get(); // switch to the value stored in the _userRdnAttribute if we can + } + catch (NamingException e) + { + } + } String userDn = _userRdnAttribute + "=" + rdnValue + "," + _userBaseDn; @@ -361,7 +363,7 @@ public class LdapLoginModule extends AbstractLoginModule while (results.hasMoreElements()) { - SearchResult result = (SearchResult) results.nextElement(); + SearchResult result = (SearchResult)results.nextElement(); Attributes attributes = result.getAttributes(); @@ -410,8 +412,8 @@ public class LdapLoginModule extends AbstractLoginModule Callback[] callbacks = configureCallbacks(); getCallbackHandler().handle(callbacks); - String webUserName = ((NameCallback) callbacks[0]).getName(); - Object webCredential = ((ObjectCallback) callbacks[1]).getObject(); + String webUserName = ((NameCallback)callbacks[0]).getName(); + Object webCredential = ((ObjectCallback)callbacks[1]).getObject(); if (webUserName == null || webCredential == null) { @@ -424,8 +426,7 @@ public class LdapLoginModule extends AbstractLoginModule if (_forceBindingLogin) { authed = bindingLogin(webUserName, webCredential); - } - else + } else { // This sets read and the credential UserInfo userInfo = getUserInfo(webUserName); @@ -439,7 +440,7 @@ public class LdapLoginModule extends AbstractLoginModule setCurrentUser(new JAASUserInfo(userInfo)); if (webCredential instanceof String) - authed = credentialLogin(Credential.getCredential((String) webCredential)); + authed = credentialLogin(Credential.getCredential((String)webCredential)); else authed = credentialLogin(webCredential); } @@ -494,7 +495,7 @@ public class LdapLoginModule extends AbstractLoginModule * @param username the user name * @param password the password * @return true always - * @throws LoginException if unable to bind the login + * @throws LoginException if unable to bind the login * @throws NamingException if failure to bind login */ public boolean bindingLogin(String username, Object password) throws LoginException, NamingException @@ -505,15 +506,15 @@ public class LdapLoginModule extends AbstractLoginModule LOG.info("Attempting authentication: " + userDn); - Hashtable environment = getEnvironment(); + Hashtable environment = getEnvironment(); - if ( userDn == null || "".equals(userDn) ) + if (userDn == null || "".equals(userDn)) { throw new NamingException("username may not be empty"); } environment.put(Context.SECURITY_PRINCIPAL, userDn); // RFC 4513 section 6.3.1, protect against ldap server implementations that allow successful binding on empty passwords - if ( password == null || "".equals(password)) + if (password == null || "".equals(password)) { throw new NamingException("password may not be empty"); } @@ -542,9 +543,9 @@ public class LdapLoginModule extends AbstractLoginModule LOG.debug("Searching for user " + username + " with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); Object[] filterArguments = new Object[]{ - _userObjectClass, - _userIdAttribute, - username + _userObjectClass, + _userIdAttribute, + username }; NamingEnumeration results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls); @@ -556,7 +557,7 @@ public class LdapLoginModule extends AbstractLoginModule throw new LoginException("User not found."); } - return (SearchResult) results.nextElement(); + return (SearchResult)results.nextElement(); } @@ -565,37 +566,37 @@ public class LdapLoginModule extends AbstractLoginModule *

    * Called once by JAAS after new instance is created. * - * @param subject the subect + * @param subject the subect * @param callbackHandler the callback handler - * @param sharedState the shared state map - * @param options the option map + * @param sharedState the shared state map + * @param options the option map */ public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, - Map options) + Map sharedState, + Map options) { super.initialize(subject, callbackHandler, sharedState, options); - _hostname = (String) options.get("hostname"); - _port = Integer.parseInt((String) options.get("port")); - _contextFactory = (String) options.get("contextFactory"); - _bindDn = (String) options.get("bindDn"); - _bindPassword = (String) options.get("bindPassword"); - _authenticationMethod = (String) options.get("authenticationMethod"); + _hostname = (String)options.get("hostname"); + _port = Integer.parseInt((String)options.get("port")); + _contextFactory = (String)options.get("contextFactory"); + _bindDn = (String)options.get("bindDn"); + _bindPassword = (String)options.get("bindPassword"); + _authenticationMethod = (String)options.get("authenticationMethod"); - _userBaseDn = (String) options.get("userBaseDn"); + _userBaseDn = (String)options.get("userBaseDn"); - _roleBaseDn = (String) options.get("roleBaseDn"); + _roleBaseDn = (String)options.get("roleBaseDn"); if (options.containsKey("forceBindingLogin")) { - _forceBindingLogin = Boolean.parseBoolean((String) options.get("forceBindingLogin")); + _forceBindingLogin = Boolean.parseBoolean((String)options.get("forceBindingLogin")); } if (options.containsKey("useLdaps")) { - _useLdaps = Boolean.parseBoolean((String) options.get("useLdaps")); + _useLdaps = Boolean.parseBoolean((String)options.get("useLdaps")); } _userObjectClass = getOption(options, "userObjectClass", _userObjectClass); @@ -625,7 +626,7 @@ public class LdapLoginModule extends AbstractLoginModule } catch (NamingException e) { - throw new LoginException( "error closing root context: " + e.getMessage() ); + throw new LoginException("error closing root context: " + e.getMessage()); } return super.commit(); @@ -639,13 +640,13 @@ public class LdapLoginModule extends AbstractLoginModule } catch (NamingException e) { - throw new LoginException( "error closing root context: " + e.getMessage() ); + throw new LoginException("error closing root context: " + e.getMessage()); } return super.abort(); } - private String getOption(Map options, String key, String defaultValue) + private String getOption(Map options, String key, String defaultValue) { Object value = options.get(key); @@ -654,7 +655,7 @@ public class LdapLoginModule extends AbstractLoginModule return defaultValue; } - return (String) value; + return (String)value; } /** @@ -670,7 +671,7 @@ public class LdapLoginModule extends AbstractLoginModule if (_hostname != null) { - env.put(Context.PROVIDER_URL, (_useLdaps?"ldaps://":"ldap://") + _hostname + (_port==0?"":":"+_port) +"/"); + env.put(Context.PROVIDER_URL, (_useLdaps ? "ldaps://" : "ldap://") + _hostname + (_port == 0 ? "" : ":" + _port) + "/"); } if (_authenticationMethod != null) diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java index ae0e56f161b..e398841684d 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.jmx; import java.io.IOException; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -46,14 +47,11 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De { private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName()); private final static ConcurrentMap __unique = new ConcurrentHashMap<>(); - - public static void resetUnique() - { - __unique.clear(); - } + private static final Container ROOT = new ContainerLifeCycle(); private final MBeanServer _mbeanServer; - private final Map _beans = new ConcurrentHashMap<>(); + private final ConcurrentMap _beans = new ConcurrentHashMap<>(); + private final ConcurrentMap _mbeans = new ConcurrentHashMap<>(); private String _domain = null; /** @@ -64,7 +62,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De */ public ObjectName findMBean(Object object) { - return _beans.get(object); + return _mbeans.get(object); } /** @@ -75,7 +73,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De */ public Object findBean(ObjectName objectName) { - for (Map.Entry entry : _beans.entrySet()) + for (Map.Entry entry : _mbeans.entrySet()) { if (entry.getValue().equals(objectName)) return entry.getKey(); @@ -130,9 +128,19 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De if (LOG.isDebugEnabled()) LOG.debug("beanAdded {}->{}", parent, obj); + if (obj == null) + return; + + if (parent == null) + parent = ROOT; + + // Is the bean already tracked ? + if (_beans.putIfAbsent(obj, parent) != null) + return; + // Is there an object name for the parent ? ObjectName parentObjectName = null; - if (parent != null) + if (parent != ROOT) { parentObjectName = findMBean(parent); if (parentObjectName == null) @@ -143,10 +151,6 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De } } - // Does the mbean already exist ? - if (obj == null || _beans.containsKey(obj)) - return; - try { // Create an MBean for the object. @@ -207,7 +211,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De if (LOG.isDebugEnabled()) LOG.debug("Registered {}", objectName); - _beans.put(obj, objectName); + _mbeans.put(obj, objectName); } catch (Throwable x) { @@ -219,12 +223,17 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De public void beanRemoved(Container parent, Object obj) { if (LOG.isDebugEnabled()) - LOG.debug("beanRemoved {}", obj); + LOG.debug("beanRemoved {}->{}", parent, obj); - ObjectName objectName = _beans.remove(obj); + if (parent == null) + parent = ROOT; - if (objectName != null) - unregister(objectName); + if (_beans.remove(obj, parent)) + { + ObjectName objectName = _mbeans.remove(obj); + if (objectName != null) + unregister(objectName); + } } /** @@ -248,7 +257,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De public void dump(Appendable out, String indent) throws IOException { ContainerLifeCycle.dumpObject(out,this); - ContainerLifeCycle.dump(out, indent, _beans.entrySet()); + ContainerLifeCycle.dump(out, indent, _mbeans.entrySet()); } @Override @@ -260,9 +269,11 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De @Override public void destroy() { - _beans.values().stream() - .filter(objectName -> objectName != null) + _mbeans.values().stream() + .filter(Objects::nonNull) .forEach(this::unregister); + _mbeans.clear(); + _beans.clear(); } private void unregister(ObjectName objectName) diff --git a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/MBeanContainerTest.java b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/MBeanContainerTest.java index fae5dc00ae3..691735187b5 100644 --- a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/MBeanContainerTest.java +++ b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/MBeanContainerTest.java @@ -200,8 +200,8 @@ public class MBeanContainerTest setUpDestroy(); // when - mbeanContainer.destroy(); objectName = mbeanContainer.findMBean(managed); + mbeanContainer.destroy(); // then Assert.assertFalse("Unregistered bean - managed", mbeanContainer.getMBeanServer().isRegistered(objectName)); @@ -212,9 +212,9 @@ public class MBeanContainerTest { // given setUpDestroy(); - objectName = mbeanContainer.findMBean(managed); // when + objectName = mbeanContainer.findMBean(managed); mbeanContainer.getMBeanServer().unregisterMBean(objectName); // then @@ -224,4 +224,34 @@ public class MBeanContainerTest // an exception of type InstanceNotFoundException occurs. mbeanContainer.destroy(); } + + @Test + public void testNonManagedLifecycleNotUnregistered() throws Exception + { + testNonManagedObjectNotUnregistered(new ContainerLifeCycle()); + } + + @Test + public void testNonManagedPojoNotUnregistered() throws Exception + { + testNonManagedObjectNotUnregistered(new Object()); + } + + private void testNonManagedObjectNotUnregistered(Object lifeCycle) throws Exception + { + ContainerLifeCycle parent = new ContainerLifeCycle(); + parent.addBean(mbeanContainer); + + ContainerLifeCycle child = new ContainerLifeCycle(); + parent.addBean(child); + + parent.addBean(lifeCycle, true); + child.addBean(lifeCycle, false); + + parent.start(); + + parent.removeBean(child); + + Assert.assertNotNull(mbeanContainer.findMBean(lifeCycle)); + } } diff --git a/jetty-memcached/jetty-memcached-sessions/src/main/config/etc/sessions/session-data-cache/xmemcached.xml b/jetty-memcached/jetty-memcached-sessions/src/main/config/etc/sessions/session-data-cache/xmemcached.xml index 30c71580c6a..60448c0ac06 100644 --- a/jetty-memcached/jetty-memcached-sessions/src/main/config/etc/sessions/session-data-cache/xmemcached.xml +++ b/jetty-memcached/jetty-memcached-sessions/src/main/config/etc/sessions/session-data-cache/xmemcached.xml @@ -7,8 +7,8 @@ - - + + @@ -20,7 +20,7 @@ 100 - - + + diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java index 80935a4693f..4c4aef88441 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java @@ -25,9 +25,11 @@ import java.util.Set; import javax.servlet.ServletContainerInitializer; + import org.eclipse.jetty.annotations.AnnotationParser.Handler; import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -184,6 +186,23 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot parseBundle(context,parser,webbundle,webbundle); } + + + + /** + * @see org.eclipse.jetty.annotations.AnnotationConfiguration#parseWebInfClasses(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.annotations.AnnotationParser) + */ + @Override + public void parseWebInfClasses(WebAppContext context, org.eclipse.jetty.annotations.AnnotationParser parser) + throws Exception + { + Bundle webbundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE); + String bundleClasspath = (String)webbundle.getHeaders().get(Constants.BUNDLE_CLASSPATH); + //only scan WEB-INF/classes if we didn't already scan it with parseWebBundle + if (StringUtil.isBlank(bundleClasspath) || !bundleClasspath.contains("WEB-INF/classes")) + super.parseWebInfClasses(context, parser); + } + /** * Scan a bundle required by the webbundle for servlet annotations * @param context The webapp context 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 64668220574..dfa5eeae91e 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 @@ -203,12 +203,11 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa } //transform into a classname to pass to the resolver String shortName = name.replace('/', '.').substring(0,name.length()-6); - if (!isParsed(shortName)) + + addParsedClass(shortName, getResource(bundle)); + try (InputStream classInputStream = classUrl.openStream()) { - try (InputStream classInputStream = classUrl.openStream()) - { - scanClass(handlers, getResource(bundle), classInputStream); - } + scanClass(handlers, getResource(bundle), classInputStream); } } } diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java index bd64e9e6e40..c78488e03f6 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java @@ -128,7 +128,7 @@ public class TestOSGiUtil for (Bundle b : bundleContext.getBundles()) { bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b); - System.err.println(" " + b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState()); + System.err.println(" " + b.getBundleId()+" "+b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState()); } } diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java index 84e8c764b75..3153a3ca3a7 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java @@ -30,11 +30,17 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FilePermission; import java.io.FileWriter; +import java.io.IOException; import java.io.OutputStream; import java.io.Writer; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -139,7 +145,7 @@ public class PropertyUserStoreTest } } - return "jar:file:" + usersJar.getCanonicalPath() + "!/" + entryPath; + return "jar:" + usersJar.toURI().toASCIIString() + "!/" + entryPath; } private void writeUser(File usersFile) @@ -205,30 +211,47 @@ public class PropertyUserStoreTest userCount.awaitCount(3); } - @Test public void testPropertyUserStoreLoadUpdateUser() throws Exception { assumeThat("Skipping on OSX", OS.IS_OSX, is(false)); final UserCount userCount = new UserCount(); final File usersFile = initUsersText(); - - PropertyUserStore store = new PropertyUserStore(); + final AtomicInteger loadCount = new AtomicInteger(0); + PropertyUserStore store = new PropertyUserStore() + { + @Override + protected void loadUsers() throws IOException + { + loadCount.incrementAndGet(); + super.loadUsers(); + } + }; store.setHotReload(true); store.setConfigFile(usersFile); - store.registerUserListener(userCount); store.start(); userCount.assertThatCount(is(3)); - - addAdditionalUser(usersFile,"skip: skip, roleA\n"); - - userCount.awaitCount(4); - - assertThat("Failed to retrieve UserIdentity from PropertyUserStore directly", store.getUserIdentity("skip"), notNullValue()); + assertThat(loadCount.get(),is(1)); + addAdditionalUser(usersFile,"skip: skip, roleA\n"); + userCount.awaitCount(4); + assertThat(loadCount.get(),is(2)); + assertThat(store.getUserIdentity("skip"), notNullValue()); + userCount.assertThatCount(is(4)); + userCount.assertThatUsers(hasItem("skip")); + + if (OS.IS_LINUX) + Files.createFile(testdir.getPath().toRealPath().resolve("unrelated.txt"), + PosixFilePermissions.asFileAttribute(EnumSet.noneOf(PosixFilePermission.class))); + else + Files.createFile(testdir.getPath().toRealPath().resolve("unrelated.txt")); + + Thread.sleep(1100); + assertThat(loadCount.get(),is(2)); + userCount.assertThatCount(is(4)); userCount.assertThatUsers(hasItem("skip")); } diff --git a/jetty-server/src/main/config/etc/jetty-connectionlimit.xml b/jetty-server/src/main/config/etc/jetty-connectionlimit.xml new file mode 100644 index 00000000000..07bd2463d48 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-connectionlimit.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/jetty-server/src/main/config/etc/jetty-lowresources.xml b/jetty-server/src/main/config/etc/jetty-lowresources.xml index ee4786ade45..c45954c651a 100644 --- a/jetty-server/src/main/config/etc/jetty-lowresources.xml +++ b/jetty-server/src/main/config/etc/jetty-lowresources.xml @@ -16,6 +16,7 @@ + diff --git a/jetty-server/src/main/config/modules/connectionlimit.mod b/jetty-server/src/main/config/modules/connectionlimit.mod new file mode 100644 index 00000000000..b51829bfee7 --- /dev/null +++ b/jetty-server/src/main/config/modules/connectionlimit.mod @@ -0,0 +1,14 @@ +[description] +Enable a server wide connection limit + +[tags] +connector + +[depend] +server + +[xml] +etc/jetty-connectionlimit.xml + +[ini-template] +jetty.connection.limit=1000 diff --git a/jetty-server/src/main/config/modules/lowresources.mod b/jetty-server/src/main/config/modules/lowresources.mod index 257829afd85..c68db526cbe 100644 --- a/jetty-server/src/main/config/modules/lowresources.mod +++ b/jetty-server/src/main/config/modules/lowresources.mod @@ -27,3 +27,6 @@ etc/jetty-lowresources.xml ## Max time a resource may stay in low resource mode before actions are taken (in milliseconds) # jetty.lowresources.maxLowResourcesTime=5000 + +## Accept new connections while in low resources +# jetty.lowresources.accepting=true diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod index 73a360090c1..e16914cdddd 100644 --- a/jetty-server/src/main/config/modules/server.mod +++ b/jetty-server/src/main/config/modules/server.mod @@ -82,10 +82,3 @@ etc/jetty.xml ## Dump the state of the Jetty server, components, and webapps before shutdown # jetty.server.dumpBeforeStop=false - -## The name to uniquely identify this server instance -#jetty.defaultSessionIdManager.workerName=node1 - -## How frequently sessions are inspected -#jetty.sessionInspectionInterval.seconds=60 - diff --git a/jetty-server/src/main/config/modules/session-store-cache.mod b/jetty-server/src/main/config/modules/session-store-cache.mod index 2f884a98f82..db717d4276b 100644 --- a/jetty-server/src/main/config/modules/session-store-cache.mod +++ b/jetty-server/src/main/config/modules/session-store-cache.mod @@ -24,4 +24,4 @@ session-data-cache=xmemcached #jetty.session.memcached.host=localhost #jetty.session.memcached.port=11211 #jetty.session.memcached.expirySec= - +#jetty.session.memcached.heartbeats=true diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index ec8b1e85237..fe0e960b43f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -289,7 +289,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co protected void interruptAcceptors() { - try (Locker.Lock lock = _locker.lockIfNotHeld()) + try (Locker.Lock lock = _locker.lock()) { for (Thread thread : _acceptors) { @@ -363,7 +363,6 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co _setAccepting.signalAll(); } } - @Override public ConnectionFactory getConnectionFactory(String protocol) @@ -388,108 +387,105 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co public void addConnectionFactory(ConnectionFactory factory) { - try (Locker.Lock lock = _locker.lockIfNotHeld()) + if (isRunning()) + throw new IllegalStateException(getState()); + + Set to_remove = new HashSet<>(); + for (String key:factory.getProtocols()) { - Set to_remove = new HashSet<>(); - for (String key:factory.getProtocols()) + key=StringUtil.asciiToLowerCase(key); + ConnectionFactory old=_factories.remove(key); + if (old!=null) { - key=StringUtil.asciiToLowerCase(key); - ConnectionFactory old=_factories.remove(key); - if (old!=null) - { - if (old.getProtocol().equals(_defaultProtocol)) - _defaultProtocol=null; - to_remove.add(old); - } - _factories.put(key, factory); + if (old.getProtocol().equals(_defaultProtocol)) + _defaultProtocol=null; + to_remove.add(old); } - - // keep factories still referenced - for (ConnectionFactory f : _factories.values()) - to_remove.remove(f); - - // remove old factories - for (ConnectionFactory old: to_remove) - { - removeBean(old); - if (LOG.isDebugEnabled()) - LOG.debug("{} removed {}", this, old); - } - - // add new Bean - addBean(factory); - if (_defaultProtocol==null) - _defaultProtocol=factory.getProtocol(); - if (LOG.isDebugEnabled()) - LOG.debug("{} added {}", this, factory); + _factories.put(key, factory); } + + // keep factories still referenced + for (ConnectionFactory f : _factories.values()) + to_remove.remove(f); + + // remove old factories + for (ConnectionFactory old: to_remove) + { + removeBean(old); + if (LOG.isDebugEnabled()) + LOG.debug("{} removed {}", this, old); + } + + // add new Bean + addBean(factory); + if (_defaultProtocol==null) + _defaultProtocol=factory.getProtocol(); + if (LOG.isDebugEnabled()) + LOG.debug("{} added {}", this, factory); } public void addFirstConnectionFactory(ConnectionFactory factory) { - try (Locker.Lock lock = _locker.lock()) - { - List existings = new ArrayList<>(_factories.values()); - _factories.clear(); - addConnectionFactory(factory); - for (ConnectionFactory existing : existings) - addConnectionFactory(existing); - _defaultProtocol = factory.getProtocol(); - } + if (isRunning()) + throw new IllegalStateException(getState()); + + List existings = new ArrayList<>(_factories.values()); + _factories.clear(); + addConnectionFactory(factory); + for (ConnectionFactory existing : existings) + addConnectionFactory(existing); + _defaultProtocol = factory.getProtocol(); } public void addIfAbsentConnectionFactory(ConnectionFactory factory) { - try (Locker.Lock lock = _locker.lock()) + if (isRunning()) + throw new IllegalStateException(getState()); + + String key=StringUtil.asciiToLowerCase(factory.getProtocol()); + if (_factories.containsKey(key)) { - String key=StringUtil.asciiToLowerCase(factory.getProtocol()); - if (_factories.containsKey(key)) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} addIfAbsent ignored {}", this, factory); - } - else - { - _factories.put(key, factory); - addBean(factory); - if (_defaultProtocol==null) - _defaultProtocol=factory.getProtocol(); - if (LOG.isDebugEnabled()) - LOG.debug("{} addIfAbsent added {}", this, factory); - } + if (LOG.isDebugEnabled()) + LOG.debug("{} addIfAbsent ignored {}", this, factory); + } + else + { + _factories.put(key, factory); + addBean(factory); + if (_defaultProtocol==null) + _defaultProtocol=factory.getProtocol(); + if (LOG.isDebugEnabled()) + LOG.debug("{} addIfAbsent added {}", this, factory); } } public ConnectionFactory removeConnectionFactory(String protocol) { - try (Locker.Lock lock = _locker.lock()) - { - ConnectionFactory factory= _factories.remove(StringUtil.asciiToLowerCase(protocol)); - removeBean(factory); - return factory; - } + if (isRunning()) + throw new IllegalStateException(getState()); + + ConnectionFactory factory= _factories.remove(StringUtil.asciiToLowerCase(protocol)); + removeBean(factory); + return factory; } @Override public Collection getConnectionFactories() { - try (Locker.Lock lock = _locker.lock()) - { - return _factories.values(); - } + return _factories.values(); } public void setConnectionFactories(Collection factories) { - try (Locker.Lock lock = _locker.lock()) - { - List existing = new ArrayList<>(_factories.values()); - for (ConnectionFactory factory: existing) - removeConnectionFactory(factory.getProtocol()); - for (ConnectionFactory factory: factories) - if (factory!=null) - addConnectionFactory(factory); - } + if (isRunning()) + throw new IllegalStateException(getState()); + + List existing = new ArrayList<>(_factories.values()); + for (ConnectionFactory factory: existing) + removeConnectionFactory(factory.getProtocol()); + for (ConnectionFactory factory: factories) + if (factory!=null) + addConnectionFactory(factory); } @ManagedAttribute("The priority delta to apply to acceptor threads") @@ -521,18 +517,15 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co @ManagedAttribute("Protocols supported by this connector") public List getProtocols() { - synchronized (_factories) - { - return new ArrayList<>(_factories.keySet()); - } + return new ArrayList<>(_factories.keySet()); } public void clearConnectionFactories() { - synchronized (_factories) - { - _factories.clear(); - } + if (isRunning()) + throw new IllegalStateException(getState()); + + _factories.clear(); } @ManagedAttribute("This connector's default protocol") @@ -616,10 +609,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co if (_acceptorPriorityDelta!=0) thread.setPriority(Math.max(Thread.MIN_PRIORITY,Math.min(Thread.MAX_PRIORITY,priority+_acceptorPriorityDelta))); - synchronized (AbstractConnector.this) - { - _acceptors[_id] = thread; - } + _acceptors[_id] = thread; try { @@ -676,26 +666,6 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co } -// protected void connectionOpened(Connection connection) -// { -// _stats.connectionOpened(); -// connection.onOpen(); -// } -// -// protected void connectionClosed(Connection connection) -// { -// connection.onClose(); -// long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp(); -// _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut()); -// } -// -// public void connectionUpgraded(Connection oldConnection, Connection newConnection) -// { -// oldConnection.onClose(); -// _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut()); -// newConnection.onOpen(); -// } - @Override public Collection getConnectedEndPoints() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionLimit.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionLimit.java new file mode 100644 index 00000000000..2e4cd3ab4f7 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionLimit.java @@ -0,0 +1,150 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.Connection.Listener; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * A Connection Listener that limits the number of Connections. + *

    This listener applies a limit to the number of connections, which when + * exceeded results in a call to {@link AbstractConnector#setAccepting(boolean)} + * to prevent further connections being received. It can be applied to an + * entire server or to a specific connector. + *

    + * @see Connection.Listener + */ +@ManagedObject +public class ConnectionLimit extends AbstractLifeCycle implements Listener +{ + private static final Logger LOG = Log.getLogger(ConnectionLimit.class); + + private final Server _server; + private final List _connectors = new ArrayList<>(); + private int _connections; + private int _maxConnections; + private boolean _accepting = true; + + public ConnectionLimit(int maxConnections, Server server) + { + _maxConnections = maxConnections; + _server = server; + } + + public ConnectionLimit(int maxConnections, Connector...connectors) + { + _maxConnections = maxConnections; + _server = null; + for (Connector c: connectors) + { + if (c instanceof AbstractConnector) + _connectors.add((AbstractConnector)c); + else + LOG.warn("Connector {} is not an AbstractConnection. Connections not limited",c); + } + } + + @ManagedAttribute("The maximum number of connections allowed") + public synchronized int getMaxConnections() + { + return _maxConnections; + } + + public synchronized void setMaxConnections(int max) + { + _maxConnections = max; + } + + @ManagedAttribute("The current number of connections ") + public synchronized int getConnections() + { + return _connections; + } + + @Override + protected synchronized void doStart() throws Exception + { + if (_server!=null) + { + for (Connector c: _server.getConnectors()) + { + if (c instanceof AbstractConnector) + _connectors.add((AbstractConnector)c); + else + LOG.warn("Connector {} is not an AbstractConnection. Connections not limited",c); + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionLimit {} for {}",_maxConnections,_connectors); + + _connections = 0; + _accepting = true; + + for (AbstractConnector c : _connectors) + c.addBean(this); + } + + @Override + protected synchronized void doStop() throws Exception + { + for (AbstractConnector c : _connectors) + c.removeBean(this); + _connections = 0; + if (_server!=null) + _connectors.clear(); + } + + @Override + public synchronized void onOpened(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onOpen {} < {} {}",_connections, _maxConnections, connection); + if ( ++_connections >= _maxConnections && _accepting) + { + _accepting = false; + LOG.info("Connection Limit({}) reached for {}",_maxConnections,_connectors); + for (AbstractConnector c : _connectors) + c.setAccepting(false); + } + } + + @Override + public synchronized void onClosed(Connection connection) + { + if (LOG.isDebugEnabled()) + LOG.debug("onClosed {} < {} {}",_connections, _maxConnections, connection); + if ( --_connections < _maxConnections && !_accepting) + { + _accepting = true; + LOG.info("Connection Limit({}) cleared for {}",_maxConnections,_connectors); + for (AbstractConnector c : _connectors) + c.setAccepting(true); + } + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java index 5a0b9ea6f5e..1ef6d74cbb5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.server; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -46,7 +48,7 @@ import org.eclipse.jetty.util.thread.ThreadPool; * Low resources can be detected by: *

      *
    • {@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is - * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.
    • + * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.
    • *
    • If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()} * greater than {@link #getMaxMemory()}
    • @@ -60,6 +62,9 @@ import org.eclipse.jetty.util.thread.ThreadPool; * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the * {@link #getLowResourcesIdleTimeout()} to all connections again. Once the low resources state is * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}. + *

      + * If {@link #setAcceptingInLowResources(boolean)} is set to true, then no new connections are accepted + * when in low resources state. */ @ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected") public class LowResourceMonitor extends AbstractLifeCycle @@ -68,6 +73,7 @@ public class LowResourceMonitor extends AbstractLifeCycle private final Server _server; private Scheduler _scheduler; private Connector[] _monitoredConnectors; + private Set _acceptingConnectors = new HashSet<>(); private int _period=1000; private int _maxConnections; private long _maxMemory; @@ -78,6 +84,7 @@ public class LowResourceMonitor extends AbstractLifeCycle private String _cause; private String _reasons; private long _lowStarted; + private boolean _acceptingInLowResources = true; private final Runnable _monitor = new Runnable() { @@ -134,6 +141,17 @@ public class LowResourceMonitor extends AbstractLifeCycle _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]); } + @ManagedAttribute("If false, new connections are not accepted while in low resources") + public boolean isAcceptingInLowResources() + { + return _acceptingInLowResources; + } + + public void setAcceptingInLowResources(boolean acceptingInLowResources) + { + _acceptingInLowResources = acceptingInLowResources; + } + @ManagedAttribute("The monitor period in ms") public int getPeriod() { @@ -329,6 +347,15 @@ public class LowResourceMonitor extends AbstractLifeCycle { for(Connector connector : getMonitoredOrServerConnectors()) { + if (connector instanceof AbstractConnector) + { + AbstractConnector c = (AbstractConnector)connector; + if (c.isAccepting()) + { + _acceptingConnectors.add(c); + c.setAccepting(false); + } + } for (EndPoint endPoint : connector.getConnectedEndPoints()) endPoint.setIdleTimeout(_lowResourcesIdleTimeout); } @@ -341,6 +368,12 @@ public class LowResourceMonitor extends AbstractLifeCycle for (EndPoint endPoint : connector.getConnectedEndPoints()) endPoint.setIdleTimeout(connector.getIdleTimeout()); } + + for (AbstractConnector connector : _acceptingConnectors) + { + connector.setAccepting(true); + } + _acceptingConnectors.clear(); } private String low(String reasons, String newReason) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java index cb45294f410..8df5f42d0d0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java @@ -55,27 +55,27 @@ public abstract class NegotiatingServerConnection extends AbstractConnection this.engine = engine; } - protected List getProtocols() + public List getProtocols() { return protocols; } - protected String getDefaultProtocol() + public String getDefaultProtocol() { return defaultProtocol; } - protected Connector getConnector() + public Connector getConnector() { return connector; } - protected SSLEngine getSSLEngine() + public SSLEngine getSSLEngine() { return engine; } - protected String getProtocol() + public String getProtocol() { return protocol; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java index 2fbbb64094d..2abdec1a8f8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java @@ -32,28 +32,6 @@ import org.eclipse.jetty.io.ssl.SslConnection; public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory { - public static void checkProtocolNegotiationAvailable() - { - try - { - String javaVersion = System.getProperty("java.version"); - String alpnClassName = "org.eclipse.jetty.alpn.ALPN"; - if (javaVersion.startsWith("1.")) - { - Class klass = ClassLoader.getSystemClassLoader().loadClass(alpnClassName); - if (klass.getClassLoader() != null) - throw new IllegalStateException(alpnClassName + " must be on JVM boot classpath"); - } - else - { - NegotiatingServerConnectionFactory.class.getClassLoader().loadClass(alpnClassName); - } - } - catch (ClassNotFoundException x) - { - throw new IllegalStateException("No ALPN classes available"); - } - } private final List negotiatedProtocols; private String defaultProtocol; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index f8066c69604..f6c4f9728aa 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -2244,7 +2244,7 @@ public class Request implements HttpServletRequest event.setDispatchContext(getServletContext()); String uri = ((HttpServletRequest)servletRequest).getRequestURI(); - if (uri.startsWith(_contextPath)) + if (_contextPath!=null && uri.startsWith(_contextPath)) uri = uri.substring(_contextPath.length()); else // TODO probably need to strip encoded context from requestURI, but will do this for now: diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 20878dd6bea..9e39f3d22a4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -402,11 +402,7 @@ public class Server extends HandlerWrapper implements Attributes acceptors += abstractConnector.getAcceptors(); if (connector instanceof ServerConnector) - { - // The SelectorManager uses 2 threads for each selector, - // one for the normal and one for the low priority strategies. - selectors += 2 * ((ServerConnector)connector).getSelectorManager().getSelectorCount(); - } + selectors += ((ServerConnector)connector).getSelectorManager().getSelectorCount(); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 9b5f4ca3e38..11a40c6dffd 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -109,7 +109,8 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,W { Context scontext = ContextHandler.getCurrentContext(); _context = (scontext == null?null:scontext.getContextHandler()); - _mimeTypes = _context == null?new MimeTypes():_context.getMimeTypes(); + if (_mimeTypes==null) + _mimeTypes = _context == null?new MimeTypes():_context.getMimeTypes(); _resourceService.setContentFactory(new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats())); _resourceService.setWelcomeFactory(this); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 9e6c6a74da1..1041411005a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; +import java.util.ListIterator; import java.util.Set; import java.util.zip.Deflater; @@ -495,22 +496,27 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } // Special handling for etags - String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH); - if (etag!=null) + for (ListIterator fields = baseRequest.getHttpFields().listIterator(); fields.hasNext();) { - int i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote); - if (i>0) + HttpField field = fields.next(); + if (field.getHeader()==HttpHeader.IF_NONE_MATCH || field.getHeader()==HttpHeader.IF_MATCH) { - baseRequest.setAttribute("o.e.j.s.h.gzip.GzipHandler.etag",etag); - while (i>=0) + String etag = field.getValue(); + int i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote); + if (i>0) { - etag=etag.substring(0,i)+etag.substring(i+CompressedContentFormat.GZIP._etag.length()); - i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote,i); - } - baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag)); + baseRequest.setAttribute("o.e.j.s.h.gzip.GzipHandler.etag",etag); + while (i>=0) + { + etag=etag.substring(0,i)+etag.substring(i+CompressedContentFormat.GZIP._etag.length()); + i=etag.indexOf(CompressedContentFormat.GZIP._etagQuote,i); + } + + fields.set(new HttpField(field.getHeader(),etag)); + } } } - + HttpOutput.Interceptor orig_interceptor = out.getInterceptor(); try { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java index 4f72e7e8f8e..b34eaf3cf3d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java @@ -628,7 +628,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements { boolean dsdel = _sessionDataStore.delete(id); - if (LOG.isDebugEnabled()) LOG.debug("Session {} deleted in db {}",id, dsdel); + if (LOG.isDebugEnabled()) LOG.debug("Session {} deleted in session data store {}",id, dsdel); } //delete it from the session object store @@ -656,7 +656,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements return Collections.emptySet(); if (LOG.isDebugEnabled()) - LOG.debug("SessionDataStore checking expiration on {}", candidates); + LOG.debug("{} checking expiration on {}", this, candidates); Set allCandidates = _sessionDataStore.getExpired(candidates); Set sessionsInUse = new HashSet<>(); if (allCandidates != null) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HouseKeeper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HouseKeeper.java index 73b35d564cf..f294be51288 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HouseKeeper.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HouseKeeper.java @@ -261,7 +261,7 @@ public class HouseKeeper extends AbstractLifeCycle return; if (LOG.isDebugEnabled()) - LOG.debug("Scavenging sessions"); + LOG.debug("{} scavenging sessions", this); //find the session managers for (SessionHandler manager:_sessionIdManager.getSessionHandlers()) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java index 859736992a6..84ff111678d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java @@ -137,7 +137,7 @@ public class Session implements SessionHandler.SessionIf // True if: // 1. the session is still valid // BUT if passivated out to disk, do we really want this timer to keep going off? - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return isValid() && isResident(); } @@ -198,7 +198,7 @@ public class Session implements SessionHandler.SessionIf */ public long getRequests() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _requests; } @@ -217,7 +217,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------- */ protected void cookieSet() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { _sessionData.setCookieSet(_sessionData.getAccessed()); } @@ -225,7 +225,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------ */ protected boolean access(long time) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { if (!isValid()) return false; @@ -249,7 +249,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------ */ protected void complete() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { _requests--; } @@ -265,7 +265,7 @@ public class Session implements SessionHandler.SessionIf */ protected boolean isExpiredAt(long time) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _sessionData.isExpiredAt(time); } @@ -281,7 +281,7 @@ public class Session implements SessionHandler.SessionIf protected boolean isIdleLongerThan (int sec) { long now = System.currentTimeMillis(); - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return ((_sessionData.getAccessed() + (sec*1000)) <= now); } @@ -384,7 +384,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------ */ public boolean isValid() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _state==State.VALID; } @@ -394,7 +394,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------- */ public long getCookieSetTime() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _sessionData.getCookieSet(); } @@ -405,7 +405,7 @@ public class Session implements SessionHandler.SessionIf @Override public long getCreationTime() throws IllegalStateException { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForRead(); return _sessionData.getCreated(); @@ -420,7 +420,7 @@ public class Session implements SessionHandler.SessionIf @Override public String getId() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _sessionData.getId(); } @@ -450,7 +450,7 @@ public class Session implements SessionHandler.SessionIf @Override public long getLastAccessedTime() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _sessionData.getLastAccessed(); } @@ -473,7 +473,7 @@ public class Session implements SessionHandler.SessionIf @Override public void setMaxInactiveInterval(int secs) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { _sessionData.setMaxInactiveMs((long)secs*1000L); _sessionData.calcAndSetExpiry(); @@ -496,7 +496,7 @@ public class Session implements SessionHandler.SessionIf */ public void updateInactivityTimer () { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { if (LOG.isDebugEnabled())LOG.debug("updateInactivityTimer"); @@ -556,7 +556,7 @@ public class Session implements SessionHandler.SessionIf */ public void stopInactivityTimer () { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { if (_sessionInactivityTimer != null) { @@ -573,7 +573,7 @@ public class Session implements SessionHandler.SessionIf @Override public int getMaxInactiveInterval() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return (int)(_sessionData.getMaxInactiveMs()/1000); } @@ -653,7 +653,7 @@ public class Session implements SessionHandler.SessionIf @Override public Object getAttribute(String name) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForRead(); return _sessionData.getAttribute(name); @@ -667,7 +667,7 @@ public class Session implements SessionHandler.SessionIf @Deprecated public Object getValue(String name) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _sessionData.getAttribute(name); } @@ -679,7 +679,7 @@ public class Session implements SessionHandler.SessionIf @Override public Enumeration getAttributeNames() { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForRead(); final Iterator itor = _sessionData.getKeys().iterator(); @@ -730,7 +730,7 @@ public class Session implements SessionHandler.SessionIf @Override public String[] getValueNames() throws IllegalStateException { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForRead(); Iterator itor = _sessionData.getKeys().iterator(); @@ -751,7 +751,7 @@ public class Session implements SessionHandler.SessionIf public void setAttribute(String name, Object value) { Object old=null; - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { //if session is not valid, don't accept the set checkValidForWrite(); @@ -812,7 +812,7 @@ public class Session implements SessionHandler.SessionIf String id = null; String extendedId = null; - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForWrite(); //don't renew id on a session that is not valid id = _sessionData.getId(); //grab the values as they are now @@ -820,7 +820,7 @@ public class Session implements SessionHandler.SessionIf } String newId = _handler._sessionIdManager.renewSessionId(id, extendedId, request); - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForWrite(); _sessionData.setId(newId); @@ -877,7 +877,7 @@ public class Session implements SessionHandler.SessionIf */ public Lock lockIfNotHeld () { - return _lock.lockIfNotHeld(); + return _lock.lock(); } /* ------------------------------------------------------------- */ @@ -888,7 +888,7 @@ public class Session implements SessionHandler.SessionIf { boolean result = false; - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { switch (_state) { @@ -934,7 +934,7 @@ public class Session implements SessionHandler.SessionIf */ protected void finishInvalidate() throws IllegalStateException { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { try { @@ -970,7 +970,7 @@ public class Session implements SessionHandler.SessionIf @Override public boolean isNew() throws IllegalStateException { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { checkValidForRead(); return _newSession; @@ -982,7 +982,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------- */ public void setIdChanged(boolean changed) { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { _idChanged=changed; } @@ -992,7 +992,7 @@ public class Session implements SessionHandler.SessionIf /* ------------------------------------------------------------- */ public boolean isIdChanged () { - try (Lock lock = _lock.lockIfNotHeld()) + try (Lock lock = _lock.lock()) { return _idChanged; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java index eed33686245..5ddb073d03c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java @@ -1265,7 +1265,7 @@ public class SessionHandler extends ScopedHandler Set candidates = new HashSet(Arrays.asList(ss)); _candidateSessionIdsForExpiry.removeAll(candidates); if (LOG.isDebugEnabled()) - LOG.debug("Scavenging session ids {}", candidates); + LOG.debug("{} scavenging session ids {}", this, candidates); try { candidates = _sessionCache.checkExpiration(candidates); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java index abb038f07dc..67ddd847f7b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java @@ -384,9 +384,15 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture os.flush(); // Read the response. - String response = readResponse(client); - - Assert.assertThat(response, Matchers.containsString("HTTP/1.1 431 ")); + try + { + String response = readResponse(client); + Assert.assertThat(response, Matchers.containsString("HTTP/1.1 431 ")); + } + catch(Exception e) + { + // TODO evaluate why we sometimes get an early close on this test + } } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java index 085551b1dfd..971d42c697a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java @@ -31,6 +31,7 @@ import java.util.concurrent.CountDownLatch; import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.TimerScheduler; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -118,6 +119,51 @@ public class LowResourcesMonitorTest } + @Test + public void testNotAccepting() throws Exception + { + _lowResourcesMonitor.setAcceptingInLowResources(false); + Thread.sleep(1200); + _threadPool.setMaxThreads(_threadPool.getThreads()-_threadPool.getIdleThreads()+10); + Thread.sleep(1200); + Assert.assertFalse(_lowResourcesMonitor.isLowOnResources()); + + for (AbstractConnector c : _server.getBeans(AbstractConnector.class)) + assertThat(c.isAccepting(),Matchers.is(true)); + + final CountDownLatch latch = new CountDownLatch(1); + for (int i=0;i<100;i++) + { + _threadPool.execute(new Runnable() + { + @Override + public void run() + { + try + { + latch.await(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + }); + } + + Thread.sleep(1200); + Assert.assertTrue(_lowResourcesMonitor.isLowOnResources()); + for (AbstractConnector c : _server.getBeans(AbstractConnector.class)) + assertThat(c.isAccepting(),Matchers.is(false)); + + latch.countDown(); + Thread.sleep(1200); + Assert.assertFalse(_lowResourcesMonitor.isLowOnResources()); + for (AbstractConnector c : _server.getBeans(AbstractConnector.class)) + assertThat(c.isAccepting(),Matchers.is(true)); + } + + @Ignore ("not reliable") @Test public void testLowOnMemory() throws Exception @@ -155,18 +201,18 @@ public class LowResourcesMonitorTest Thread.sleep(1200); Assert.assertTrue(_lowResourcesMonitor.isLowOnResources()); - Socket newSocket = new Socket("localhost",_connector.getLocalPort()); - - // wait for low idle time to close sockets, but not new Socket - Thread.sleep(1200); - Assert.assertFalse(_lowResourcesMonitor.isLowOnResources()); + try(Socket newSocket = new Socket("localhost",_connector.getLocalPort())) + { + // wait for low idle time to close sockets, but not new Socket + Thread.sleep(1200); + Assert.assertFalse(_lowResourcesMonitor.isLowOnResources()); - for (int i=0;i map = request.getParameterMap(); + // should have thrown a BadMessageException + return false; + } + }; + + //Send a request with query string with illegal hex code to cause + //an exception parsing the params + String request="GET /?test_%e0%x8%81=missing HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: text/html;charset=utf8\n"+ + "Connection: close\n"+ + "\n"; + + String responses=_connector.getResponse(request); + assertThat("Responses", responses, startsWith("HTTP/1.1 400")); + } + @Test public void testEmptyHeaders() throws Exception { diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java index 75224b399c8..6348b7a022e 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.start; import java.io.IOException; import java.net.URI; -import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -30,12 +29,11 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.builders.StartDirBuilder; import org.eclipse.jetty.start.builders.StartIniBuilder; import org.eclipse.jetty.start.fileinits.BaseHomeFileInitializer; -import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer; import org.eclipse.jetty.start.fileinits.LocalFileInitializer; +import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer; import org.eclipse.jetty.start.fileinits.TestFileInitializer; import org.eclipse.jetty.start.fileinits.UriFileInitializer; diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java index 7650affa1fb..4f9ecf469fa 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java @@ -29,7 +29,6 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.Set; -import java.util.stream.Stream; /** * Interface for initializing a file resource. diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java b/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java index 59b667500bb..5fe87ac85e0 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java @@ -21,8 +21,14 @@ package org.eclipse.jetty.start; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Java Version Utility class. + *

      Parses java versions to extract a consistent set of version parts

      + */ public class JavaVersion { + // Copy of code in jetty-util + private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?"); // Regexp from JEP 223 (http://openjdk.java.net/jeps/223). private static final Pattern JDK9 = Pattern.compile("(\\d+)(\\.(\\d+))?(\\.(\\d+))?((-.+)?(\\+(\\d+)?(-.+)?)?)"); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java index ee18cf084e2..179411fb425 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.start; -import java.io.BufferedWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java index ff5a219e1a0..c18008389c0 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.start.builders; import java.io.BufferedWriter; import java.io.IOException; -import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java index 0898c2d11ba..c223b557cac 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java @@ -19,18 +19,12 @@ package org.eclipse.jetty.start.fileinits; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import org.eclipse.jetty.start.BaseHome; -import org.eclipse.jetty.start.FS; import org.eclipse.jetty.start.FileInitializer; -import org.eclipse.jetty.start.StartLog; public class UriFileInitializer extends FileInitializer { diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java index 67e342086ff..fe74a333e32 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java @@ -28,9 +28,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestTracker; -import org.eclipse.jetty.toolchain.test.IO; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java index da29578295c..d7dd86af765 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java @@ -18,6 +18,11 @@ package org.eclipse.jetty.start; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -32,11 +37,6 @@ import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Rule; import org.junit.Test; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; - public class ModulesTest { private final static String TEST_SOURCE = ""; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java b/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java new file mode 100644 index 00000000000..6dd630251ec --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java @@ -0,0 +1,166 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Java Version Utility class. + *

      Parses java versions to extract a consistent set of version parts

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

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

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

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

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

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

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

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

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

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

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

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

      + * + * @return the remaining string after the version numbers + */ + public String getSuffix() + { + return suffix; + } + + public String toString() + { + return version; + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java new file mode 100644 index 00000000000..a8720cefdf4 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java @@ -0,0 +1,250 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; + +/** + *

      Utility class to handle a Multi Release Jar file

      + */ +public class MultiReleaseJarFile +{ + private static final String META_INF_VERSIONS = "META-INF/versions/"; + + private final JarFile jarFile; + private final int majorVersion; + private final boolean multiRelease; + + /* Map to hold unversioned name to VersionedJarEntry */ + private final Map entries; + + /** + * Construct a multi release jar file for the current JVM version, ignoring directories. + * @param file The file to open + */ + public MultiReleaseJarFile(File file) throws IOException + { + this(file,JavaVersion.VERSION.getMajor(),false); + } + + /** + * Construct a multi release jar file + * @param file The file to open + * @param majorVersion The major JVM version to apply when selecting a version. + * @param includeDirectories true if any directory entries should not be ignored + * @throws IOException if the jar file cannot be read + */ + public MultiReleaseJarFile(File file, int majorVersion, boolean includeDirectories) throws IOException + { + if (file==null || !file.exists() || !file.canRead() || file.isDirectory()) + throw new IllegalArgumentException("bad jar file: "+file); + + jarFile = new JarFile(file,true,JarFile.OPEN_READ); + this.majorVersion = majorVersion; + + Manifest manifest = jarFile.getManifest(); + if (manifest==null) + multiRelease = false; + else + multiRelease = Boolean.valueOf(String.valueOf(manifest.getMainAttributes().getValue("Multi-Release"))); + + Map map = new TreeMap<>(); + jarFile.stream() + .map(VersionedJarEntry::new) + .filter(e->(includeDirectories||!e.isDirectory()) && e.isApplicable()) + .forEach(e->map.compute(e.name, (k, v) -> v==null || v.isReplacedBy(e) ? e : v)); + + for (Iterator> i = map.entrySet().iterator();i.hasNext();) + { + Map.Entry e = i.next(); + VersionedJarEntry entry = e.getValue(); + + if (entry.inner) + { + VersionedJarEntry outer = map.get(entry.outer); + + if (entry.outer==null || outer.version!= entry.version) + i.remove(); + } + } + + entries = Collections.unmodifiableMap(map); + } + + /** + * @return true IFF the jar is a multi release jar + */ + public boolean isMultiRelease() + { + return multiRelease; + } + + /** + * @return The major version applied to this jar for the purposes of selecting entries + */ + public int getVersion() + { + return majorVersion; + } + + /** + * @return A stream of versioned entries from the jar, excluded any that are not applicable + */ + public Stream stream() + { + return entries.values().stream(); + } + + /** Get a versioned resource entry by name + * @param name The unversioned name of the resource + * @return The versioned entry of the resource + */ + public VersionedJarEntry getEntry(String name) + { + return entries.get(name); + } + + @Override + public String toString() + { + return String.format("%s[%b,%d]",jarFile.getName(),isMultiRelease(),getVersion()); + } + + /** + * A versioned Jar entry + */ + public class VersionedJarEntry + { + final JarEntry entry; + final String name; + final int version; + final boolean inner; + final String outer; + + VersionedJarEntry(JarEntry entry) + { + int v = 0; + String name = entry.getName(); + if (name.startsWith(META_INF_VERSIONS)) + { + v = -1; + int index = name.indexOf('/', META_INF_VERSIONS.length()); + if (index > META_INF_VERSIONS.length() && index < name.length()) + { + try + { + v = TypeUtil.parseInt(name, META_INF_VERSIONS.length(), index - META_INF_VERSIONS.length(), 10); + name = name.substring(index + 1); + } + catch (NumberFormatException x) + { + throw new RuntimeException("illegal version in "+jarFile,x); + } + } + } + + this.entry = entry; + this.name = name; + this.version = v; + this.inner = name.contains("$") && name.toLowerCase().endsWith(".class"); + this.outer = inner ? name.substring(0, name.indexOf('$')) + name.substring(name.length() - 6, name.length()) : null; + } + + /** + * @return the unversioned name of the resource + */ + public String getName() + { + return name; + } + + /** + * @return The name of the resource within the jar, which could be versioned + */ + public String getNameInJar() + { + return entry.getName(); + } + + /** + * @return The version of the resource or 0 for a base version + */ + public int getVersion() + { + return version; + } + + /** + * + * @return True iff the entry is not from the base version + */ + public boolean isVersioned() + { + return version > 0; + } + + /** + * + * @return True iff the entry is a directory + */ + public boolean isDirectory() + { + return entry.isDirectory(); + } + + /** + * @return An input stream of the content of the versioned entry. + * @throws IOException if something goes wrong! + */ + public InputStream getInputStream() throws IOException + { + return jarFile.getInputStream(entry); + } + + boolean isApplicable() + { + if (multiRelease) + return this.version>=0 && this.version <= majorVersion && name.length()>0; + return this.version==0; + } + + boolean isReplacedBy(VersionedJarEntry entry) + { + if (isDirectory()) + return entry.version==0; + return this.name.equals(entry.name) && entry.version>version; + } + + @Override + public String toString() + { + return String.format("%s->%s[%d]",name,entry.getName(),version); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java b/jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java index d4adb9e03a7..9e9c0c32d1c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java @@ -28,19 +28,15 @@ import java.lang.reflect.Field; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.PathMatcher; -import java.nio.file.SimpleFileVisitor; import java.nio.file.WatchEvent; -import java.nio.file.WatchEvent.Kind; import java.nio.file.WatchKey; import java.nio.file.WatchService; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.AbstractSet; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EventListener; import java.util.HashMap; @@ -51,7 +47,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Scanner; -import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -71,7 +66,7 @@ import org.eclipse.jetty.util.log.Logger; */ public class PathWatcher extends AbstractLifeCycle implements Runnable { - public static class Config + public static class Config implements Predicate { public static final int UNLIMITED_DEPTH = -9999; @@ -87,22 +82,64 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable PATTERN_SEP = sep; } - protected final Path dir; + protected final Config parent; + protected final Path path; protected final IncludeExcludeSet includeExclude; protected int recurseDepth = 0; // 0 means no sub-directories are scanned protected boolean excludeHidden = false; + protected long pauseUntil; public Config(Path path) { - this(path,new IncludeExcludeSet<>(PathMatcherSet.class)); + this(path,null); } - public Config(Path path, IncludeExcludeSet includeExclude) + public Config(Path path, Config parent) { - this.dir = path; - this.includeExclude = includeExclude; + this.parent = parent; + this.includeExclude = parent==null ? new IncludeExcludeSet<>(PathMatcherSet.class) : parent.includeExclude; + + Path dir = path; + if (!Files.exists(path)) + throw new IllegalStateException("Path does not exist: "+path); + + if (!Files.isDirectory(path)) + { + dir = path.getParent(); + includeExclude.include(new ExactPathMatcher(path)); + setRecurseDepth(0); + } + + this.path = dir; } + public Config getParent() + { + return parent; + } + + public void setPauseUntil(long time) + { + if (time>pauseUntil) + pauseUntil=time; + } + + public boolean isPaused(long now) + { + if (pauseUntil==0) + return false; + if (pauseUntil>now) + { + if (LOG.isDebugEnabled()) + LOG.debug("PAUSED {}",this); + return true; + } + if (LOG.isDebugEnabled()) + LOG.debug("unpaused {}",this); + pauseUntil = 0; + return false; + } + /** * Add an exclude PathMatcher * @@ -126,10 +163,8 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable public void addExclude(final String syntaxAndPattern) { if (LOG.isDebugEnabled()) - { LOG.debug("Adding exclude: [{}]",syntaxAndPattern); - } - addExclude(dir.getFileSystem().getPathMatcher(syntaxAndPattern)); + addExclude(path.getFileSystem().getPathMatcher(syntaxAndPattern)); } /** @@ -151,7 +186,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable */ public void addExcludeGlobRelative(String pattern) { - addExclude(toGlobPattern(dir,pattern)); + addExclude(toGlobPattern(path,pattern)); } /** @@ -166,9 +201,6 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable LOG.debug("Adding hidden files and directories to exclusions"); } excludeHidden = true; - - addExclude("regex:^.*" + PATTERN_SEP + "\\..*$"); // ignore hidden files - addExclude("regex:^.*" + PATTERN_SEP + "\\..*" + PATTERN_SEP + ".*$"); // ignore files in hidden directories } } @@ -211,7 +243,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable { LOG.debug("Adding include: [{}]",syntaxAndPattern); } - addInclude(dir.getFileSystem().getPathMatcher(syntaxAndPattern)); + addInclude(path.getFileSystem().getPathMatcher(syntaxAndPattern)); } /** @@ -233,7 +265,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable */ public void addIncludeGlobRelative(String pattern) { - addInclude(toGlobPattern(dir,pattern)); + addInclude(toGlobPattern(path,pattern)); } /** @@ -262,16 +294,17 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable */ public Config asSubConfig(Path dir) { - Config subconfig = new Config(dir,includeExclude); - if (dir == this.dir) - subconfig.recurseDepth = this.recurseDepth; // TODO shouldn't really do a subconfig for this + Config subconfig = new Config(dir,this); + if (dir == this.path) + throw new IllegalStateException("sub "+dir.toString()+" of "+this); + + if (this.recurseDepth == UNLIMITED_DEPTH) + subconfig.recurseDepth = UNLIMITED_DEPTH; else - { - if (this.recurseDepth == UNLIMITED_DEPTH) - subconfig.recurseDepth = UNLIMITED_DEPTH; - else - subconfig.recurseDepth = this.recurseDepth - (dir.getNameCount() - this.dir.getNameCount()); - } + subconfig.recurseDepth = this.recurseDepth - (dir.getNameCount() - this.path.getNameCount()); + + if (LOG.isDebugEnabled()) + LOG.debug("subconfig {} of {}",subconfig,path); return subconfig; } @@ -287,46 +320,52 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable public Path getPath () { - return this.dir; + return this.path; } - @Deprecated - public boolean isExcluded(Path dir) throws IOException + public Path resolve(Path path) { - if (excludeHidden) + if (Files.isDirectory(this.path)) + return this.path.resolve(path); + if (Files.exists(this.path)) + return this.path; + return path; + } + + public boolean test(Path path) + { + if (excludeHidden && isHidden(path)) { - if (Files.isHidden(dir)) + if (LOG.isDebugEnabled()) + LOG.debug("test({}) -> [Hidden]", toShortPath(path)); + return false; + } + + if (!path.startsWith(this.path)) + { + if (LOG.isDebugEnabled()) + LOG.debug("test({}) -> [!child {}]", toShortPath(path), this.path); + return false; + } + + if (recurseDepth!=UNLIMITED_DEPTH) + { + int depth = path.getNameCount() - this.path.getNameCount() - 1; + + if (depth>recurseDepth) { - if (NOISY_LOG.isDebugEnabled()) - { - NOISY_LOG.debug("isExcluded [Hidden] on {}",dir); - } - return true; + if (LOG.isDebugEnabled()) + LOG.debug("test({}) -> [depth {}>{}]",toShortPath(path),depth,recurseDepth); + return false; } } - boolean matched = ((PathMatcherSet)includeExclude.getExcluded()).test(dir); - if (NOISY_LOG.isDebugEnabled()) - { - NOISY_LOG.debug("isExcluded [{}] on {}",matched,dir); - } - return matched; - } + boolean matched = includeExclude.test(path); - @Deprecated - public boolean isIncluded(Path dir) - { - boolean matched = ((PathMatcherSet)includeExclude.getIncluded()).test(dir); - if (NOISY_LOG.isDebugEnabled()) - { - NOISY_LOG.debug("isIncluded [{}] on {}",matched,dir); - } - return matched; - } + if (LOG.isDebugEnabled()) + LOG.debug("test({}) -> {}", toShortPath(path), matched); - public boolean matches(Path path) - { - return includeExclude.test(path); + return matched; } /** @@ -342,32 +381,6 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable this.recurseDepth = depth; } - - - /** - * Determine if the provided child directory should be recursed into based on the configured {@link #setRecurseDepth(int)} - * - * @param child - * the child directory to test against - * @return true if recurse should occur, false otherwise - */ - public boolean shouldRecurseDirectory(Path child) - { - if (!child.startsWith(dir)) - { - // not part of parent? don't recurse - return false; - } - - //If not limiting depth, should recurse all - if (isRecurseDepthUnlimited()) - return true; - - //Depth limited, check it - int childDepth = dir.relativize(child).getNameCount(); - return (childDepth <= recurseDepth); - } - private String toGlobPattern(Path path, String subPattern) { StringBuilder s = new StringBuilder(); @@ -379,9 +392,9 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable Path root = path.getRoot(); if (root != null) { - if (NOISY_LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) { - NOISY_LOG.debug("Path: {} -> Root: {}",path,root); + LOG.debug("Path: {} -> Root: {}", path, root); } for (char c : root.toString().toCharArray()) { @@ -434,116 +447,76 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable return s.toString(); } + + DirAction handleDir(Path path) + { + try + { + if (!Files.isDirectory(path)) + return DirAction.IGNORE; + if (excludeHidden && isHidden(path)) + return DirAction.IGNORE; + if (getRecurseDepth()==0) + return DirAction.WATCH; + return DirAction.ENTER; + } + catch(Exception e) + { + LOG.ignore(e); + return DirAction.IGNORE; + } + } + + public boolean isHidden(Path path) + { + try + { + if (!path.startsWith(this.path)) + return true; + for (int i=this.path.getNameCount(); i 0) - { - s.append(" [depth=").append(recurseDepth).append("]"); - } + s.append(path).append(" [depth="); + if (recurseDepth==UNLIMITED_DEPTH) + s.append("UNLIMITED"); + else + s.append(recurseDepth); + s.append(']'); return s.toString(); } + } - public static class DepthLimitedFileVisitor extends SimpleFileVisitor + + public static enum DirAction { - private Config base; - private PathWatcher watcher; - - public DepthLimitedFileVisitor (PathWatcher watcher, Config base) - { - this.base = base; - this.watcher = watcher; - } - - /* - * 2 situations: - * - * 1. a subtree exists at the time a dir to watch is added (eg watching /tmp/xxx and it contains aaa/) - * - will start with /tmp/xxx for which we want to register with the poller - * - want to visit each child - * - if child is file, gen add event - * - if child is dir, gen add event but ONLY register it if inside depth limit and ONLY continue visit of child if inside depth limit - * 2. a subtree is added inside a watched dir (eg watching /tmp/xxx, add aaa/ to xxx/) - * - will start with /tmp/xxx/aaa - * - gen add event but ONLY register it if inside depth limit and ONLY continue visit of children if inside depth limit - * - */ - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException - { - //In a directory: - // 1. the dir is the base directory - // - register it with the poll mechanism - // - generate pending add event (iff notifiable and matches patterns) - // - continue the visit (sibling dirs, sibling files) - // 2. the dir is a subdir at some depth in the basedir's tree - // - if the level of the subdir less or equal to base's limit - // - register it wih the poll mechanism - // - generate pending add event (iff notifiable and matches patterns) - // - else stop visiting this dir - - if (!base.isExcluded(dir)) - { - if (base.isIncluded(dir)) - { - if (watcher.isNotifiable()) - { - // Directory is specifically included in PathMatcher, then - // it should be notified as such to interested listeners - PathWatchEvent event = new PathWatchEvent(dir,PathWatchEventType.ADDED); - if (LOG.isDebugEnabled()) - { - LOG.debug("Pending {}",event); - } - watcher.addToPendingList(dir, event); - } - } - - //Register the dir with the watcher if it is: - // - the base dir and recursion is unlimited - // - the base dir and its depth is 0 (meaning we want to capture events from it, but not necessarily its children) - // - the base dir and we are recursing it and the depth is within the limit - // - a child dir and its depth is within the limits - if ((base.getPath().equals(dir) && (base.isRecurseDepthUnlimited() || base.getRecurseDepth() >= 0)) || base.shouldRecurseDirectory(dir)) - watcher.register(dir,base); - } - - //Continue walking the tree of this dir if it is: - // - the base dir and recursion is unlimited - // - the base dir and we're not recursing in it - // - the base dir and we are recursing it and the depth is within the limit - // - a child dir and its depth is within the limits - if ((base.getPath().equals(dir)&& (base.isRecurseDepthUnlimited() || base.getRecurseDepth() >= 0)) || base.shouldRecurseDirectory(dir)) - return FileVisitResult.CONTINUE; - else - return FileVisitResult.SKIP_SUBTREE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException - { - // In a file: - // - register with poll mechanism - // - generate pending add event (iff notifiable and matches patterns) - - if (base.matches(file) && watcher.isNotifiable()) - { - PathWatchEvent event = new PathWatchEvent(file,PathWatchEventType.ADDED); - if (LOG.isDebugEnabled()) - { - LOG.debug("Pending {}",event); - } - watcher.addToPendingList(file, event); - } - - return FileVisitResult.CONTINUE; - } - + IGNORE, WATCH, ENTER; } - + /** * Listener for path change events */ @@ -561,30 +534,38 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable { void onPathWatchEvents(List events); } - + /** * PathWatchEvent * * Represents a file event. Reported to registered listeners. */ - public static class PathWatchEvent + public class PathWatchEvent { private final Path path; private final PathWatchEventType type; - private int count = 0; + private final Config config; + long checked; + long modified; + long length; - public PathWatchEvent(Path path, PathWatchEventType type) + public PathWatchEvent(Path path, PathWatchEventType type, Config config) { this.path = path; - this.count = 1; this.type = type; - + this.config = config; + checked = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + check(); } - public PathWatchEvent(Path path, WatchEvent event) + public Config getConfig() + { + return config; + } + + public PathWatchEvent(Path path, WatchEvent event, Config config) { this.path = path; - this.count = event.count(); if (event.kind() == ENTRY_CREATE) { this.type = PathWatchEventType.ADDED; @@ -601,8 +582,63 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable { this.type = PathWatchEventType.UNKNOWN; } + this.config = config; + checked = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + check(); + } + + private void check() + { + if (Files.exists(path)) + { + try + { + modified = Files.getLastModifiedTime(path).toMillis(); + length = Files.size(path); + } + catch(IOException e) + { + modified = -1; + length = -1; + } + } + else + { + modified = -1; + length = -1; + } } + public boolean isQuiet(long now, long quietTime) + { + long lastModified = modified; + long lastLength = length; + + check(); + + if (lastModified == modified && lastLength == length) + return (now-checked)>=quietTime; + + checked = now; + return false; + } + + public long toQuietCheck(long now, long quietTime) + { + long check = quietTime - (now-checked); + if (check<=0) + return quietTime; + return check; + } + + public void modified() + { + long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + checked = now; + check(); + config.setPauseUntil(now+getUpdateQuietTimeMillis()); + } + /** * @see java.lang.Object#equals(java.lang.Object) */ @@ -650,14 +686,10 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable return type; } - public void incrementCount(int num) - { - count += num; - } - + @Deprecated public int getCount() { - return count; + return 1; } /** @@ -679,121 +711,10 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable @Override public String toString() { - return String.format("PathWatchEvent[%s|%s]",type,path); + return String.format("PathWatchEvent[%8s|%s]",type,path); } } - - - - /** - * PathPendingEvents - * - * For a given path, a list of events that are awaiting the - * quiet time. The list is in the order that the event were - * received from the WatchService - */ - public static class PathPendingEvents - { - private Path _path; - private List _events; - private long _timestamp; - private long _lastFileSize = -1; - public PathPendingEvents (Path path) - { - _path = path; - } - - public PathPendingEvents (Path path, PathWatchEvent event) - { - this (path); - addEvent(event); - } - - public void addEvent (PathWatchEvent event) - { - long now = System.currentTimeMillis(); - _timestamp = now; - - if (_events == null) - { - _events = new ArrayList(); - _events.add(event); - } - else - { - //Check if the same type of event is already present, in which case we - //can increment its counter. Otherwise, add it - PathWatchEvent existingType = null; - for (PathWatchEvent e:_events) - { - if (e.getType() == event.getType()) - { - existingType = e; - break; - } - } - - if (existingType == null) - { - _events.add(event); - } - else - { - existingType.incrementCount(event.getCount()); - } - } - - } - - public List getEvents() - { - return _events; - } - - public long getTimestamp() - { - return _timestamp; - } - - - /** - * Check to see if the file referenced by this Event is quiet. - *

      - * Will validate the timestamp to see if it is expired, as well as if the file size hasn't changed within the quiet period. - *

      - * Always updates timestamp to 'now' on use of this method. - * - * @param now the time now - * - * @param expiredDuration - * the expired duration past the timestamp to be considered expired - * @param expiredUnit - * the unit of time for the expired check - * @return true if expired, false if not - */ - public boolean isQuiet(long now, long expiredDuration, TimeUnit expiredUnit) - { - - long pastdue = _timestamp + expiredUnit.toMillis(expiredDuration); - _timestamp = now; - - long fileSize = _path.toFile().length(); // File.length() returns 0 for non existant files - boolean fileSizeChanged = (_lastFileSize != fileSize); - _lastFileSize = fileSize; - - if ((now > pastdue) && (!fileSizeChanged /*|| fileSize == 0*/)) - { - // Quiet period timestamp has expired, and file size hasn't changed, or the file - // has been deleted. - // Consider this a quiet event now. - return true; - } - - return false; - } - - } /** * PathWatchEventType @@ -806,7 +727,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable } private static final boolean IS_WINDOWS; - + static { String os = System.getProperty("os.name"); @@ -821,11 +742,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable } } - private static final Logger LOG = Log.getLogger(PathWatcher.class); - /** - * super noisy debug logging - */ - private static final Logger NOISY_LOG = Log.getLogger(PathWatcher.class.getName() + ".Noisy"); + static final Logger LOG = Log.getLogger(PathWatcher.class); @SuppressWarnings("unchecked") protected static WatchEvent cast(WatchEvent event) @@ -834,14 +751,18 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable } private static final WatchEvent.Kind WATCH_EVENT_KINDS[] = { ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY }; + private static final WatchEvent.Kind WATCH_DIR_KINDS[] = { ENTRY_CREATE, ENTRY_DELETE }; - private WatchService watchService; - private WatchEvent.Modifier watchModifiers[]; - private boolean nativeWatchService; - - private Map keys = new HashMap<>(); - private List listeners = new CopyOnWriteArrayList<>(); //a listener may modify the listener list directly or by stopping the PathWatcher - private List configs = new ArrayList<>(); + private WatchService watchService; + private WatchEvent.Modifier watchModifiers[]; + private boolean nativeWatchService; + + private final List configs = new ArrayList<>(); + private final Map keys = new HashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); //a listener may modify the listener list directly or by stopping the PathWatcher + + private final Map pending = new LinkedHashMap<>(32,(float)0.75,false); + private final List events = new ArrayList<>(); /** * Update Quiet Time - set to 1000 ms as default (a lower value in Windows is not supported) @@ -850,8 +771,6 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable private TimeUnit updateQuietTimeUnit = TimeUnit.MILLISECONDS; private Thread thread; private boolean _notifyExistingOnStart = true; - private Map pendingEvents = new LinkedHashMap<>(); - /** @@ -860,7 +779,12 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable public PathWatcher() { } - + + public Collection getConfigs() + { + return configs; + } + /** * Request watch on a the given path (either file or dir) * using all Config defaults. In the case of a dir, @@ -919,24 +843,6 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable configs.add(config); } - /** - * Register path in the config with the file watch service, - * walking the tree if it happens to be a directory. - * - * @param baseDir the base directory configuration to watch - * @throws IOException if unable to walk the filesystem tree - */ - protected void prepareConfig (final Config baseDir) throws IOException - { - if (LOG.isDebugEnabled()) - { - LOG.debug("Watching directory {}",baseDir); - } - Files.walkFileTree(baseDir.getPath(), new DepthLimitedFileVisitor(this, baseDir)); - } - - - /** * Add a listener for changes the watcher notices. * @@ -958,7 +864,7 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable for (Config config : keys.values()) { - dirs.add(config.dir); + dirs.add(config.path); } Collections.sort(dirs); @@ -994,12 +900,14 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable // Register all watched paths, walking dir hierarchies as needed, possibly generating // fake add events if notifyExistingOnStart is true for (Config c:configs) - prepareConfig(c); + registerTree(c.getPath(),c,isNotifyExistingOnStart()); // Start Thread for watcher take/pollKeys loop StringBuilder threadId = new StringBuilder(); - threadId.append("PathWatcher-Thread"); - appendConfigId(threadId); + threadId.append("PathWatcher@"); + threadId.append(Integer.toHexString(hashCode())); + if (LOG.isDebugEnabled()) + LOG.debug("{} -> {}", this, threadId); thread = new Thread(this,threadId.toString()); thread.setDaemon(true); @@ -1018,7 +926,8 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable watchService = null; thread = null; keys.clear(); - pendingEvents.clear(); + pending.clear(); + events.clear(); super.doStop(); } @@ -1062,9 +971,9 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable Class c = Class.forName("com.sun.nio.file.SensitivityWatchEventModifier"); Field f = c.getField("HIGH"); modifiers = new WatchEvent.Modifier[] - { - (WatchEvent.Modifier)f.get(c) - }; + { + (WatchEvent.Modifier)f.get(c) + }; } } catch (Throwable t) @@ -1109,72 +1018,93 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable return TimeUnit.MILLISECONDS.convert(updateQuietTimeDuration,updateQuietTimeUnit); } - /** - * Generate events to the listeners. - * - * @param events the events captured - */ - protected void notifyOnPathWatchEvents (List events) + private void registerTree(Path dir, Config config, boolean notify) throws IOException { - if (events == null || events.isEmpty()) - return; - - for (EventListener listener : listeners) + if (LOG.isDebugEnabled()) + LOG.debug("registerTree {} {} {}", dir, config, notify); + + if (!Files.isDirectory(dir)) + throw new IllegalArgumentException(dir.toString()); + + register(dir,config); + + final MultiException me = new MultiException(); + Files.list(dir).forEach(p-> { - if (listener instanceof EventListListener) - { - try - { - ((EventListListener)listener).onPathWatchEvents(events); - } - catch (Throwable t) - { - LOG.warn(t); - } - } - else - { - Listener l = (Listener)listener; - for (PathWatchEvent event:events) - { - try - { - l.onPathWatchEvent(event); - } - catch (Throwable t) - { - LOG.warn(t); - } - } - } - } + if (LOG.isDebugEnabled()) + LOG.debug("registerTree? {}",p); + try + { + if (notify && config.test(p)) + pending.put(p,new PathWatchEvent(p,PathWatchEventType.ADDED,config)); + + switch(config.handleDir(p)) + { + case ENTER: + registerTree(p,config.asSubConfig(p),notify); + break; + case WATCH: + registerDir(p,config); + break; + case IGNORE: + default: + break; + } + } + catch(IOException e) + { + me.add(e); + } + }); + + try + { + me.ifExceptionThrow(); + } + catch(IOException e) + { + throw e; + } + catch(Throwable th) + { + throw new IOException(th); + } } - /** - * Register a path (directory) with the WatchService. - * - * @param dir the directory to register - * @param root the configuration root - * @throws IOException if unable to register the path with the watch service. - */ - protected void register(Path dir, Config root) throws IOException + private void registerDir(Path path, Config config) throws IOException + { + if (LOG.isDebugEnabled()) + LOG.debug("registerDir {} {}", path, config); + + if (!Files.isDirectory(path)) + throw new IllegalArgumentException(path.toString()); + + register(path,config.asSubConfig(path),WATCH_DIR_KINDS); + } + + protected void register(Path path, Config config) throws IOException + { + if (LOG.isDebugEnabled()) + LOG.debug("Registering watch on {} {}",path,watchModifiers==null?null:Arrays.asList(watchModifiers)); + + register(path,config,WATCH_EVENT_KINDS); + } + + private void register(Path path, Config config, WatchEvent.Kind[] kinds) throws IOException { - - LOG.debug("Registering watch on {}",dir); if(watchModifiers != null) { // Java Watcher - WatchKey key = dir.register(watchService,WATCH_EVENT_KINDS,watchModifiers); - keys.put(key,root.asSubConfig(dir)); + WatchKey key = path.register(watchService,kinds,watchModifiers); + keys.put(key,config); } else { // Native Watcher - WatchKey key = dir.register(watchService,WATCH_EVENT_KINDS); - keys.put(key,root.asSubConfig(dir)); + WatchKey key = path.register(watchService,kinds); + keys.put(key,config); } } - /** * Delete a listener @@ -1207,59 +1137,55 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable @Override public void run() { - - List notifiableEvents = new ArrayList(); - // Start the java.nio watching if (LOG.isDebugEnabled()) { LOG.debug("Starting java.nio file watching with {}",watchService); } - while (watchService != null && thread == Thread.currentThread()) - { - WatchKey key = null; + long wait_time = getUpdateQuietTimeMillis(); + WatchService watch = watchService; + + while (isRunning() && thread == Thread.currentThread()) + { + + WatchKey key; + try { - //If no pending events, wait forever for new events - if (pendingEvents.isEmpty()) + // Reset all keys before watching + long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + for (Map.Entry e : keys.entrySet()) { - if (NOISY_LOG.isDebugEnabled()) - NOISY_LOG.debug("Waiting for take()"); - key = watchService.take(); - } - else - { - //There are existing events that might be ready to go, - //only wait as long as the quiet time for any new events - if (NOISY_LOG.isDebugEnabled()) - NOISY_LOG.debug("Waiting for poll({}, {})",updateQuietTimeDuration,updateQuietTimeUnit); - - key = watchService.poll(updateQuietTimeDuration,updateQuietTimeUnit); - - //If no new events its safe to process the pendings - if (key == null) + WatchKey k = e.getKey(); + Config c = e.getValue(); + + if (!c.isPaused(now) && !k.reset()) { - long now = System.currentTimeMillis(); - // no new event encountered. - for (Path path : new HashSet(pendingEvents.keySet())) + keys.remove(k); + if (keys.isEmpty()) { - PathPendingEvents pending = pendingEvents.get(path); - if (pending.isQuiet(now, updateQuietTimeDuration,updateQuietTimeUnit)) - { - //No fresh events received during quiet time for this path, - //so generate the events that were pent up - for (PathWatchEvent p:pending.getEvents()) - { - notifiableEvents.add(p); - } - // remove from pending list - pendingEvents.remove(path); - } + return; // all done, no longer monitoring anything } } } + + if (LOG.isDebugEnabled()) + LOG.debug("Waiting for poll({})", wait_time); + key = wait_time<0?watch.take():wait_time>0?watch.poll(wait_time,updateQuietTimeUnit):watch.poll(); + + // handle all active keys + while (key!=null) + { + handleKey(key); + key = watch.poll(); + } + + wait_time = processPending(); + + notifyEvents(); + } catch (ClosedWatchServiceException e) { @@ -1276,74 +1202,69 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable { LOG.ignore(e); } - return; - } - - //If there was some new events to process - if (key != null) - { - - Config config = keys.get(key); - if (config == null) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("WatchKey not recognized: {}",key); - } - continue; - } - - for (WatchEvent event : key.pollEvents()) - { - @SuppressWarnings("unchecked") - WatchEvent.Kind kind = (Kind)event.kind(); - WatchEvent ev = cast(event); - Path name = ev.context(); - Path child = config.dir.resolve(name); - - if (kind == ENTRY_CREATE) - { - // handle special case for registering new directories - // recursively - if (Files.isDirectory(child,LinkOption.NOFOLLOW_LINKS)) - { - try - { - prepareConfig(config.asSubConfig(child)); - } - catch (IOException e) - { - LOG.warn(e); - } - } - else if (config.matches(child)) - { - addToPendingList(child, new PathWatchEvent(child,ev)); - } - } - else if (config.matches(child)) - { - addToPendingList(child, new PathWatchEvent(child,ev)); - } - } - } - - //Send any notifications generated this pass - notifyOnPathWatchEvents(notifiableEvents); - notifiableEvents.clear(); - - if (key != null && !key.reset()) - { - keys.remove(key); - if (keys.isEmpty()) - { - return; // all done, no longer monitoring anything - } } } } - + private void handleKey(WatchKey key) + { + Config config = keys.get(key); + if (config == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("WatchKey not recognized: {}",key); + return; + } + + for (WatchEvent event : key.pollEvents()) + { + WatchEvent ev = cast(event); + Path name = ev.context(); + Path path = config.resolve(name); + + if (LOG.isDebugEnabled()) + LOG.debug("handleKey? {} {} {}", ev.kind(), config.toShortPath(path), config); + + // Ignore modified events on directories. These are handled as create/delete events of their contents + if (ev.kind()==ENTRY_MODIFY && Files.exists(path) && Files.isDirectory(path)) + continue; + + if (config.test(path)) + handleWatchEvent(path, new PathWatchEvent(path,ev,config)); + else if (config.getRecurseDepth()==-1) + { + // Convert a watched directory into a modify event on its parent + Path parent = path.getParent(); + Config parentConfig = config.getParent(); + handleWatchEvent(parent, new PathWatchEvent(parent,PathWatchEventType.MODIFIED,parentConfig)); + continue; + } + + if (ev.kind() == ENTRY_CREATE) + { + try + { + switch(config.handleDir(path)) + { + case ENTER: + registerTree(path,config.asSubConfig(path),true); + break; + case WATCH: + registerDir(path,config); + break; + case IGNORE: + default: + break; + } + } + catch(IOException e) + { + LOG.warn(e); + } + } + } + } + /** * Add an event reported by the WatchService to list of pending events * that will be sent after their quiet time has expired. @@ -1351,23 +1272,130 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable * @param path the path to add to the pending list * @param event the pending event */ - public void addToPendingList (Path path, PathWatchEvent event) + public void handleWatchEvent (Path path, PathWatchEvent event) { - PathPendingEvents pending = pendingEvents.get(path); + PathWatchEvent existing = pending.get(path); + + if (LOG.isDebugEnabled()) + LOG.debug("handleWatchEvent {} {} <= {}", path, event, existing); - //Are there already pending events for this path? - if (pending == null) + switch(event.getType()) { - //No existing pending events, create pending list - pendingEvents.put(path,new PathPendingEvents(path, event)); - } - else - { - //There are already some events pending for this path - pending.addEvent(event); + case ADDED: + if (existing!=null && existing.getType()==PathWatchEventType.MODIFIED) + events.add(new PathWatchEvent(path,PathWatchEventType.DELETED,existing.getConfig())); + pending.put(path,event); + break; + + case MODIFIED: + if (existing==null) + pending.put(path,event); + else + existing.modified(); + break; + + case DELETED: + case UNKNOWN: + if (existing!=null) + pending.remove(path); + events.add(event); + break; + } } - + + private long processPending() + { + if (LOG.isDebugEnabled()) + LOG.debug("processPending> {}",pending.values()); + + long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + long wait = Long.MAX_VALUE; + + // pending map is maintained in LRU order + for (PathWatchEvent event : new ArrayList<>(pending.values())) + { + Path path = event.getPath(); + // for directories, wait until parent is quiet + if (pending.containsKey(path.getParent())) + continue; + + // if the path is quiet move to events + if (event.isQuiet(now,getUpdateQuietTimeMillis())) + { + if (LOG.isDebugEnabled()) + LOG.debug("isQuiet {}",event); + pending.remove(path); + events.add(event); + } + else + { + long ms_to_check = event.toQuietCheck(now,getUpdateQuietTimeMillis()); + if (LOG.isDebugEnabled()) + LOG.debug("pending {} {}",event, ms_to_check); + if (ms_to_check implements Predicate { @Override @@ -1436,4 +1479,5 @@ public class PathWatcher extends AbstractLifeCycle implements Runnable return false; } } + } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index 6c183a5d36d..43feec4bc9b 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -1056,5 +1056,4 @@ public class StringUtil { return object==null?null:String.valueOf(object); } - } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java index 87dfc9a910c..ccbbd175876 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java @@ -22,10 +22,10 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** - * Convenience Lock Wrapper. + *

      Convenience auto closeable {@link java.util.concurrent.locks.ReentrantLock} wrapper.

      * *
      - * try(Locker.Lock lock = locker.lock())
      + * try (Locker.Lock lock = locker.lock())
        * {
        *   // something 
        * }
      @@ -33,44 +33,49 @@ import java.util.concurrent.locks.ReentrantLock;
        */
       public class Locker
       {
      -    private static final Lock LOCKED = new Lock();
           private final ReentrantLock _lock = new ReentrantLock();
      -    private final Lock _unlock = new UnLock();
      -
      -    public Locker()
      -    {
      -    }
      +    private final Lock _unlock = new Lock();
       
      +    /**
      +     * 

      Acquires the lock.

      + * + * @return the lock to unlock + */ public Lock lock() { - if (_lock.isHeldByCurrentThread()) - throw new IllegalStateException("Locker is not reentrant"); - _lock.lock(); - return _unlock; - } - - public Lock lockIfNotHeld () - { - if (_lock.isHeldByCurrentThread()) - return LOCKED; _lock.lock(); return _unlock; } + /** + * @deprecated use {@link #lock()} instead + */ + @Deprecated + public Lock lockIfNotHeld() + { + return lock(); + } + + /** + * @return whether this lock has been acquired + */ public boolean isLocked() { return _lock.isLocked(); } - - public static class Lock implements AutoCloseable + + /** + * @return a {@link Condition} associated with this lock + */ + public Condition newCondition() { - @Override - public void close() - { - } + return _lock.newCondition(); } - - public class UnLock extends Lock + + /** + *

      The unlocker object that unlocks when it is closed.

      + */ + public class Lock implements AutoCloseable { @Override public void close() @@ -78,9 +83,9 @@ public class Locker _lock.unlock(); } } - - public Condition newCondition() + + @Deprecated + public class UnLock extends Lock { - return _lock.newCondition(); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java index 441b9c45f5c..6dd85adcd25 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java @@ -22,6 +22,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.Condition; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -32,17 +34,18 @@ import org.eclipse.jetty.util.log.Logger; * with a Thread immediately being assigned the Runnable task, or fail if no Thread is * available. Threads are preallocated up to the capacity from a wrapped {@link Executor}. */ +@ManagedObject("A pool for reserved threads") public class ReservedThreadExecutor extends AbstractLifeCycle implements Executor { private static final Logger LOG = Log.getLogger(ReservedThreadExecutor.class); - + private final Executor _executor; private final Locker _locker = new Locker(); private final ReservedThread[] _queue; private int _head; private int _size; private int _pending; - + public ReservedThreadExecutor(Executor executor) { this(executor,1); @@ -60,15 +63,15 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo if (capacity < 0) { - if (executor instanceof ThreadPool) + int cpus = Runtime.getRuntime().availableProcessors(); + if (executor instanceof ThreadPool.SizedThreadPool) { - int threads = ((ThreadPool)executor).getThreads(); - int cpus = Runtime.getRuntime().availableProcessors(); - capacity = Math.max(1,Math.min(cpus,threads/8)); + int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads(); + capacity = Math.max(1, Math.min(cpus, threads / 8)); } else { - capacity = Runtime.getRuntime().availableProcessors(); + capacity = cpus; } } @@ -79,34 +82,31 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo { return _executor; } - + + @ManagedAttribute(value = "max number of reserved threads", readonly = true) public int getCapacity() { return _queue.length; } - - public int getPreallocated() + + @ManagedAttribute(value = "available reserved threads", readonly = true) + public int getAvailable() { try (Locker.Lock lock = _locker.lock()) { return _size; } } - - @Override - public void doStart() throws Exception + + @ManagedAttribute(value = "pending reserved threads", readonly = true) + public int getPending() { try (Locker.Lock lock = _locker.lock()) { - _head = _size = _pending = 0; - while (_pending<_queue.length) - { - _executor.execute(new ReservedThread()); - _pending++; - } + return _pending; } } - + @Override public void doStop() throws Exception { @@ -121,15 +121,15 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo thread._wakeup.signal(); } } - } - + } + @Override public void execute(Runnable task) throws RejectedExecutionException { if (!tryExecute(task)) throw new RejectedExecutionException(); } - + /** * @param task The task to run * @return True iff a reserved thread was available and has been assigned the task to run. @@ -138,7 +138,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo { if (task==null) return false; - + try (Locker.Lock lock = _locker.lock()) { if (_size==0) @@ -150,21 +150,21 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo } return false; } - + ReservedThread thread = _queue[_head]; _queue[_head] = null; _head = (_head+1)%_queue.length; _size--; - + if (_size==0 && _pending<_queue.length) { _executor.execute(new ReservedThread()); _pending++; } - + thread._task = task; thread._wakeup.signal(); - + return true; } catch(RejectedExecutionException e) @@ -174,23 +174,31 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo } } + @Override + public String toString() + { + try (Locker.Lock lock = _locker.lock()) + { + return String.format("%s{s=%d,p=%d}",super.toString(),_size,_pending); + } + } + private class ReservedThread implements Runnable { private Condition _wakeup = null; private Runnable _task = null; - + private void reservedWait() throws InterruptedException { _wakeup.await(); } - + @Override public void run() { while (true) { Runnable task = null; - try (Locker.Lock lock = _locker.lock()) { // if this is our first loop, decrement pending count @@ -199,20 +207,24 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo _pending--; _wakeup = _locker.newCondition(); } - + // Exit if no longer running or there now too many preallocated threads if (!isRunning() || _size>=_queue.length) break; - + // Insert ourselves in the queue _queue[(_head+_size++)%_queue.length] = this; - // Wait for a task, ignoring spurious interrupts - do + // Wait for a task, ignoring spurious wakeups + while (isRunning() && task==null) { try { + if (LOG.isDebugEnabled()) + LOG.debug("{} waiting", this); reservedWait(); + if (LOG.isDebugEnabled()) + LOG.debug("{} woken up", this); task = _task; _task = null; } @@ -221,7 +233,6 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo LOG.ignore(e); } } - while (isRunning() && task==null); } // Run any task @@ -231,22 +242,12 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo { task.run(); } - catch (Exception e) + catch (Throwable e) { LOG.warn(e); - break; } } } } } - - @Override - public String toString() - { - try (Locker.Lock lock = _locker.lock()) - { - return String.format("%s{s=%d,p=%d}",super.toString(),_size,_pending); - } - } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java index 58c38c07bbf..079e9e2684d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java @@ -21,7 +21,11 @@ package org.eclipse.jetty.util.thread.strategy; import java.io.Closeable; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.LongAdder; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -57,6 +61,7 @@ import org.eclipse.jetty.util.thread.ReservedThreadExecutor; * sub-strategy is called ProduceExecuteConsume (PEC). *

      */ +@ManagedObject("eat what you kill execution strategy") public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrategy, Runnable { private static final Logger LOG = Log.getLogger(EatWhatYouKill.class); @@ -64,19 +69,22 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat private enum State { IDLE, PRODUCING, REPRODUCING } private final Locker _locker = new Locker(); - private State _state = State.IDLE; + private final LongAdder _nonBlocking = new LongAdder(); + private final LongAdder _blocking = new LongAdder(); + private final LongAdder _executed = new LongAdder(); private final Producer _producer; private final Executor _executor; private final ReservedThreadExecutor _producers; + private State _state = State.IDLE; public EatWhatYouKill(Producer producer, Executor executor) { this(producer,executor,new ReservedThreadExecutor(executor,1)); } - public EatWhatYouKill(Producer producer, Executor executor, int maxProducersPending) + public EatWhatYouKill(Producer producer, Executor executor, int maxReserved) { - this(producer,executor,new ReservedThreadExecutor(executor,maxProducersPending)); + this(producer,executor,new ReservedThreadExecutor(executor,maxReserved)); } public EatWhatYouKill(Producer producer, Executor executor, ReservedThreadExecutor producers) @@ -184,7 +192,9 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat { // Could another one just have been queued with a produce call? if (_state==State.REPRODUCING) + { _state = State.PRODUCING; + } else { if (LOG.isDebugEnabled()) @@ -194,36 +204,42 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat } } } - else if (Invocable.getInvocationType(task)==InvocationType.NON_BLOCKING) - { - // PRODUCE CONSUME (EWYK!) - if (LOG.isDebugEnabled()) - LOG.debug("{} PC t={}",this,task); - task.run(); - } else { boolean consume; - try (Lock locked = _locker.lock()) + if (Invocable.getInvocationType(task) == InvocationType.NON_BLOCKING) { - if (_producers.tryExecute(this)) + // PRODUCE CONSUME (EWYK!) + if (LOG.isDebugEnabled()) + LOG.debug("{} PC t={}", this, task); + consume = true; + _nonBlocking.increment(); + } + else + { + try (Lock locked = _locker.lock()) { - // EXECUTE PRODUCE CONSUME! - // We have executed a new Producer, so we can EWYK consume - _state = State.IDLE; - producing = false; - consume = true; - } - else - { - // PRODUCE EXECUTE CONSUME! - consume = false; + if (_producers.tryExecute(this)) + { + // EXECUTE PRODUCE CONSUME! + // We have executed a new Producer, so we can EWYK consume + _state = State.IDLE; + producing = false; + consume = true; + _blocking.increment(); + } + else + { + // PRODUCE EXECUTE CONSUME! + consume = false; + _executed.increment(); + } } + + if (LOG.isDebugEnabled()) + LOG.debug("{} {} t={}", this, consume ? "EPC" : "PEC", task); } - if (LOG.isDebugEnabled()) - LOG.debug("{} {} t={}",this,consume?"EPC":"PEC",task); - // Consume or execute task try { @@ -232,7 +248,7 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat else _executor.execute(task); } - catch(RejectedExecutionException e) + catch (RejectedExecutionException e) { LOG.warn(e); if (task instanceof Closeable) @@ -241,13 +257,13 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat { ((Closeable)task).close(); } - catch(Throwable e2) + catch (Throwable e2) { LOG.ignore(e2); } } } - catch(Throwable e) + catch (Throwable e) { LOG.warn(e); } @@ -257,7 +273,26 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat return producing; } - public Boolean isIdle() + @ManagedAttribute(value = "number of non blocking tasks consumed", readonly = true) + public long getNonBlockingTasksConsumed() + { + return _nonBlocking.longValue(); + } + + @ManagedAttribute(value = "number of blocking tasks consumed", readonly = true) + public long getBlockingTasksConsumed() + { + return _blocking.longValue(); + } + + @ManagedAttribute(value = "number of blocking tasks executed", readonly = true) + public long getBlockingTasksExecuted() + { + return _executed.longValue(); + } + + @ManagedAttribute(value = "whether this execution strategy is idle", readonly = true) + public boolean isIdle() { try (Lock locked = _locker.lock()) { @@ -265,6 +300,14 @@ public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrat } } + @ManagedOperation(value = "resets the task counts", impact = "ACTION") + public void reset() + { + _nonBlocking.reset(); + _blocking.reset(); + _executed.reset(); + } + public String toString() { try (Lock locked = _locker.lock()) diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java new file mode 100644 index 00000000000..01031f6296a --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java @@ -0,0 +1,92 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.io.File; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jetty.toolchain.test.AdvancedRunner; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +@RunWith(AdvancedRunner.class) +public class MultiReleaseJarFileTest +{ + private File testResources = MavenTestingUtils.getTestResourcesDir().getAbsoluteFile(); + private File example = new File(testResources,"example.jar"); + + @Test + public void testExampleJarIsMR() throws Exception + { + MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example); + assertTrue(jarFile.isMultiRelease()); + } + + @Test + public void testBase() throws Exception + { + MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,8,false); + assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/InBoth$InnerBase.class").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/InBoth$InnerBoth.class").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(0)); + + assertThat(jarFile.stream().count(), is(5L)); + } + + @Test + public void test9() throws Exception + { + MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,9,false); + assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/InBoth$InnerBoth.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/OnlyIn9.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/onlyIn9/OnlyIn9.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/InBoth$Inner9.class").getVersion(), is(9)); + + assertThat(jarFile.stream().count(), is(7L)); + } + + @Test + public void test10() throws Exception + { + MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,10,false); + assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0)); + assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(10)); + assertThat(jarFile.getEntry("org/example/OnlyIn9.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/onlyIn9/OnlyIn9.class").getVersion(), is(9)); + assertThat(jarFile.getEntry("org/example/In10Only.class").getVersion(), is(10)); + + assertThat(jarFile.stream().count(), is(6L)); + } + + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java index 055192c8ae9..7691941cc05 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java @@ -18,9 +18,14 @@ package org.eclipse.jetty.util; -import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.*; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.ADDED; +import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.DELETED; +import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.MODIFIED; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; @@ -28,6 +33,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.DosFileAttributes; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -36,19 +44,37 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.util.PathWatcher.PathWatchEvent; import org.eclipse.jetty.util.PathWatcher.PathWatchEventType; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.junit.Ignore; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; -@Ignore("Disabled due to behavioral differences in various FileSystems (hard to write a single testcase that works in all scenarios)") +@RunWith(AdvancedRunner.class) public class PathWatcherTest { + public static final int QUIET_TIME; + public static final int WAIT_TIME; + public static final int LONG_TIME; + + static + { + if (OS.IS_LINUX) + QUIET_TIME = 300; + else if (OS.IS_OSX) + QUIET_TIME = 5000; + else + QUIET_TIME = 1000; + WAIT_TIME = 2 * QUIET_TIME; + LONG_TIME = 5 * QUIET_TIME; + } + public static class PathWatchEventCapture implements PathWatcher.Listener { public final static String FINISH_TAG = "#finished#.tag"; @@ -76,27 +102,17 @@ public class PathWatcherTest events.clear(); } + public void reset(int count) + { + setFinishTrigger(count); + events.clear(); + } + @Override public void onPathWatchEvent(PathWatchEvent event) { synchronized (events) { - //if triggered by path - if (triggerPath != null) - { - - if (triggerPath.equals(event.getPath()) && (event.getType() == triggerType)) - { - LOG.debug("Encountered finish trigger: {} on {}",event.getType(),event.getPath()); - finishedLatch.countDown(); - } - } - else if (finishedLatch != null) - { - finishedLatch.countDown(); - } - - Path relativePath = this.baseDir.relativize(event.getPath()); String key = relativePath.toString().replace(File.separatorChar,'/'); @@ -109,6 +125,20 @@ public class PathWatcherTest this.events.put(key,types); LOG.debug("Captured Event: {} | {}",event.getType(),key); } + //if triggered by path + if (triggerPath != null) + { + + if (triggerPath.equals(event.getPath()) && (event.getType() == triggerType)) + { + LOG.debug("Encountered finish trigger: {} on {}",event.getType(),event.getPath()); + finishedLatch.countDown(); + } + } + else if (finishedLatch != null) + { + finishedLatch.countDown(); + } } /** @@ -122,13 +152,21 @@ public class PathWatcherTest */ public void assertEvents(Map expectedEvents) { - assertThat("Event match (file|diretory) count",this.events.size(),is(expectedEvents.size())); - - for (Map.Entry entry : expectedEvents.entrySet()) + try { - String relativePath = entry.getKey(); - PathWatchEventType[] expectedTypes = entry.getValue(); - assertEvents(relativePath,expectedTypes); + assertThat("Event match (file|directory) count", this.events.size(), is(expectedEvents.size())); + + for (Map.Entry entry : expectedEvents.entrySet()) + { + String relativePath = entry.getKey(); + PathWatchEventType[] expectedTypes = entry.getValue(); + assertEvents(relativePath, expectedTypes); + } + } + catch(Throwable th) + { + System.err.println(this.events); + throw th; } } @@ -175,7 +213,7 @@ public class PathWatcherTest latchCount = count; finishedLatch = new CountDownLatch(latchCount); } - + /** * Await the countdown latch on the finish trigger * @@ -197,6 +235,12 @@ public class PathWatcherTest assertThat("Timed Out (" + awaitMillis + "ms) waiting for capture to finish",finishedLatch.await(awaitMillis,TimeUnit.MILLISECONDS),is(true)); LOG.debug("Finished capture"); } + + @Override + public String toString() + { + return events.toString(); + } } private static void updateFile(Path path, String newContents) throws IOException @@ -228,39 +272,37 @@ public class PathWatcherTest * @throws InterruptedException * if sleep between writes was interrupted */ - private void updateFileOverTime(Path path, int fileSize, int timeDuration, TimeUnit timeUnit) throws IOException, InterruptedException + private void updateFileOverTime(Path path, int timeDuration, TimeUnit timeUnit) { - // how long to sleep between writes - int sleepMs = 100; - - // how many millis to spend writing entire file size - long totalMs = timeUnit.toMillis(timeDuration); - - // how many write chunks to write - int writeCount = (int)((int)totalMs / (int)sleepMs); - - // average chunk buffer - int chunkBufLen = fileSize / writeCount; - byte chunkBuf[] = new byte[chunkBufLen]; - Arrays.fill(chunkBuf,(byte)'x'); - - try (FileOutputStream out = new FileOutputStream(path.toFile())) + try { - int left = fileSize; + // how long to sleep between writes + int sleepMs = 200; - while (left > 0) + // average chunk buffer + int chunkBufLen = 16; + byte chunkBuf[] = new byte[chunkBufLen]; + Arrays.fill(chunkBuf, (byte)'x'); + long end = System.nanoTime() + timeUnit.toNanos(timeDuration); + + try (FileOutputStream out = new FileOutputStream(path.toFile())) { - int len = Math.min(left,chunkBufLen); - out.write(chunkBuf,0,len); - left -= chunkBufLen; - out.flush(); - out.getChannel().force(true); - // Force file to actually write to disk. - // Skipping any sort of filesystem caching of the write - out.getFD().sync(); - TimeUnit.MILLISECONDS.sleep(sleepMs); + while(System.nanoTime() expected = new HashMap<>(); - // Create a few directories - Files.createDirectories(dir.resolve("a/b/c/d")); + // Check initial scan events + capture.setFinishTrigger(4); + pathWatcher.start(); + expected.put("file0",new PathWatchEventType[] { ADDED }); + expected.put("subdir0",new PathWatchEventType[] { ADDED }); + expected.put("subdir0/fileA",new PathWatchEventType[] { ADDED }); + expected.put("subdir0/subsubdir0",new PathWatchEventType[] { ADDED }); - PathWatcher.Config config = new PathWatcher.Config(dir); + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); + capture.assertEvents(expected); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); - config.setRecurseDepth(2); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c]",config.shouldRecurseDirectory(dir.resolve("a/b/c")),is(false)); - assertThat("Config.recurse[1].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(true)); - } - - - @Test - public void testConfig_ShouldRecurse_3() throws IOException - { - Path dir = testdir.getEmptyPathDir(); - - //Create some deep dirs - Files.createDirectories(dir.resolve("a/b/c/d/e/f/g")); - - PathWatcher.Config config = new PathWatcher.Config(dir); - config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/g]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/g")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/f]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/f")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/e]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/e")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a/b/c]",config.shouldRecurseDirectory(dir.resolve("a/b/c")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(true)); - assertThat("Config.recurse[1].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(true)); + // Check adding files + capture.reset(3); + expected.clear(); + Files.createFile(dir.resolve("subdir0/subsubdir0/toodeep")); + expected.put("subdir0/subsubdir0",new PathWatchEventType[] { MODIFIED }); + Files.createFile(dir.resolve("file1")); + expected.put("file1",new PathWatchEventType[] { ADDED }); + Files.createFile(dir.resolve("subdir0/fileB")); + expected.put("subdir0/fileB",new PathWatchEventType[] { ADDED }); + + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); + capture.assertEvents(expected); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); + + // Check slow modification + capture.reset(1); + expected.clear(); + long start = System.nanoTime(); + new Thread(()->{updateFileOverTime(dir.resolve("file1"),2*QUIET_TIME,TimeUnit.MILLISECONDS);}).start(); + expected.put("file1",new PathWatchEventType[] { MODIFIED }); + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); + long end = System.nanoTime(); + capture.assertEvents(expected); + assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME))); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); + + // Check slow add + capture.reset(1); + expected.clear(); + start = System.nanoTime(); + new Thread(()->{updateFileOverTime(dir.resolve("file2"),2*QUIET_TIME,TimeUnit.MILLISECONDS);}).start(); + expected.put("file2",new PathWatchEventType[] { ADDED }); + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); + end = System.nanoTime(); + capture.assertEvents(expected); + assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME))); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); + + // Check move directory + if (OS.IS_LINUX) + { + capture.reset(5); + expected.clear(); + Files.move(dir.resolve("subdir0"), dir.resolve("subdir1"), StandardCopyOption.ATOMIC_MOVE); + expected.put("subdir0", new PathWatchEventType[]{DELETED}); + // TODO expected.put("subdir0/fileA",new PathWatchEventType[] { DELETED }); + // TODO expected.put("subdir0/subsubdir0",new PathWatchEventType[] { DELETED }); + expected.put("subdir1", new PathWatchEventType[]{ADDED}); + expected.put("subdir1/fileA", new PathWatchEventType[]{ADDED}); + expected.put("subdir1/fileB", new PathWatchEventType[]{ADDED}); + expected.put("subdir1/subsubdir0", new PathWatchEventType[]{ADDED}); + + capture.finishedLatch.await(LONG_TIME, TimeUnit.MILLISECONDS); + capture.assertEvents(expected); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); + } + + // Check delete file + capture.reset(2); + expected.clear(); + Files.delete(dir.resolve("file1")); + expected.put("file1",new PathWatchEventType[] { DELETED }); + Files.delete(dir.resolve("file2")); + expected.put("file2",new PathWatchEventType[] { DELETED }); + + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); + capture.assertEvents(expected); + Thread.sleep(WAIT_TIME); + capture.assertEvents(expected); + + } + finally + { + pathWatcher.stop(); + } } @Test @@ -371,7 +456,7 @@ public class PathWatcherTest PathWatcher pathWatcher = new PathWatcher(); pathWatcher.setNotifyExistingOnStart(true); - pathWatcher.setUpdateQuietTime(500,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); @@ -440,13 +525,17 @@ public class PathWatcherTest // Files we don't care about Files.createFile(dir.resolve("foo.war.backup")); - Files.createFile(dir.resolve(".hidden.war")); + + String hidden_war = OS.IS_WINDOWS ? "hidden.war" : ".hidden.war"; + Files.createFile(dir.resolve(hidden_war)); + if (OS.IS_WINDOWS) + Files.setAttribute(dir.resolve(hidden_war),"dos:hidden",Boolean.TRUE); Files.createDirectories(dir.resolve(".wat/WEB-INF")); Files.createFile(dir.resolve(".wat/huh.war")); Files.createFile(dir.resolve(".wat/WEB-INF/web.xml")); PathWatcher pathWatcher = new PathWatcher(); - pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); @@ -462,17 +551,19 @@ public class PathWatcherTest try { + capture.setFinishTrigger(2); pathWatcher.start(); // Let quiet time do its thing - awaitQuietTime(pathWatcher); + capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS); Map expected = new HashMap<>(); - expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED }); expected.put("foo.war",new PathWatchEventType[] { ADDED }); capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); } finally { @@ -498,11 +589,10 @@ public class PathWatcherTest PathWatcher pathWatcher = new PathWatcher(); - pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); - capture.setFinishTrigger(3); pathWatcher.addListener(capture); // Add test dir configuration @@ -514,10 +604,9 @@ public class PathWatcherTest try { + capture.setFinishTrigger(3); pathWatcher.start(); - - // Let quiet time do its thing - awaitQuietTime(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); Map expected = new HashMap<>(); @@ -525,6 +614,8 @@ public class PathWatcherTest expected.put("b/b.txt",new PathWatchEventType[] { ADDED }); expected.put("c/d/d.txt",new PathWatchEventType[] { ADDED }); capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); } finally { @@ -543,16 +634,15 @@ public class PathWatcherTest Files.createFile(dir.resolve("bar/WEB-INF/web.xml")); PathWatcher pathWatcher = new PathWatcher(); - pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); - capture.setFinishTrigger(5); pathWatcher.addListener(capture); // Add test dir configuration PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir); - baseDirConfig.setRecurseDepth(2); + baseDirConfig.setRecurseDepth(100); baseDirConfig.addExcludeHidden(); baseDirConfig.addIncludeGlobRelative("*.war"); baseDirConfig.addIncludeGlobRelative("*/WEB-INF/web.xml"); @@ -560,11 +650,13 @@ public class PathWatcherTest try { + capture.setFinishTrigger(2); pathWatcher.start(); - // Pretend that startup occurred - awaitQuietTime(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + capture.setFinishTrigger(3); + // Update web.xml Path webFile = dir.resolve("bar/WEB-INF/web.xml"); //capture.setFinishTrigger(webFile,MODIFIED); @@ -577,7 +669,7 @@ public class PathWatcherTest Files.createFile(dir.resolve("bar.war")); // Let capture complete - capture.awaitFinish(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); Map expected = new HashMap<>(); @@ -586,6 +678,8 @@ public class PathWatcherTest expected.put("bar.war",new PathWatchEventType[] { ADDED }); capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); } finally { @@ -604,7 +698,7 @@ public class PathWatcherTest Files.createFile(dir.resolve("bar/WEB-INF/web.xml")); PathWatcher pathWatcher = new PathWatcher(); - pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); @@ -620,26 +714,36 @@ public class PathWatcherTest try { + capture.setFinishTrigger(2); pathWatcher.start(); // Pretend that startup occurred - awaitQuietTime(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); // New war added + capture.setFinishTrigger(1); Path warFile = dir.resolve("hello.war"); - capture.setFinishTrigger(warFile,MODIFIED); - updateFile(warFile,"Hello Update"); + updateFile(warFile,"Create Hello"); + Thread.sleep(QUIET_TIME/2); + updateFile(warFile,"Hello 1"); + Thread.sleep(QUIET_TIME/2); + updateFile(warFile,"Hello two"); + Thread.sleep(QUIET_TIME/2); + updateFile(warFile,"Hello three"); // Let capture finish - capture.awaitFinish(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); Map expected = new HashMap<>(); expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED }); expected.put("foo.war",new PathWatchEventType[] { ADDED }); - expected.put("hello.war",new PathWatchEventType[] { ADDED, MODIFIED }); + expected.put("hello.war",new PathWatchEventType[] { ADDED }); capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + Assume.assumeFalse(OS.IS_OSX); // TODO fix this + capture.assertEvents(expected); } finally { @@ -647,8 +751,195 @@ public class PathWatcherTest } } + @Test + public void testDeployFiles_NewDir() throws Exception + { + Path dir = testdir.getEmptyPathDir(); + + // Files we are interested in + Files.createFile(dir.resolve("foo.war")); + + PathWatcher pathWatcher = new PathWatcher(); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); + + // Add listener + PathWatchEventCapture capture = new PathWatchEventCapture(dir); + pathWatcher.addListener(capture); + + // Add test dir configuration + PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir); + baseDirConfig.setRecurseDepth(2); + baseDirConfig.addExcludeHidden(); + baseDirConfig.addIncludeGlobRelative("*.war"); + baseDirConfig.addIncludeGlobRelative("*/WEB-INF/web.xml"); + pathWatcher.watch(baseDirConfig); + + try + { + capture.setFinishTrigger(1); + pathWatcher.start(); + + // Pretend that startup occurred + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + + // New war added + capture.setFinishTrigger(1); + + Files.createDirectories(dir.resolve("bar/WEB-INF")); + Thread.sleep(QUIET_TIME/2); + Files.createFile(dir.resolve("bar/WEB-INF/web.xml")); + Thread.sleep(QUIET_TIME/2); + updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update"); + Thread.sleep(QUIET_TIME/2); + updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update web.xml"); + + // Let capture finish + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + + Map expected = new HashMap<>(); + + expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED }); + expected.put("foo.war",new PathWatchEventType[] { ADDED }); + + capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); + } + finally + { + pathWatcher.stop(); + } + } + + + @Test + public void testDeployFilesBeyondDepthLimit() throws Exception + { + Path dir = testdir.getEmptyPathDir(); + + // Files we are interested in + Files.createDirectories(dir.resolve("foo/WEB-INF/lib")); + Files.createDirectories(dir.resolve("bar/WEB-INF/lib")); + + PathWatcher pathWatcher = new PathWatcher(); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); + + // Add listener + PathWatchEventCapture capture = new PathWatchEventCapture(dir); + pathWatcher.addListener(capture); + + // Add test dir configuration + PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir); + baseDirConfig.setRecurseDepth(0); + pathWatcher.watch(baseDirConfig); + + try + { + capture.setFinishTrigger(2); + pathWatcher.start(); + + // Pretend that startup occurred + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + + Map expected = new HashMap<>(); + expected.put("foo",new PathWatchEventType[] { ADDED }); + expected.put("bar",new PathWatchEventType[] { ADDED }); + + capture.assertEvents(expected); + + capture.reset(1); + expected.clear(); + expected.put("bar",new PathWatchEventType[] { MODIFIED }); + Files.createFile(dir.resolve("bar/index.html")); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + + capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); + + capture.reset(1); + expected.clear(); + expected.put("bob",new PathWatchEventType[] { ADDED }); + Files.createFile(dir.resolve("bar/WEB-INF/lib/ignored")); + PathWatcher.LOG.debug("create bob"); + Files.createDirectories(dir.resolve("bob/WEB-INF/lib")); + Thread.sleep(QUIET_TIME/2); + PathWatcher.LOG.debug("create bob/index.html"); + Files.createFile(dir.resolve("bob/index.html")); + Thread.sleep(QUIET_TIME/2); + PathWatcher.LOG.debug("update bob/index.html"); + updateFile(dir.resolve("bob/index.html"),"Update"); + Thread.sleep(QUIET_TIME/2); + PathWatcher.LOG.debug("update bob/index.html"); + updateFile(dir.resolve("bob/index.html"),"Update index.html"); + + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); + + } + finally + { + pathWatcher.stop(); + } + } + + + @Test + public void testWatchFile() throws Exception + { + Path dir = testdir.getEmptyPathDir(); + + // Files we are interested in + Files.createDirectories(dir.resolve("bar/WEB-INF")); + Files.createFile(dir.resolve("bar/WEB-INF/web.xml")); + + PathWatcher pathWatcher = new PathWatcher(); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); + + // Add listener + PathWatchEventCapture capture = new PathWatchEventCapture(dir); + pathWatcher.addListener(capture); + + // Add test configuration + pathWatcher.watch(dir.resolve("bar/WEB-INF/web.xml")); + pathWatcher.setNotifyExistingOnStart(false); + + try + { + pathWatcher.start(); + Thread.sleep(WAIT_TIME); + assertThat(capture.events.size(),is(0)); + + Files.createFile(dir.resolve("bar/index.htnl")); + Files.createFile(dir.resolve("bar/WEB-INF/other.xml")); + Files.createDirectories(dir.resolve("bar/WEB-INF/lib")); + + Thread.sleep(WAIT_TIME); + assertThat(capture.events.size(),is(0)); + + capture.setFinishTrigger(1); + updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update web.xml"); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + + Map expected = new HashMap<>(); + + expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { MODIFIED }); + + capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); + } + finally + { + pathWatcher.stop(); + } + } + + /** - * Pretend to add a new war file that is large, and being copied into place + * Pretend to modify a new war file that is large, and being copied into place * using some sort of technique that is slow enough that it takes a while for * the entire war file to exist in place. *

      @@ -658,17 +949,18 @@ public class PathWatcherTest * on test failure */ @Test - public void testDeployFiles_NewWar_LargeSlowCopy() throws Exception + public void testDeployFiles_ModifyWar_LargeSlowCopy() throws Exception { Path dir = testdir.getEmptyPathDir(); // Files we are interested in Files.createFile(dir.resolve("foo.war")); + Files.createFile(dir.resolve("hello.war")); Files.createDirectories(dir.resolve("bar/WEB-INF")); Files.createFile(dir.resolve("bar/WEB-INF/web.xml")); PathWatcher pathWatcher = new PathWatcher(); - pathWatcher.setUpdateQuietTime(500,TimeUnit.MILLISECONDS); + pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS); // Add listener PathWatchEventCapture capture = new PathWatchEventCapture(dir); @@ -684,26 +976,35 @@ public class PathWatcherTest try { + capture.setFinishTrigger(3); pathWatcher.start(); // Pretend that startup occurred - awaitQuietTime(pathWatcher); + assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS)); + // New war added (slowly) + capture.setFinishTrigger(1); Path warFile = dir.resolve("hello.war"); - capture.setFinishTrigger(warFile,MODIFIED); - updateFileOverTime(warFile,50 * MB,3,TimeUnit.SECONDS); - - // Let capture finish - capture.awaitFinish(pathWatcher); + long start = System.nanoTime(); + new Thread(()-> + { + updateFileOverTime(warFile,2*QUIET_TIME,TimeUnit.MILLISECONDS); + }).start(); + + assertTrue(capture.finishedLatch.await(4*QUIET_TIME,TimeUnit.MILLISECONDS)); + long end = System.nanoTime(); + assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME))); + Map expected = new HashMap<>(); - expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED }); expected.put("foo.war",new PathWatchEventType[] { ADDED }); expected.put("hello.war",new PathWatchEventType[] { ADDED, MODIFIED }); capture.assertEvents(expected); + TimeUnit.MILLISECONDS.sleep(WAIT_TIME); + capture.assertEvents(expected); } finally { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index 599f46a62c8..e81bdae9a35 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -32,13 +32,11 @@ import java.security.KeyStore; import javax.net.ssl.SSLEngine; -import org.eclipse.jetty.toolchain.test.JDK; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.resource.Resource; import org.hamcrest.Matchers; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -225,8 +223,7 @@ public class SslContextFactoryTest public void testSetIncludeCipherSuitesRegex() throws Exception { cf.setIncludeCipherSuites(".*ECDHE.*",".*WIBBLE.*"); - Assume.assumeFalse(JDK.IS_8); - + cf.start(); SSLEngine sslEngine = cf.newSSLEngine(); String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites(); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java index f6209683a59..3a3f73325dd 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java @@ -25,6 +25,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -33,163 +34,138 @@ import static org.junit.Assert.assertThat; public class ReservedThreadExecutorTest { - final static int SIZE = 2; - TestExecutor _executor; - ReservedThreadExecutor _pae; - + private static final int SIZE = 2; + private static final Runnable NOOP = () -> {}; + + private TestExecutor _executor; + private ReservedThreadExecutor _reservedExecutor; + @Before public void before() throws Exception { _executor = new TestExecutor(); - _pae = new ReservedThreadExecutor(_executor,SIZE); - _pae.start(); + _reservedExecutor = new ReservedThreadExecutor(_executor, SIZE); + _reservedExecutor.start(); } - - + @After public void after() throws Exception { - _pae.stop(); + _reservedExecutor.stop(); } @Test public void testStarted() throws Exception { - assertThat(_executor._queue.size(),is(SIZE)); - while(!_executor._queue.isEmpty()) - _executor.execute(); - - assertThat(_pae.getCapacity(),is(SIZE)); - - long started = System.nanoTime(); - while (_pae.getPreallocated()10) - break; - Thread.sleep(100); - } - assertThat(_pae.getPreallocated(),is(SIZE)); + // Reserved threads are lazily started. + assertThat(_executor._queue.size(), is(0)); } @Test public void testPending() throws Exception { - assertThat(_executor._queue.size(),is(SIZE)); - assertThat(_pae.tryExecute(new NOOP()),is(false)); - assertThat(_executor._queue.size(),is(SIZE)); - - _executor.execute(); - assertThat(_executor._queue.size(),is(SIZE-1)); - while (!_executor._queue.isEmpty()) + assertThat(_executor._queue.size(), is(0)); + + for (int i = 0; i < SIZE; i++) + _reservedExecutor.tryExecute(NOOP); + assertThat(_executor._queue.size(), is(SIZE)); + + for (int i = 0; i < SIZE; i++) _executor.execute(); + assertThat(_executor._queue.size(), is(0)); - long started = System.nanoTime(); - while (_pae.getPreallocated()10) - break; - Thread.sleep(100); - } - assertThat(_executor._queue.size(),is(0)); - assertThat(_pae.getPreallocated(),is(SIZE)); - - for (int i=SIZE;i-->0;) - assertThat(_pae.tryExecute(new Task()),is(true)); - assertThat(_executor._queue.size(),is(1)); - assertThat(_pae.getPreallocated(),is(0)); + waitForAllAvailable(); - for (int i=SIZE;i-->0;) - assertThat(_pae.tryExecute(new NOOP()),is(false)); - assertThat(_executor._queue.size(),is(SIZE)); - assertThat(_pae.getPreallocated(),is(0)); - - assertThat(_pae.tryExecute(new NOOP()),is(false)); - assertThat(_executor._queue.size(),is(SIZE)); - assertThat(_pae.getPreallocated(),is(0)); + for (int i = 0; i < SIZE; i++) + assertThat(_reservedExecutor.tryExecute(new Task()), is(true)); + assertThat(_executor._queue.size(), is(1)); + assertThat(_reservedExecutor.getAvailable(), is(0)); + + for (int i = 0; i < SIZE; i++) + assertThat(_reservedExecutor.tryExecute(NOOP), is(false)); + assertThat(_executor._queue.size(), is(SIZE)); + assertThat(_reservedExecutor.getAvailable(), is(0)); } @Test public void testExecuted() throws Exception { - while(!_executor._queue.isEmpty()) + assertThat(_executor._queue.size(), is(0)); + + for (int i = 0; i < SIZE; i++) + _reservedExecutor.tryExecute(NOOP); + assertThat(_executor._queue.size(), is(SIZE)); + + for (int i = 0; i < SIZE; i++) _executor.execute(); - long started = System.nanoTime(); - while (_pae.getPreallocated()10) - break; - Thread.sleep(100); - } - assertThat(_pae.getPreallocated(),is(SIZE)); - - Task[] task = new Task[SIZE]; - for (int i=SIZE;i-->0;) - { - task[i] = new Task(); - assertThat(_pae.tryExecute(task[i]),is(true)); + tasks[i] = new Task(); + assertThat(_reservedExecutor.tryExecute(tasks[i]), is(true)); } - for (int i=SIZE;i-->0;) - { - task[i]._ran.await(10,TimeUnit.SECONDS); - } + for (int i = 0; i < SIZE; i++) + tasks[i]._ran.await(10, TimeUnit.SECONDS); + + assertThat(_executor._queue.size(), is(1)); - assertThat(_executor._queue.size(),is(1)); Task extra = new Task(); - assertThat(_pae.tryExecute(extra),is(false)); - assertThat(_executor._queue.size(),is(2)); - Thread.sleep(100); - assertThat(extra._ran.getCount(),is(1L)); + assertThat(_reservedExecutor.tryExecute(extra), is(false)); + assertThat(_executor._queue.size(), is(2)); - for (int i=SIZE;i-->0;) - { - task[i]._complete.countDown(); - } - - started = System.nanoTime(); - while (_pae.getPreallocated()10) - break; - Thread.sleep(100); - } - assertThat(_pae.getPreallocated(),is(SIZE)); - - + Thread.sleep(500); + assertThat(extra._ran.getCount(), is(1L)); + + for (int i = 0; i < SIZE; i++) + tasks[i]._complete.countDown(); + + waitForAllAvailable(); } - - + + protected void waitForAllAvailable() throws InterruptedException + { + long started = System.nanoTime(); + while (_reservedExecutor.getAvailable() < SIZE) + { + long elapsed = System.nanoTime() - started; + if (elapsed > TimeUnit.SECONDS.toNanos(10)) + Assert.fail(); + Thread.sleep(10); + } + assertThat(_reservedExecutor.getAvailable(), is(SIZE)); + } + private static class TestExecutor implements Executor { - Deque _queue = new ArrayDeque<>(); + private final Deque _queue = new ArrayDeque<>(); @Override public void execute(Runnable task) { _queue.addLast(task); } - + public void execute() { Runnable task = _queue.pollFirst(); - if (task!=null) + if (task != null) new Thread(task).start(); } } - - private static class NOOP implements Runnable - { - @Override - public void run() {} - } - + private static class Task implements Runnable { private CountDownLatch _ran = new CountDownLatch(1); private CountDownLatch _complete = new CountDownLatch(1); + @Override - public void run() - { + public void run() + { _ran.countDown(); try { diff --git a/jetty-util/src/test/resources/example.jar b/jetty-util/src/test/resources/example.jar new file mode 100644 index 00000000000..171a823145c Binary files /dev/null and b/jetty-util/src/test/resources/example.jar differ diff --git a/jetty-util/src/test/resources/jetty-logging.properties b/jetty-util/src/test/resources/jetty-logging.properties index 3a0ce97e2e0..adc470de2b3 100644 --- a/jetty-util/src/test/resources/jetty-logging.properties +++ b/jetty-util/src/test/resources/jetty-logging.properties @@ -1,3 +1,4 @@ # Setup default logging implementation for during testing org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog #org.eclipse.jetty.util.LEVEL=DEBUG +#org.eclipse.jetty.util.PathWatcher.LEVEL=DEBUG \ No newline at end of file diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java index 7dfa1603661..aa6b97b8936 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java @@ -43,7 +43,9 @@ import org.eclipse.jetty.util.resource.Resource; * Subclasses should implement the processEntry(URL jarUrl, JarEntry entry) * method to handle entries in jar files whose names match the supplied * pattern. + * @deprecated Does not handle MR Jars */ +@Deprecated() public abstract class JarScanner extends org.eclipse.jetty.util.PatternMatcher { private static final Logger LOG = Log.getLogger(JarScanner.class); diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 1ec031603f5..54a8fb68623 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -1431,6 +1431,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node) { + if (context.getSecurityHandler() == null) + { + LOG.warn("security-constraint declared but SecurityHandler==null"); + return; + } + Constraint scBase = new Constraint(); //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive @@ -1704,6 +1710,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node) { + if (context.getSecurityHandler() == null) + { + LOG.warn("security-role declared but SecurityHandler==null"); + return; + } //ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged XmlParser.Node roleNode = node.get("role-name"); String role = roleNode.toString(false, true); @@ -1941,6 +1952,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor */ public void visitDenyUncoveredHttpMethods(WebAppContext context, Descriptor descriptor, XmlParser.Node node) { + if (context.getSecurityHandler() == null) + { + LOG.warn("deny-uncovered-http-methods declared but SecurityHandler==null"); + return; + } + ((ConstraintAware)context.getSecurityHandler()).setDenyUncoveredHttpMethods(true); } diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java index 9935948af8e..b8841a4b015 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HotSwapHandler; +import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; @@ -218,6 +219,34 @@ public class WebAppContextTest } } + @Test + public void testNullSessionAndSecurityHandler() throws Exception + { + Server server = new Server(0); + HandlerList handlers = new HandlerList(); + ContextHandlerCollection contexts = new ContextHandlerCollection(); + WebAppContext context = new WebAppContext(null, null, null, null, null, new ErrorPageErrorHandler(), + ServletContextHandler.NO_SESSIONS|ServletContextHandler.NO_SECURITY); + context.setContextPath("/"); + context.setBaseResource(Resource.newResource("./src/test/webapp")); + server.setHandler(handlers); + handlers.addHandler(contexts); + contexts.addHandler(context); + + LocalConnector connector = new LocalConnector(server); + server.addConnector(connector); + + try + { + server.start(); + Assert.assertTrue(context.isAvailable()); + } + finally + { + server.stop(); + } + } + class ServletA extends GenericServlet { @Override diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java index 9208ae3c106..657367bcf12 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java @@ -260,9 +260,15 @@ public class WebSocketPolicy } /** - * Get the maximum size of a binary message during parsing/generating. + * Get the maximum size of a binary message during parsing. *

      - * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + * This is a memory conservation option, memory over this limit will not be + * allocated by Jetty for handling binary messages. This applies to individual frames, + * whole message handling, and partial message handling. + *

      + *

      + * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

      * * @return the maximum size of a binary message */ @@ -282,9 +288,15 @@ public class WebSocketPolicy } /** - * Get the maximum size of a text message during parsing/generating. + * Get the maximum size of a text message during parsing. *

      - * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + * This is a memory conservation option, memory over this limit will not be + * allocated by Jetty for handling text messages. This applies to individual frames, + * whole message handling, and partial message handling. + *

      + *

      + * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

      * * @return the maximum size of a text message. */ @@ -372,7 +384,7 @@ public class WebSocketPolicy /** * The maximum size of a binary message buffer. *

      - * Used ONLY for stream based message writing. + * Used ONLY for stream based binary message writing. * * @param size * the maximum size of the binary message buffer @@ -403,9 +415,15 @@ public class WebSocketPolicy } /** - * The maximum size of a binary message during parsing/generating. + * The maximum size of a binary message during parsing. *

      - * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + * This is a memory conservation option, memory over this limit will not be + * allocated by Jetty for handling binary messages. This applies to individual frames, + * whole message handling, and partial message handling. + *

      + *

      + * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

      * * @param size * the maximum allowed size of a binary message. @@ -420,7 +438,7 @@ public class WebSocketPolicy /** * The maximum size of a text message buffer. *

      - * Used ONLY for stream based message writing. + * Used ONLY for stream based text message writing. * * @param size * the maximum size of the text message buffer @@ -450,9 +468,15 @@ public class WebSocketPolicy } /** - * The maximum size of a text message during parsing/generating. + * The maximum size of a text message during parsing. *

      - * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + * This is a memory conservation option, memory over this limit will not be + * allocated by Jetty for handling text messages. This applies to individual frames, + * whole message handling, and partial message handling. + *

      + *

      + * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

      * * @param size * the maximum allowed size of a text message. diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java index 9b09d34804f..2f34b617069 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java @@ -258,7 +258,7 @@ public abstract class CompressExtension extends AbstractExtension private static boolean supplyInput(Inflater inflater, ByteBuffer buf) { - if (buf.remaining() <= 0) + if (buf == null || buf.remaining() <= 0) { if (LOG.isDebugEnabled()) { @@ -298,7 +298,7 @@ public abstract class CompressExtension extends AbstractExtension private static boolean supplyInput(Deflater deflater, ByteBuffer buf) { - if (buf.remaining() <= 0) + if (buf == null || buf.remaining() <= 0) { if (LOG.isDebugEnabled()) { @@ -451,6 +451,10 @@ public abstract class CompressExtension extends AbstractExtension // the heap if the payload is a huge mapped file. Frame frame = entry.frame; ByteBuffer data = frame.getPayload(); + + if(data == null) + data = BufferUtil.EMPTY_BUFFER; + int remaining = data.remaining(); int outputLength = Math.max(256,data.remaining()); if (LOG.isDebugEnabled()) diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/DelayedStartClientTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/DelayedStartClientTest.java index 58a29e1ccec..a66db939468 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/DelayedStartClientTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/jsr356/DelayedStartClientTest.java @@ -97,10 +97,6 @@ public class DelayedStartClientTest QueuedThreadPool qtp = (QueuedThreadPool) executor; threadNames.add(qtp.getName()); } - else - { - System.err.println("### Executor: " + executor); - } } for (ContainerLifeCycle child : container.getBeans(ContainerLifeCycle.class)) diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/DelayedStartClientOnServerTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/DelayedStartClientOnServerTest.java index 70232069a48..2451060d1fa 100644 --- a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/DelayedStartClientOnServerTest.java +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/jsr356/DelayedStartClientOnServerTest.java @@ -385,10 +385,6 @@ public class DelayedStartClientOnServerTest QueuedThreadPool qtp = (QueuedThreadPool) executor; threadNames.add(qtp.getName()); } - else - { - System.err.println("### Executor: " + executor); - } } for (ContainerLifeCycle child : container.getBeans(ContainerLifeCycle.class)) diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java index db12d70f446..953fddb40aa 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -51,6 +51,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.LifeCycle; @@ -531,6 +532,8 @@ public class XmlConfiguration if (LOG.isDebugEnabled()) LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")"); + MultiException me = new MultiException(); + // Try for trivial match try { @@ -541,6 +544,7 @@ public class XmlConfiguration catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException e) { LOG.ignore(e); + me.add(e); } // Try for native match @@ -555,6 +559,7 @@ public class XmlConfiguration catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException e) { LOG.ignore(e); + me.add(e); } // Try a field @@ -570,11 +575,13 @@ public class XmlConfiguration catch (NoSuchFieldException e) { LOG.ignore(e); + me.add(e); } // Search for a match by trying all the set methods Method[] sets = oClass.getMethods(); Method set = null; + String types = null; for (int s = 0; sets != null && s < sets.length; s++) { if (sets[s].getParameterCount()!=1) @@ -582,6 +589,7 @@ public class XmlConfiguration Class[] paramTypes = sets[s].getParameterTypes(); if (name.equals(sets[s].getName())) { + types = types==null?paramTypes[0].getName():(types+","+paramTypes[0].getName()); // lets try it try { @@ -592,6 +600,7 @@ public class XmlConfiguration catch (IllegalArgumentException | IllegalAccessException e) { LOG.ignore(e); + me.add(e); } try @@ -606,6 +615,7 @@ public class XmlConfiguration catch (IllegalAccessException e) { LOG.ignore(e); + me.add(e); } } } @@ -636,11 +646,21 @@ public class XmlConfiguration catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) { LOG.ignore(e); + me.add(e); } } // No Joy - throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")"); + String message = oClass + "." + name + "(" + vClass[0] + ")"; + if (types!=null) + message += ". Found setters for "+types; + throw new NoSuchMethodException(message) + { + { + for (int i=0; i"); + TestConfiguration tc = new TestConfiguration(); + try + { + configuration.configure(tc); + Assert.fail(); + } + catch(NoSuchMethodException e) + { + assertThat(e.getMessage(),containsString("Found setters for int")); + } + } + @Test public void testListConstructorArg() throws Exception { diff --git a/pom.xml b/pom.xml index 067fc3c4a41..99d3dc81967 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,6 @@ org.eclipse.jetty jetty-project 10.0.0-SNAPSHOT - Jetty :: Project The Eclipse Jetty Project pom @@ -21,7 +20,7 @@ 1.6.6 1.2 1.1.3.v20160715 - 8.5.9.1 + 8.5.20 4.0.0-b07 4.0.0-b07 @@ -395,7 +394,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.0.1 + 3.0.2 org.apache.maven.plugins @@ -593,7 +592,7 @@ org.eclipse.jetty.toolchain jetty-version-maven-plugin - 2.4 + 2.5 org.jacoco @@ -1112,6 +1111,20 @@ true false true + Tag for release: jetty-${project.version} + + jetty-documentation/.* + examples/.* + aggregates/.* + .*/test-.* + .*/.*-test/.* + .*/.*-tests/.* + .*/src/test/.* + \.git.* + .*\.md$ + .*\.txt$ + Jenkinsfile + @@ -1121,12 +1134,8 @@ aggregate-site diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index 78832e43f19..d4e41dfe06d 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -21,6 +21,20 @@ [1.8,1.9) + + + org.eclipse.jetty + jetty-alpn-openjdk8-client + ${project.version} + test + + + org.eclipse.jetty + jetty-alpn-openjdk8-server + ${project.version} + test + + @@ -92,6 +106,12 @@ + + org.eclipse.jetty + jetty-jmx + ${project.version} + test + org.eclipse.jetty jetty-servlet @@ -110,12 +130,6 @@ ${project.version} test - - org.eclipse.jetty - jetty-alpn-server - ${project.version} - test - org.eclipse.jetty.http2 http2-http-client-transport diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java index 911b38f59e9..a4cfa47104b 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.http.client; +import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.List; @@ -34,6 +35,7 @@ import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -114,7 +116,10 @@ public abstract class AbstractTest sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); QueuedThreadPool serverThreads = new QueuedThreadPool(); serverThreads.setName("server"); + serverThreads.setDetailedDump(true); server = new Server(serverThreads); + MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); + server.addBean(mbeanContainer); connector = newServerConnector(server); server.addConnector(connector); server.setHandler(handler); @@ -130,10 +135,13 @@ public abstract class AbstractTest { QueuedThreadPool clientThreads = new QueuedThreadPool(); clientThreads.setName("client"); + clientThreads.setDetailedDump(true); client = newHttpClient(provideClientTransport(transport), sslContextFactory); client.setExecutor(clientThreads); client.setSocketAddressResolver(new SocketAddressResolver.Sync()); client.start(); + if (server != null) + server.addBean(client); } protected ConnectionFactory[] provideServerConnectionFactory(Transport transport) diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index a2367057b61..606de7470d3 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -84,6 +84,12 @@ org.eclipse.jetty jetty-infinispan ${project.version} + + + org.infinispan + infinispan-core + + org.eclipse.jetty @@ -96,10 +102,16 @@ jetty-test-helper test + + org.infinispan + infinispan-core + 9.1.0.Final + test + org.infinispan infinispan-client-hotrod - 7.1.1.Final + 9.1.0.Final test diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredLastAccessTimeTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredLastAccessTimeTest.java index e3780be7c3b..14fd6b2e0d9 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredLastAccessTimeTest.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredLastAccessTimeTest.java @@ -19,26 +19,27 @@ package org.eclipse.jetty.server.session; import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory; +import org.eclipse.jetty.toolchain.test.JDK; import org.junit.AfterClass; import org.junit.BeforeClass; public class ClusteredLastAccessTimeTest extends AbstractClusteredLastAccessTimeTest { - public static InfinispanTestSupport __testSupport; - - + public static InfinispanTestSupport __testSupport; + @BeforeClass public static void setup () throws Exception { - __testSupport = new InfinispanTestSupport(); - __testSupport.setUseFileStore(true); - __testSupport.setup(); + __testSupport = new InfinispanTestSupport(); + __testSupport.setUseFileStore(true); + __testSupport.setup(); } @AfterClass public static void teardown () throws Exception { - __testSupport.teardown(); + if (__testSupport != null) + __testSupport.teardown(); } @@ -58,5 +59,4 @@ public class ClusteredLastAccessTimeTest extends AbstractClusteredLastAccessTime factory.setCache(__testSupport.getCache()); return factory; } - } diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java index e574172302f..538d6c15491 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.server.session; import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory; +import org.eclipse.jetty.toolchain.test.JDK; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -30,7 +31,6 @@ import org.junit.BeforeClass; */ public class ClusteredSessionScavengingTest extends AbstractClusteredSessionScavengingTest { - public static InfinispanTestSupport __testSupport; @BeforeClass @@ -44,7 +44,8 @@ public class ClusteredSessionScavengingTest extends AbstractClusteredSessionScav @AfterClass public static void teardown () throws Exception { - __testSupport.teardown(); + if (__testSupport != null) + __testSupport.teardown(); } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml index 4a978e9fa79..10347114ba0 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml @@ -71,9 +71,7 @@ detected.