diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9391cc18ab2..ef6433b76c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,10 +53,10 @@ Be sure to search for existing bugs before you create another one. Remember that Reporting Security Issues ----------------- -There are a number of avenues for reporting security issues to the Jetty project available. -If the issue is directly related to Jetty itself then reporting to the Jetty developers is encouraged. -The most direct method is to mail [security@webtide.com](mailto:security@webtide.com). -Webtide is comprised of the active committers of the Jetty project is our preferred reporting method. +There are a number of avenues for reporting security issues to the Jetty project available. +If the issue is directly related to Jetty itself then reporting to the Jetty developers is encouraged. +The most direct method is to mail [security@webtide.com](mailto:security@webtide.com). +Webtide is comprised of the active committers of the Jetty project is our preferred reporting method. We are flexible in how we work with reporters of security issues but we reserve the right to act in the interests of the Jetty project in all circumstances. If the issue is related to Eclipse or its Jetty integration then we encourage you to reach out to [security@eclipse.org](mailto:security@eclipse.org). diff --git a/Jenkinsfile b/Jenkinsfile index bfbf28a8a79..822859a218c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,11 +40,11 @@ pipeline { } } - stage("Build / Test - JDK12") { + stage("Build / Test - JDK13") { agent { node { label 'linux' } } steps { timeout(time: 120, unit: 'MINUTES') { - mavenBuild("jdk12", "-Pmongodb install", "maven3", true) + mavenBuild("jdk13", "-Pmongodb install", "maven3", true) warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml' } diff --git a/KEYS.txt b/KEYS.txt index acf04a8cc33..73b9fb49a4e 100644 --- a/KEYS.txt +++ b/KEYS.txt @@ -2,6 +2,6 @@ Jan Bartel AED5 EE6C 45D0 FE8D 5D1B 164F 27DE D4BF 6216 DB8F Jesse McConnell 2A68 4B57 436A 81FA 8706 B53C 61C3 351A 438A 3B7D Joakim Erdfelt 5989 BAF7 6217 B843 D66B E55B 2D0E 1FB8 FE4B 68B4 -Joakim Erdfelt B59B 67FD 7904 9843 67F9 3180 0818 D9D6 8FB6 7BAC +Joakim Erdfelt B59B 67FD 7904 9843 67F9 3180 0818 D9D6 8FB6 7BAC Joakim Erdfelt BFBB 21C2 46D7 7768 3628 7A48 A04E 0C74 ABB3 5FEA Simone Bordet 8B09 6546 B1A8 F026 56B1 5D3B 1677 D141 BCF3 584D diff --git a/VERSION.txt b/VERSION.txt index 75fe0fd2454..84b55cbe58f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -79,6 +79,7 @@ jetty-10.0.0-alpha0 - 11 July 2019 + 2095 Remove FastCGI multiplexing + 2103 Server should open connectors early in start sequence + 2108 Update licence headers and plugin for 2018 + + 2140 Infinispan and hazelcast changes to scavenge zombie expired sessions. + 2172 Support javax.websocket 1.1 + 2175 Refactor WebSocket close handling + 2191 JPMS Support @@ -289,6 +290,7 @@ jetty-9.4.18.v20190429 - 29 April 2019 + 3609 Fix infinispan start module dependencies jetty-9.4.17.v20190418 - 18 April 2019 + + 2140 Infinispan and hazelcast changes to scavenge zombie expired sessions. + 3464 Split SslContextFactory into Client and Server + 3549 Directory Listing on Windows reveals Resource Base path + 3555 DefaultHandler Reveals Base Resource Path of each Context diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index c1cb601169c..cd11566a15b 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -132,7 +132,32 @@ org.eclipse.jetty.toolchain jetty-test-helper - + test + + + org.eclipse.jetty.websocket + jetty-websocket-client + ${project.version} + test + + + org.eclipse.jetty + jetty-distribution + ${project.version} + tar.gz + test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java index e225703dc2b..7a53073e6ee 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; -import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -35,7 +34,7 @@ public class AsyncEchoServlet extends HttpServlet private static final long serialVersionUID = 1L; @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { AsyncContext asyncContext = request.startAsync(request, response); asyncContext.setTimeout(0); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java index 38372558bd8..0f21dccf722 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java @@ -20,6 +20,8 @@ package org.eclipse.jetty.embedded; import java.io.IOException; import java.io.PrintWriter; +import java.util.Collections; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -45,12 +47,28 @@ public class DumpServlet extends HttpServlet out.println("pathInfo=" + request.getPathInfo()); out.println("session=" + request.getSession(true).getId()); + ServletContext servletContext = getServletContext(); + String r = request.getParameter("resource"); if (r != null) { - out.println("resource(" + r + ")=" + getServletContext().getResource(r)); + out.println("resource(" + r + ")=" + servletContext.getResource(r)); } + Collections.list(request.getAttributeNames()) + .stream() + .filter((name) -> name.startsWith("X-")) + .sorted() + .forEach((name) -> + out.println("request.attribute[" + name + "]=" + request.getAttribute(name))); + + Collections.list(servletContext.getAttributeNames()) + .stream() + .filter((name) -> name.startsWith("X-")) + .sorted() + .forEach((name) -> + out.println("servletContext.attribute[" + name + "]=" + servletContext.getAttribute(name))); + out.println(""); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java index b4d5ac45fff..f003bafe00a 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java @@ -28,12 +28,12 @@ import org.eclipse.jetty.servlet.ServletContextHandler; public class ExampleServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { Server server = new Server(); ServerConnector connector = new ServerConnector(server); - connector.setPort(8080); + connector.setPort(port); server.setConnectors(new Connector[]{connector}); ServletContextHandler context = new ServletContextHandler(); @@ -45,6 +45,13 @@ public class ExampleServer handlers.setHandlers(new Handler[]{context, new DefaultHandler()}); server.setHandler(handlers); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java index e65f001967a..1ca2a34440f 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java @@ -18,21 +18,31 @@ package org.eclipse.jetty.embedded; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.xml.XmlConfiguration; /** * Configures and Starts a Jetty server from an XML declaration. - *

- * See exampleserver.xml - *

*/ public class ExampleServerXml { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws Exception { // Find Jetty XML (in classpath) that configures and starts Server. + // See src/main/resources/exampleserver.xml Resource serverXml = Resource.newSystemResource("exampleserver.xml"); - XmlConfiguration.main(serverXml.getFile().getAbsolutePath()); + XmlConfiguration xml = new XmlConfiguration(serverXml); + xml.getProperties().put("http.port", Integer.toString(port)); + Server server = (Server)xml.configure(); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + server.start(); + server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleUtil.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleUtil.java new file mode 100644 index 00000000000..ce4d77c8966 --- /dev/null +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleUtil.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import org.eclipse.jetty.util.StringUtil; + +public class ExampleUtil +{ + /** + * Get a port, possibly configured from Command line or System property. + * + * @param args the command line arguments + * @param propertyName the property name + * @param defValue the default value + * @return the configured port + */ + public static int getPort(String[] args, String propertyName, int defValue) + { + for (String arg : args) + { + if (arg.startsWith(propertyName + "=")) + { + String value = arg.substring(propertyName.length() + 2); + int port = toInt(value); + if (isValidPort(port)) + return port; + } + } + + String value = System.getProperty(propertyName); + int port = toInt(value); + if (isValidPort(port)) + return port; + + return defValue; + } + + /** + * Test if port is in the valid range to be used. + * + * @param port the port to test + * @return true if valid + */ + private static boolean isValidPort(int port) + { + return (port >= 0) && (port <= 65535); + } + + /** + * Parse an int, ignoring any {@link NumberFormatException} + * + * @param value the string value to parse + * @return the int (if parsed), or -1 if not parsed. + */ + private static int toInt(String value) + { + if (StringUtil.isBlank(value)) + return -1; + + try + { + return Integer.parseInt(value); + } + catch (NumberFormatException ignored) + { + // ignored + return -1; + } + } +} diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java index fc5e952f443..189fbdfa5ac 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java @@ -58,17 +58,24 @@ import org.eclipse.jetty.util.resource.Resource; */ public class FastFileServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port, File resourceBase) { - Server server = new Server(8080); + Server server = new Server(port); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[]{ - new FastFileHandler(new File(System.getProperty("user.dir"))), + new FastFileHandler(resourceBase), new DefaultHandler() }); server.setHandler(handlers); + return server; + } + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + File directory = new File(System.getProperty("user.dir")); + Server server = createServer(port, directory); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java index e57bc126c4f..b9d6714f2cc 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java @@ -18,11 +18,16 @@ package org.eclipse.jetty.embedded; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.util.resource.PathResource; +import org.eclipse.jetty.util.resource.Resource; /** * Simple Jetty FileServer. @@ -30,12 +35,12 @@ import org.eclipse.jetty.server.handler.ResourceHandler; */ public class FileServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port, Resource baseResource) throws Exception { // Create a basic Jetty server object that will listen on port 8080. Note that if you set this to port 0 // then a randomly available port will be assigned that you can either look in the logs for the port, // or programmatically obtain it for use in test cases. - final Server server = new Server(8080); + Server server = new Server(port); // Create the ResourceHandler. It is the object that will actually handle the request for a given file. It is // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples. @@ -45,13 +50,24 @@ public class FileServer // In this example it is the current directory but it can be configured to anything that the jvm has access to. resourceHandler.setDirectoriesListed(true); resourceHandler.setWelcomeFiles(new String[]{"index.html"}); - resourceHandler.setResourceBase("."); + resourceHandler.setBaseResource(baseResource); // Add the ResourceHandler to the server. HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[]{resourceHandler, new DefaultHandler()}); server.setHandler(handlers); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Path userDir = Paths.get(System.getProperty("user.dir")); + PathResource pathResource = new PathResource(userDir); + + Server server = createServer(port, pathResource); + // Start things up! By using the server.join() the server thread will join with the current thread. // See "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()" for more details. server.start(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java index 8b37cdb6fdd..73481ef4e34 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java @@ -18,6 +18,9 @@ package org.eclipse.jetty.embedded; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.xml.XmlConfiguration; @@ -28,17 +31,25 @@ import org.eclipse.jetty.xml.XmlConfiguration; * This server is identical to {@link FileServer}, except that it is configured * via an {@link XmlConfiguration} config file that does the identical work. *

- *

- * See fileserver.xml - *

*/ public class FileServerXml { - public static void main(String[] args) throws Exception + public static Server createServer(int port, Path baseResource) throws Exception { + // Find Jetty XML (in classpath) that configures and starts Server. + // See src/main/resources/fileserver.xml Resource fileServerXml = Resource.newSystemResource("fileserver.xml"); XmlConfiguration configuration = new XmlConfiguration(fileServerXml); - Server server = (Server)configuration.configure(); + configuration.getProperties().put("http.port", Integer.toString(port)); + configuration.getProperties().put("fileserver.baseresource", baseResource.toAbsolutePath().toString()); + return (Server)configuration.configure(); + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Path userDir = Paths.get(System.getProperty("user.dir")); + Server server = createServer(port, userDir); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloWorld.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloWorld.java index 151432fe6b9..5218c5e9249 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloWorld.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloWorld.java @@ -51,7 +51,8 @@ public class HelloWorld extends AbstractHandler public static void main(String[] args) throws Exception { - Server server = new Server(8080); + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = new Server(port); server.setHandler(new HelloWorld()); server.start(); 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 c07590e1d37..0552725c53d 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 @@ -18,9 +18,12 @@ package org.eclipse.jetty.embedded; -import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Date; import java.util.EnumSet; @@ -56,12 +59,15 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.PushCacheFilter; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.ssl.SslContextFactory; public class Http2Server { public static void main(String... args) throws Exception { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443); Server server = new Server(); MBeanContainer mbContainer = new MBeanContainer( @@ -69,10 +75,11 @@ public class Http2Server server.addBean(mbContainer); ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); - String docroot = "src/main/resources/docroot"; - if (!new File(docroot).exists()) - docroot = "examples/embedded/src/main/resources/docroot"; - context.setResourceBase(docroot); + Path docroot = Paths.get("src/main/resources/docroot"); + if (!Files.exists(docroot)) + throw new FileNotFoundException(docroot.toString()); + + context.setBaseResource(new PathResource(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)); @@ -83,21 +90,21 @@ public class Http2Server // HTTP Configuration HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSecureScheme("https"); - httpConfig.setSecurePort(8443); + httpConfig.setSecurePort(securePort); httpConfig.setSendXPoweredBy(true); httpConfig.setSendServerVersion(true); // HTTP Connector ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig)); - http.setPort(8080); + http.setPort(port); server.addConnector(http); // SSL Context Factory for HTTPS and HTTP/2 - String jettyDistro = System.getProperty("jetty.distro", "../../jetty-distribution/target/distribution"); - if (!new File(jettyDistro).exists()) - jettyDistro = "jetty-distribution/target/distribution"; + Path keystorePath = Paths.get("src/main/resources/etc/keystore").toAbsolutePath(); + if (!Files.exists(keystorePath)) + throw new FileNotFoundException(keystorePath.toString()); SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePath(jettyDistro + "/demo-base/etc/keystore"); + sslContextFactory.setKeyStorePath(keystorePath.toString()); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); @@ -119,7 +126,7 @@ public class Http2Server // HTTP/2 Connector ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2, new HttpConnectionFactory(httpsConfig)); - http2Connector.setPort(8443); + http2Connector.setPort(securePort); server.addConnector(http2Connector); server.start(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java index c84ee9b3c33..027f5f922d6 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java @@ -18,7 +18,11 @@ package org.eclipse.jetty.embedded; -import org.eclipse.jetty.server.Handler; +import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; @@ -28,24 +32,36 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.resource.Resource; /** - * + * Example of serving content from a JAR file. + * The JAR file in this example does not belong to any Classpath. */ public class JarServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws Exception { - final Server server = new Server(8080); + Server server = new Server(port); + + Path jarFile = Paths.get("src/main/other/content.jar"); + if (!Files.exists(jarFile)) + throw new FileNotFoundException(jarFile.toString()); ServletContextHandler context = new ServletContextHandler(); Resource.setDefaultUseCaches(true); - Resource base = Resource.newResource("jar:file:src/main/resources/content.jar!/"); + Resource base = Resource.newResource("jar:" + jarFile.toAbsolutePath().toUri().toASCIIString() + "!/"); context.setBaseResource(base); context.addServlet(new ServletHolder(new DefaultServlet()), "/"); HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{context, new DefaultHandler()}); + handlers.addHandler(context); + handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); + return server; + } + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JettyDistribution.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JettyDistribution.java index 5f707aca97f..32d09d17c99 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JettyDistribution.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JettyDistribution.java @@ -18,8 +18,9 @@ package org.eclipse.jetty.embedded; -import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; @@ -41,54 +42,80 @@ public class JettyDistribution static { Path distro = asJettyDistribution(System.getProperty("jetty.home")); + LOG.debug("JettyDistribution(prop(jetty.home)) = " + distro); if (distro == null) + { distro = asJettyDistribution(System.getenv().get("JETTY_HOME")); + LOG.debug("JettyDistribution(env(JETTY_HOME)) = " + distro); + } if (distro == null) { try { - Path working = new File(".").getAbsoluteFile().getCanonicalFile().toPath(); + Path working = Paths.get(System.getProperty("user.dir")); + LOG.debug("JettyDistribution(prop(user.dir)) = " + working); while (distro == null && working != null) { distro = asJettyDistribution(working.resolve("jetty-distribution/target/distribution").toString()); working = working.getParent(); } + LOG.debug("JettyDistribution(working.resolve(...)) = " + distro); } catch (Throwable th) { LOG.warn(th); } } + + if (distro == null) + { + LOG.info("JettyDistribution() FAILURE: NOT FOUND"); + } + else + { + LOG.debug("JettyDistribution() FOUND = " + distro); + } DISTRIBUTION = distro; } - private static Path asJettyDistribution(String test) + private static Path asJettyDistribution(String jettyHome) { try { - if (StringUtil.isBlank(test)) + if (jettyHome == null) { - LOG.info("asJettyDistribution {} is blank", test); return null; } - File dir = new File(test); - if (!dir.exists() || !dir.isDirectory()) + if (StringUtil.isBlank(jettyHome)) { - LOG.info("asJettyDistribution {} is not a directory", test); + LOG.debug("asJettyDistribution {} is blank", jettyHome); return null; } - File demoBase = new File(dir, "demo-base"); - if (!demoBase.exists() || !demoBase.isDirectory()) + Path dir = Paths.get(jettyHome); + if (!Files.exists(dir)) { - LOG.info("asJettyDistribution {} has no demo-base", test); + LOG.debug("asJettyDistribution {} does not exist", jettyHome); return null; } - LOG.info("asJettyDistribution {}", dir); - return dir.getAbsoluteFile().getCanonicalFile().toPath(); + if (!Files.isDirectory(dir)) + { + LOG.debug("asJettyDistribution {} is not a directory", jettyHome); + return null; + } + + Path demoBase = dir.resolve("demo-base"); + if (!Files.exists(demoBase) || !Files.isDirectory(demoBase)) + { + LOG.debug("asJettyDistribution {} has no demo-base", jettyHome); + return null; + } + + LOG.debug("asJettyDistribution {}", dir); + return dir.toAbsolutePath(); } catch (Exception e) { @@ -97,9 +124,16 @@ public class JettyDistribution return null; } + public static Path get() + { + if (DISTRIBUTION == null) + throw new RuntimeException("jetty-distribution not found"); + return DISTRIBUTION; + } + public static Path resolve(String path) { - return DISTRIBUTION.resolve(path); + return get().resolve(path); } public static void main(String... arg) diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java index 752d071f682..fa0b53a12d1 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java @@ -18,12 +18,16 @@ package org.eclipse.jetty.embedded; -import java.io.File; +import java.io.FileNotFoundException; import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.deploy.DeploymentManager; import org.eclipse.jetty.deploy.PropertiesConfigurationManager; +import org.eclipse.jetty.deploy.bindings.DebugListenerBinding; import org.eclipse.jetty.deploy.providers.WebAppProvider; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.jmx.MBeanContainer; @@ -35,6 +39,7 @@ import org.eclipse.jetty.rewrite.handler.ValidUrlRule; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.AsyncRequestLogWriter; import org.eclipse.jetty.server.CustomRequestLog; +import org.eclipse.jetty.server.DebugListener; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -59,21 +64,21 @@ import org.eclipse.jetty.webapp.Configurations; */ public class LikeJettyXml { - public static void main(String[] args) throws Exception + public static Server createServer(int port, int securePort, boolean addDebugListener) throws Exception { // Path to as-built jetty-distribution directory - String jettyHomeBuild = JettyDistribution.DISTRIBUTION.toString(); + Path jettyHomeBuild = JettyDistribution.get(); // Find jetty home and base directories - String homePath = System.getProperty("jetty.home", jettyHomeBuild); - File homeDir = new File(homePath); + String homePath = System.getProperty("jetty.home", jettyHomeBuild.toString()); + Path homeDir = Paths.get(homePath); - String basePath = System.getProperty("jetty.base", homeDir + "/demo-base"); - File baseDir = new File(basePath); + String basePath = System.getProperty("jetty.base", homeDir.resolve("demo-base").toString()); + Path baseDir = Paths.get(basePath); // Configure jetty.home and jetty.base system properties - String jettyHome = homeDir.getAbsolutePath(); - String jettyBase = baseDir.getAbsolutePath(); + String jettyHome = homeDir.toAbsolutePath().toString(); + String jettyBase = baseDir.toAbsolutePath().toString(); System.setProperty("jetty.home", jettyHome); System.setProperty("jetty.base", jettyBase); @@ -91,7 +96,7 @@ public class LikeJettyXml // HTTP Configuration HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSecureScheme("https"); - httpConfig.setSecurePort(8443); + httpConfig.setSecurePort(securePort); httpConfig.setOutputBufferSize(32768); httpConfig.setRequestHeaderSize(8192); httpConfig.setResponseHeaderSize(8192); @@ -105,11 +110,6 @@ public class LikeJettyXml handlers.setHandlers(new Handler[]{contexts, new DefaultHandler()}); server.setHandler(handlers); - // Extra options - server.setDumpAfterStart(true); - server.setDumpBeforeStop(false); - server.setStopAtShutdown(true); - // === jetty-jmx.xml === MBeanContainer mbContainer = new MBeanContainer( ManagementFactory.getPlatformMBeanServer()); @@ -118,24 +118,21 @@ public class LikeJettyXml // === jetty-http.xml === ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); - http.setPort(8080); + http.setPort(port); http.setIdleTimeout(30000); server.addConnector(http); // === jetty-https.xml === // SSL Context Factory + Path keystorePath = Paths.get("src/main/resources/etc/keystore").toAbsolutePath(); + if (!Files.exists(keystorePath)) + throw new FileNotFoundException(keystorePath.toString()); SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePath(jettyHome + "/../../../jetty-server/src/test/config/etc/keystore"); + sslContextFactory.setKeyStorePath(keystorePath.toString()); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); - sslContextFactory.setTrustStorePath(jettyHome + "/../../../jetty-server/src/test/config/etc/keystore"); + sslContextFactory.setTrustStorePath(keystorePath.toString()); sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); - sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA", - "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", - "SSL_RSA_EXPORT_WITH_RC4_40_MD5", - "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", - "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); // SSL HTTP Configuration HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); @@ -145,14 +142,17 @@ public class LikeJettyXml ServerConnector sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConfig)); - sslConnector.setPort(8443); + sslConnector.setPort(securePort); server.addConnector(sslConnector); // === jetty-deploy.xml === DeploymentManager deployer = new DeploymentManager(); - //DebugListener debug = new DebugListener(System.out,true,true,true); - // server.addBean(debug); - // deployer.addLifeCycleBinding(new DebugListenerBinding(debug)); + if (addDebugListener) + { + DebugListener debug = new DebugListener(System.err, true, true, true); + server.addBean(debug); + deployer.addLifeCycleBinding(new DebugListenerBinding(debug)); + } deployer.setContexts(contexts); deployer.setContextAttribute( "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", @@ -208,6 +208,20 @@ public class LikeJettyXml login.setHotReload(false); server.addBean(login); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443); + Server server = createServer(port, securePort, true); + + // Extra options + server.setDumpAfterStart(true); + server.setDumpBeforeStop(false); + server.setStopAtShutdown(true); + // Start the server server.start(); 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 fa8f86f1c97..351d853c9fb 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 @@ -18,8 +18,10 @@ package org.eclipse.jetty.embedded; -import java.io.File; import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Connector; @@ -36,23 +38,13 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; */ public class ManyConnectors { - public static void main(String[] args) throws Exception + public static Server createServer(int plainPort, int securePort) 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 - // probably be a direct path to your own keystore. - - String jettyDistKeystore = "../../jetty-distribution/target/distribution/demo-base/etc/test-keystore"; - String keystorePath = System.getProperty("example.keystore", jettyDistKeystore); - File keystoreFile = new File(keystorePath); - if (!keystoreFile.exists()) - { - keystorePath = "jetty-distribution/target/distribution/demo-base/etc/keystore"; - keystoreFile = new File(keystorePath); - if (!keystoreFile.exists()) - throw new FileNotFoundException(keystoreFile.getAbsolutePath()); - } + // with the appropriate key. + Path keystorePath = Paths.get("src/main/resources/etc/keystore").toAbsolutePath(); + if (!Files.exists(keystorePath)) + throw new FileNotFoundException(keystorePath.toString()); // Create a basic jetty server object without declaring the port. Since // we are configuring connectors directly we'll be setting ports on @@ -67,7 +59,7 @@ public class ManyConnectors // done. The port for secured communication is also set here. HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSecureScheme("https"); - httpConfig.setSecurePort(8443); + httpConfig.setSecurePort(securePort); httpConfig.setOutputBufferSize(32768); // HTTP connector @@ -77,7 +69,7 @@ public class ManyConnectors // configure an idle timeout. ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); - http.setPort(8080); + http.setPort(plainPort); http.setIdleTimeout(30000); // SSL Context Factory for HTTPS @@ -88,7 +80,7 @@ public class ManyConnectors // keystore to be used. SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath()); + sslContextFactory.setKeyStorePath(keystorePath.toString()); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); @@ -118,7 +110,7 @@ public class ManyConnectors ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConfig)); - https.setPort(8443); + https.setPort(securePort); https.setIdleTimeout(500000); // Here you see the server having multiple connectors registered with @@ -132,7 +124,14 @@ public class ManyConnectors // Set a handler server.setHandler(new HelloHandler()); + return server; + } + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443); + Server server = createServer(port, securePort); // Start the server server.start(); server.dumpStdErr(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java index 944cce31853..d64735e964f 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java @@ -18,39 +18,42 @@ package org.eclipse.jetty.embedded; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; public class ManyContexts { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - final Server server = new Server(8080); + Server server = new Server(port); ContextHandler context = new ContextHandler("/"); context.setContextPath("/"); context.setHandler(new HelloHandler("Root Hello")); ContextHandler contextFR = new ContextHandler("/fr"); - contextFR.setHandler(new HelloHandler("Bonjoir")); + contextFR.setHandler(new HelloHandler("Bonjour")); ContextHandler contextIT = new ContextHandler("/it"); - contextIT.setHandler(new HelloHandler("Bongiorno")); + contextIT.setHandler(new HelloHandler("Buongiorno")); ContextHandler contextV = new ContextHandler("/"); contextV.setVirtualHosts(new String[]{"127.0.0.2"}); contextV.setHandler(new HelloHandler("Virtual Hello")); - ContextHandlerCollection contexts = new ContextHandlerCollection(); - contexts.setHandlers(new Handler[]{ - context, contextFR, contextIT, - contextV - }); + ContextHandlerCollection contexts = new ContextHandlerCollection( + context, contextFR, contextIT, contextV + ); server.setHandler(contexts); + return server; + } + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.dumpStdErr(); server.join(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java index 1cbc13d0d4f..e6dfce9a1b9 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java @@ -30,6 +30,8 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; @@ -99,20 +101,23 @@ public class ManyHandlers HttpServletResponse response) throws IOException, ServletException { - request.setAttribute("welcome", "Hello"); + response.setHeader("X-Welcome", "Greetings from WelcomeWrapHandler"); super.handle(target, baseRequest, request, response); } } - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws IOException { - final Server server = new Server(8080); + Server server = new Server(port); // create the handlers - final Handler param = new ParamHandler(); - final HandlerWrapper wrapper = new WelcomeWrapHandler(); - final Handler hello = new HelloHandler(); - final Handler dft = new DefaultHandler(); + Handler param = new ParamHandler(); + HandlerWrapper wrapper = new WelcomeWrapHandler(); + Handler hello = new HelloHandler(); + GzipHandler gzipHandler = new GzipHandler(); + gzipHandler.setMinGzipSize(10); + gzipHandler.addIncludedMimeTypes("text/plain"); + gzipHandler.addIncludedMimeTypes("text/html"); // configure request logging File requestLogFile = File.createTempFile("demo", "log"); @@ -120,16 +125,47 @@ public class ManyHandlers server.setRequestLog(ncsaLog); // create the handler collections - HandlerCollection handlers = new HandlerCollection(); - HandlerList list = new HandlerList(); + HandlerList handlers = new HandlerList(); - // link them all together + // wrap contexts around specific handlers wrapper.setHandler(hello); - list.setHandlers(new Handler[]{param, new GzipHandler()}); - handlers.setHandlers(new Handler[]{list, dft}); + ContextHandler helloContext = new ContextHandler("/hello"); + helloContext.setHandler(wrapper); + ContextHandler paramContext = new ContextHandler("/params"); + paramContext.setHandler(param); + + ContextHandlerCollection contexts = new ContextHandlerCollection(helloContext, paramContext); + + // Wrap Contexts with GZIP + gzipHandler.setHandler(contexts); + + // Set the top level Handler List + handlers.addHandler(gzipHandler); + handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); + /* At this point you have the following handler hierarchy. + * + * Server.handler: + * HandlerList + * \- GzipHandler + * | \- ContextHandlerCollection + * | \- ContextHandler ("/hello") + * | | \- WelcomeWrapHandler + * | | \- HelloHandler + * | \- ContextHandler ("/params") + * | \- ParamHandler + * \- DefaultHandler + */ + + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java index 53fa7317c04..d1adc4be4c3 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java @@ -29,9 +29,9 @@ import org.eclipse.jetty.servlet.ServletHolder; public class ManyServletContexts { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - Server server = new Server(8080); + Server server = new Server(port); // Setup JMX MBeanContainer mbContainer = new MBeanContainer( @@ -48,7 +48,7 @@ public class ManyServletContexts // Add servlets to root context root.addServlet(new ServletHolder(new HelloServlet("Hello")), "/"); root.addServlet(new ServletHolder(new HelloServlet("Ciao")), "/it/*"); - root.addServlet(new ServletHolder(new HelloServlet("Bonjoir")), "/fr/*"); + root.addServlet(new ServletHolder(new HelloServlet("Bonjour")), "/fr/*"); // Configure context "/other" for servlets ServletContextHandler other = new ServletContextHandler(contexts, @@ -57,6 +57,13 @@ public class ManyServletContexts other.addServlet(DefaultServlet.class.getCanonicalName(), "/"); other.addServlet(new ServletHolder(new HelloServlet("YO!")), "*.yo"); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.dumpStdErr(); server.join(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java index 5ce9a24016c..76a9fe82cda 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.embedded; import java.io.IOException; -import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,13 +28,13 @@ import org.eclipse.jetty.servlet.ServletHandler; public class MinimalServlets { - public static void main(String[] args) throws Exception + + public static Server createServer(int port) { - // Create a basic jetty server object that will listen on port 8080. // Note that if you set this to port 0 then a randomly available port // will be assigned that you can either look in the logs for the port, // or programmatically obtain it for use in test cases. - Server server = new Server(8080); + Server server = new Server(port); // The ServletHandler is a dead simple way to create a context handler // that is backed by an instance of a Servlet. @@ -51,13 +50,20 @@ public class MinimalServlets // through a web.xml @WebServlet annotation, or anything similar. handler.addServletWithMapping(HelloServlet.class, "/*"); + return server; + } + + public static void main(String[] args) throws Exception + { + // Create a basic jetty server object that will listen on port 8080. + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + // Start things up! server.start(); // The use of server.join() the will make the current thread join and - // wait until the server is done executing. - // See - // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() + // wait until the server thread is done executing. server.join(); } @@ -66,11 +72,11 @@ public class MinimalServlets { @Override protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, - IOException + HttpServletResponse response) throws IOException { - response.setContentType("text/html"); response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("text/html"); + response.setCharacterEncoding("utf-8"); response.getWriter().println("

Hello from HelloServlet

"); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java index c6debe8a23b..6e183117536 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java @@ -26,7 +26,7 @@ import org.eclipse.jetty.server.ServerConnector; */ public class OneConnector { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws Exception { // The Server Server server = new Server(); @@ -34,7 +34,7 @@ public class OneConnector // HTTP connector ServerConnector http = new ServerConnector(server); http.setHost("localhost"); - http.setPort(8080); + http.setPort(port); http.setIdleTimeout(30000); // Set the connector @@ -42,6 +42,13 @@ public class OneConnector // Set a handler server.setHandler(new HelloHandler()); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); // Start the server server.start(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java index 1331e6abb55..c27556f0f1e 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java @@ -23,9 +23,9 @@ import org.eclipse.jetty.server.handler.ContextHandler; public class OneContext { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - Server server = new Server(8080); + Server server = new Server(port); // Add a single handler on context "/hello" ContextHandler context = new ContextHandler(); @@ -35,6 +35,13 @@ public class OneContext // Can be accessed using http://localhost:8080/hello server.setHandler(context); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); // Start the server server.start(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java index b9a2f942f75..10df55d687d 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java @@ -22,11 +22,17 @@ import org.eclipse.jetty.server.Server; public class OneHandler { + public static Server createServer(int port) + { + Server server = new Server(port); + server.setHandler(new HelloHandler()); + return server; + } + public static void main(String[] args) throws Exception { - Server server = new Server(8080); - server.setHandler(new HelloHandler()); - + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java index 720309dd917..aa468d089cb 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java @@ -19,8 +19,9 @@ package org.eclipse.jetty.embedded; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.EnumSet; -import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -31,38 +32,59 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ListenerHolder; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.resource.PathResource; +import org.eclipse.jetty.util.resource.Resource; + +import static javax.servlet.DispatcherType.ASYNC; +import static javax.servlet.DispatcherType.REQUEST; public class OneServletContext { - public static void main(String[] args) throws Exception + public static Server createServer(int port, Resource baseResource) { - Server server = new Server(8080); + Server server = new Server(port); - ServletContextHandler context = new ServletContextHandler( - ServletContextHandler.SESSIONS); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); - context.setResourceBase(System.getProperty("java.io.tmpdir")); + context.setBaseResource(baseResource); server.setHandler(context); - // Add dump servlet - context.addServlet( - context.addServlet(DumpServlet.class, "/dump/*"), - "*.dump"); + // add hello servlet context.addServlet(HelloServlet.class, "/hello/*"); + + // Add dump servlet on multiple url-patterns + ServletHolder debugHolder = new ServletHolder("debug", DumpServlet.class); + context.addServlet(debugHolder, "/dump/*"); + context.addServlet(debugHolder, "*.dump"); + + // add default servlet (for error handling and static resources) context.addServlet(DefaultServlet.class, "/"); - context.addFilter(TestFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - context.addFilter(TestFilter.class, "/test", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC)); - context.addFilter(TestFilter.class, "*.test", EnumSet.of(DispatcherType.REQUEST, DispatcherType.INCLUDE, DispatcherType.FORWARD)); + // sprinkle in a few filters to demonstrate behaviors + context.addFilter(TestFilter.class, "/test/*", EnumSet.of(REQUEST)); + context.addFilter(TestFilter.class, "*.test", EnumSet.of(REQUEST, ASYNC)); + // and a few listeners to show other ways of working with servlets context.getServletHandler().addListener(new ListenerHolder(InitListener.class)); context.getServletHandler().addListener(new ListenerHolder(RequestListener.class)); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Path tempDir = Paths.get(System.getProperty("java.io.tmpdir")); + + Server server = createServer(port, new PathResource(tempDir)); + server.start(); server.dumpStdErr(); server.join(); @@ -71,14 +93,18 @@ public class OneServletContext public static class TestFilter implements Filter { @Override - public void init(FilterConfig filterConfig) throws ServletException + public void init(FilterConfig filterConfig) { - } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (response instanceof HttpServletResponse) + { + HttpServletResponse httpServletResponse = (HttpServletResponse)response; + httpServletResponse.setHeader("X-TestFilter", "true"); + } chain.doFilter(request, response); } @@ -94,6 +120,7 @@ public class OneServletContext @Override public void contextInitialized(ServletContextEvent sce) { + sce.getServletContext().setAttribute("X-Init", "true"); } @Override @@ -105,15 +132,14 @@ public class OneServletContext public static class RequestListener implements ServletRequestListener { @Override - public void requestDestroyed(ServletRequestEvent sre) + public void requestInitialized(ServletRequestEvent sre) { - + sre.getServletRequest().setAttribute("X-ReqListener", "true"); } @Override - public void requestInitialized(ServletRequestEvent sre) + public void requestDestroyed(ServletRequestEvent sre) { - } } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java index 557c4588b65..9a33e10ea69 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java @@ -28,9 +28,10 @@ import org.eclipse.jetty.servlet.ServletContextHandler; public class OneServletContextJmxStats { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - Server server = new Server(8080); + Server server = new Server(port); + // Add JMX tracking to Server server.addBean(new MBeanContainer(ManagementFactory .getPlatformMBeanServer())); @@ -45,6 +46,13 @@ public class OneServletContextJmxStats // Add Connector Statistics tracking to all connectors ServerConnectionStatistics.addToAllConnectors(server); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextWithSession.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextWithSession.java index 35d18c2d848..992bfec14ea 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextWithSession.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextWithSession.java @@ -18,24 +18,29 @@ package org.eclipse.jetty.embedded; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.session.DefaultSessionCache; import org.eclipse.jetty.server.session.NullSessionDataStore; import org.eclipse.jetty.server.session.SessionCache; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.resource.PathResource; +import org.eclipse.jetty.util.resource.Resource; public class OneServletContextWithSession { - public static void main(String[] args) throws Exception + public static Server createServer(int port, Resource baseResource) { - Server server = new Server(8080); + Server server = new Server(port); // Create a ServletContext, with a session handler enabled. ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS); context.setContextPath("/"); - context.setResourceBase(System.getProperty("java.io.tmpdir")); + context.setBaseResource(baseResource); server.setHandler(context); // Access the SessionHandler from the context. @@ -55,6 +60,15 @@ public class OneServletContextWithSession // Servlet to read/set the greeting stored in the session. // Can be accessed using http://localhost:8080/hello context.addServlet(HelloSessionServlet.class, "/"); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Path dir = Paths.get(System.getProperty("user.dir")); + PathResource baseResource = new PathResource(dir); + Server server = createServer(port, baseResource); server.start(); server.dumpStdErr(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java index a69f20f042a..0942600f5da 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java @@ -19,27 +19,20 @@ package org.eclipse.jetty.embedded; import java.io.File; -import java.lang.management.ManagementFactory; -import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.Configurations; import org.eclipse.jetty.webapp.WebAppContext; public class OneWebApp { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { // Create a basic jetty server object that will listen on port 8080. // Note that if you set this to port 0 then a randomly available port // will be assigned that you can either look in the logs for the port, // or programmatically obtain it for use in test cases. - Server server = new Server(8080); - - // Setup JMX - MBeanContainer mbContainer = new MBeanContainer( - ManagementFactory.getPlatformMBeanServer()); - server.addBean(mbContainer); + Server server = new Server(port); // The WebAppContext is the entity that controls the environment in // which a web application lives and breathes. In this example the @@ -56,6 +49,13 @@ public class OneWebApp // A WebAppContext is a ContextHandler as well so it needs to be set to // the server so it is aware of where to send the appropriate requests. server.setHandler(webapp); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); Configurations.setServerDefault(server); @@ -66,7 +66,6 @@ public class OneWebApp // The use of server.join() the will make the current thread join and // wait until the server is done executing. - // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java index 0d51c81d234..e2dcfb79e3d 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java @@ -18,49 +18,45 @@ package org.eclipse.jetty.embedded; -import java.io.File; -import java.lang.management.ManagementFactory; +import java.io.FileNotFoundException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import org.eclipse.jetty.annotations.AnnotationConfiguration; -import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.webapp.WebAppContext; public class OneWebAppWithJsp { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws FileNotFoundException { // Create a basic jetty server object that will listen on port 8080. // Note that if you set this to port 0 then // a randomly available port will be assigned that you can either look // in the logs for the port, // or programmatically obtain it for use in test cases. - Server server = new Server(8080); - - // Setup JMX - MBeanContainer mbContainer = new MBeanContainer( - ManagementFactory.getPlatformMBeanServer()); - server.addBean(mbContainer); + Server server = new Server(port); // The WebAppContext is the entity that controls the environment in - // which a web application lives and - // breathes. In this example the context path is being set to "/" so it + // which a web application lives and breathes. + // In this example the context path is being set to "/" so it // is suitable for serving root context - // requests and then we see it setting the location of the war. A whole - // host of other configurations are + // requests and then we see it setting the location of the war. + // A whole host of other configurations are // available, ranging from configuring to support annotation scanning in - // the webapp (through - // PlusConfiguration) to choosing where the webapp will unpack itself. + // the webapp (through PlusConfiguration), to choosing where + // the webapp will unpack itself. WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); - File warFile = new File( - "jetty-distribution/target/distribution/demo-base/webapps/test.war"); - if (!warFile.exists()) + Path warFile = JettyDistribution.resolve("demo-base/webapps/test.war"); + if (!Files.exists(warFile)) { - throw new RuntimeException("Unable to find WAR File: " + warFile.getAbsolutePath()); + throw new FileNotFoundException(warFile.toString()); } - webapp.setWar(warFile.getAbsolutePath()); + webapp.setWarResource(new PathResource(warFile)); webapp.setExtractWAR(true); // This webapp will use jsps and jstl. We need to enable the @@ -89,19 +85,32 @@ public class OneWebAppWithJsp // its own we register it as a bean with the Jetty server object so it // can be started and stopped according to the lifecycle of the server // itself. + String realmResourceName = "etc/realm.properties"; + ClassLoader classLoader = OneWebAppWithJsp.class.getClassLoader(); + URL realmProps = classLoader.getResource(realmResourceName); + if (realmProps == null) + throw new FileNotFoundException("Unable to find " + realmResourceName); + HashLoginService loginService = new HashLoginService(); loginService.setName("Test Realm"); - loginService.setConfig("examples/embedded/src/test/resources/realm.properties"); + loginService.setConfig(realmProps.toExternalForm()); server.addBean(loginService); - // Start things up! + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + + // Start things up! server.start(); server.dumpStdErr(); // The use of server.join() the will make the current thread join and // wait until the server is done executing. - // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java index ff3412f6cb3..872c57f102b 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java @@ -27,11 +27,13 @@ import org.eclipse.jetty.servlet.ServletHolder; public class ProxyServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { Server server = new Server(); + + // Establish listening connector ServerConnector connector = new ServerConnector(server); - connector.setPort(8888); + connector.setPort(port); server.addConnector(connector); // Setup proxy handler to handle CONNECT methods @@ -45,6 +47,15 @@ public class ProxyServer proxyServlet.setInitParameter("blackList", "www.eclipse.org"); context.addServlet(proxyServlet, "/*"); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + server.start(); + server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/RewriteServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/RewriteServer.java index a74427f0ffd..0347886b29b 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/RewriteServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/RewriteServer.java @@ -18,27 +18,29 @@ package org.eclipse.jetty.embedded; +import java.util.Arrays; + import org.eclipse.jetty.rewrite.RewriteCustomizer; import org.eclipse.jetty.rewrite.handler.CompactPathRule; import org.eclipse.jetty.rewrite.handler.RewriteRegexRule; -import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; public class RewriteServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - Server server = new Server(8080); - - HttpConfiguration config = server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration(); + Server server = new Server(port); RewriteCustomizer rewrite = new RewriteCustomizer(); - config.addCustomizer(rewrite); rewrite.addRule(new CompactPathRule()); rewrite.addRule(new RewriteRegexRule("(.*)foo(.*)", "$1FOO$2")); + Arrays.stream(server.getConnectors()) + .forEach((connector) -> connector.getConnectionFactory(HttpConnectionFactory.class) + .getHttpConfiguration().addCustomizer(rewrite)); + ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS); context.setContextPath("/"); @@ -46,6 +48,14 @@ public class RewriteServer context.addServlet(DumpServlet.class, "/*"); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + server.start(); server.join(); } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java index aaa0c806a5e..1f339b33fa2 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.embedded; +import java.io.FileNotFoundException; +import java.net.URL; import java.util.Collections; import org.eclipse.jetty.security.ConstraintMapping; @@ -30,13 +32,13 @@ import org.eclipse.jetty.util.security.Constraint; public class SecuredHelloHandler { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws FileNotFoundException { // Create a basic jetty server object that will listen on port 8080. // Note that if you set this to port 0 then a randomly available port // will be assigned that you can either look in the logs for the port, // or programmatically obtain it for use in test cases. - Server server = new Server(8080); + Server server = new Server(port); // Since this example is for our test webapp, we need to setup a // LoginService so this shows how to create a very simple hashmap based @@ -46,8 +48,14 @@ public class SecuredHelloHandler // started and stopped according to the lifecycle of the server itself. // In this example the name can be whatever you like since we are not // dealing with webapp realms. + String realmResourceName = "etc/realm.properties"; + ClassLoader classLoader = SecuredHelloHandler.class.getClassLoader(); + URL realmProps = classLoader.getResource(realmResourceName); + if (realmProps == null) + throw new FileNotFoundException("Unable to find " + realmResourceName); + LoginService loginService = new HashLoginService("MyRealm", - "src/test/resources/realm.properties"); + realmProps.toExternalForm()); server.addBean(loginService); // A security handler is a jetty handler that secures content behind a @@ -68,7 +76,7 @@ public class SecuredHelloHandler constraint.setRoles(new String[]{"user", "admin"}); // Binds a url pattern with the previously created constraint. The roles - // for this constraing mapping are mined from the Constraint itself + // for this constraint mapping are mined from the Constraint itself // although methods exist to declare and bind roles separately as well. ConstraintMapping mapping = new ConstraintMapping(); mapping.setPathSpec("/*"); @@ -92,13 +100,19 @@ public class SecuredHelloHandler // chain the hello handler into the security handler security.setHandler(hh); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + // Start things up! server.start(); // The use of server.join() the will make the current thread join and // wait until the server is done executing. - // See - // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java index 7b148e0c703..869e4214f2a 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java @@ -19,6 +19,9 @@ package org.eclipse.jetty.embedded; import java.io.File; +import java.io.FileNotFoundException; +import java.net.URL; +import javax.naming.NamingException; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.plus.jndi.EnvEntry; @@ -36,10 +39,10 @@ import org.eclipse.jetty.webapp.WebAppContext; */ public class ServerWithAnnotations { - public static final void main(String[] args) throws Exception + public static Server createServer(int port) throws NamingException, FileNotFoundException { // Create the server - final Server server = new Server(8080); + Server server = new Server(port); // Create a WebApp WebAppContext webapp = new WebAppContext(); @@ -60,7 +63,7 @@ public class ServerWithAnnotations new Transaction(new com.acme.MockUserTransaction()); // Define an env entry with webapp scope. - // THIS ENTRY IS OVERRIDEN BY THE ENTRY IN jetty-env.xml + // THIS ENTRY IS OVERRIDDEN BY THE ENTRY IN jetty-env.xml new EnvEntry(webapp, "maxAmount", 100d, true); // Register a mock DataSource scoped to the webapp @@ -70,10 +73,23 @@ public class ServerWithAnnotations server.addBean(new NamingDump()); // Configure a LoginService + String realmResourceName = "etc/realm.properties"; + ClassLoader classLoader = ServerWithAnnotations.class.getClassLoader(); + URL realmProps = classLoader.getResource(realmResourceName); + if (realmProps == null) + throw new FileNotFoundException("Unable to find " + realmResourceName); + HashLoginService loginService = new HashLoginService(); loginService.setName("Test Realm"); - loginService.setConfig("examples/embedded/src/test/resources/realm.properties"); + loginService.setConfig(realmProps.toExternalForm()); server.addBean(loginService); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.dumpStdErr(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java index 95b38e56a18..c3c14df0b49 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.embedded; import java.lang.management.ManagementFactory; +import java.net.MalformedURLException; import javax.management.remote.JMXServiceURL; import org.eclipse.jetty.jmx.ConnectorServer; @@ -26,17 +27,16 @@ import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.Server; /** - * The simplest possible Jetty server. + * A Jetty Server with JMX enabled for remote connections */ public class ServerWithJMX { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws MalformedURLException { - // === jetty-jmx.xml === + Server server = new Server(port); + MBeanContainer mbContainer = new MBeanContainer( ManagementFactory.getPlatformMBeanServer()); - - Server server = new Server(8080); server.addBean(mbContainer); ConnectorServer jmx = new ConnectorServer( @@ -48,6 +48,14 @@ public class ServerWithJMX "org.eclipse.jetty.jmx:name=rmiconnectorserver"); server.addBean(jmx); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); + server.start(); server.dumpStdErr(); server.join(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java index 456d639007d..6e3b2854bf6 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java @@ -18,12 +18,14 @@ package org.eclipse.jetty.embedded; -import java.io.File; +import java.nio.file.Path; import java.util.Properties; +import javax.naming.NamingException; import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.webapp.WebAppContext; /** @@ -31,18 +33,16 @@ import org.eclipse.jetty.webapp.WebAppContext; */ public class ServerWithJNDI { - public static void main(String[] args) throws Exception + public static Server createServer(int port) throws NamingException { - // Create the server - Server server = new Server(8080); + Server server = new Server(port); // Create a WebApp WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); - File warFile = new File( - "../../jetty-distribution/target/distribution/demo-base/webapps/test-jndi.war"); - webapp.setWar(warFile.getAbsolutePath()); + Path testJndiWar = JettyDistribution.resolve("demo-base/webapps/test-jndi.war"); + webapp.setWarResource(new PathResource(testJndiWar)); server.setHandler(webapp); // Enable parsing of jndi-related parts of web.xml and jetty-env.xml @@ -74,7 +74,7 @@ public class ServerWithJNDI // Note that the last arg of "true" means that this definition for // "wiggle" would override an entry of the // same name in web.xml - new org.eclipse.jetty.plus.jndi.EnvEntry(webapp, "wiggle", 100D, true); + new org.eclipse.jetty.plus.jndi.EnvEntry(webapp, "wiggle", 100d, true); // Register a reference to a mail service scoped to the webapp. // This must be linked to the webapp by an entry in web.xml: @@ -84,7 +84,8 @@ public class ServerWithJNDI // Container // // At runtime the webapp accesses this as java:comp/env/mail/Session - org.eclipse.jetty.jndi.factories.MailSessionReference mailref = new org.eclipse.jetty.jndi.factories.MailSessionReference(); + org.eclipse.jetty.jndi.factories.MailSessionReference mailref = + new org.eclipse.jetty.jndi.factories.MailSessionReference(); mailref.setUser("CHANGE-ME"); mailref.setPassword("CHANGE-ME"); Properties props = new Properties(); @@ -106,6 +107,13 @@ public class ServerWithJNDI // java:comp/env/jdbc/mydatasource new org.eclipse.jetty.plus.jndi.Resource( webapp, "jdbc/mydatasource", new com.acme.MockDataSource()); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); server.join(); diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java index a48235c55d5..3bdb6d4fa27 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java @@ -25,11 +25,20 @@ import org.eclipse.jetty.server.Server; */ public class SimplestServer { + public static Server createServer(int port) + { + Server server = new Server(port); + // This has a connector listening on port specified + // and no handlers, meaning all requests will result + // in a 404 response + return server; + } + public static void main(String[] args) throws Exception { - Server server = new Server(8080); + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); - server.dumpStdErr(); server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java index 4a681baa93e..f8b46272b56 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java @@ -18,16 +18,14 @@ package org.eclipse.jetty.embedded; -import java.io.File; +import java.nio.file.Paths; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; /** @@ -37,59 +35,68 @@ import org.eclipse.jetty.util.resource.Resource; */ public class SplitFileServer { - public static void main(String[] args) throws Exception + public static Server createServer(int port, Resource baseResource0, Resource baseResource1) { // Create the Server object and a corresponding ServerConnector and then // set the port for the connector. In this example the server will - // listen on port 8090. If you set this to port 0 then when the server + // listen on port 8080. If you set this to port 0 then when the server // has been started you can called connector.getLocalPort() to // programmatically get the port the server started on. Server server = new Server(); ServerConnector connector = new ServerConnector(server); - connector.setPort(8090); - server.setConnectors(new Connector[]{connector}); + connector.setPort(port); + server.addConnector(connector); // Create a Context Handler and ResourceHandler. The ContextHandler is // getting set to "/" path but this could be anything you like for - // builing out your url. Note how we are setting the ResourceBase using + // building out your url. Note how we are setting the ResourceBase using // our jetty maven testing utilities to get the proper resource // directory, you needn't use these, you simply need to supply the paths // you are looking to serve content from. ResourceHandler rh0 = new ResourceHandler(); + rh0.setDirectoriesListed(false); ContextHandler context0 = new ContextHandler(); context0.setContextPath("/"); - File dir0 = MavenTestingUtils.getTestResourceDir("dir0"); - context0.setBaseResource(Resource.newResource(dir0)); + context0.setBaseResource(baseResource0); context0.setHandler(rh0); // Rinse and repeat the previous item, only specifying a different // resource base. ResourceHandler rh1 = new ResourceHandler(); + rh1.setDirectoriesListed(false); ContextHandler context1 = new ContextHandler(); context1.setContextPath("/"); - File dir1 = MavenTestingUtils.getTestResourceDir("dir1"); - context1.setBaseResource(Resource.newResource(dir1)); + context1.setBaseResource(baseResource1); context1.setHandler(rh1); // Create a ContextHandlerCollection and set the context handlers to it. // This will let jetty process urls against the declared contexts in // order to match up content. - ContextHandlerCollection contexts = new ContextHandlerCollection(); - contexts.setHandlers(new Handler[]{context0, context1}); - + ContextHandlerCollection contexts = new ContextHandlerCollection( + context0, context1 + ); server.setHandler(contexts); + return server; + } - // Start things up! - server.start(); + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Resource resource0 = new PathResource(Paths.get("src/test/resources/dir0")); + Resource resource1 = new PathResource(Paths.get("src/test/resources/dir1")); + + Server server = createServer(port, resource0, resource1); // Dump the server state - System.out.println(server.dump()); + server.setDumpAfterStart(true); + + // Start things up! + server.start(); // The use of server.join() the will make the current thread join and // wait until the server is done executing. - // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join() server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java index 473fae972ab..d8d88c58f05 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java @@ -46,25 +46,37 @@ public class WebSocketJsrServer } } - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - final Server server = new Server(8080); + Server server = new Server(port); HandlerList handlers = new HandlerList(); - ServletContextHandler contextHandler = new ServletContextHandler( - ServletContextHandler.SESSIONS); - contextHandler.setContextPath("/"); - handlers.addHandler(contextHandler); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + handlers.addHandler(context); + + // Enable javax.websocket configuration for the context + JavaxWebSocketServletContainerInitializer.configure(context, + (servletContext, serverContainer) -> + { + // Add your websocket to the javax.websocket.server.ServerContainer + serverContainer.addEndpoint(EchoJsrSocket.class); + } + ); + handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - // Enable javax.websocket configuration for the context - JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> - container.addEndpoint(EchoJsrSocket.class)); + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); - contextHandler.dumpStdErr(); server.join(); } } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java index 40fc6465595..395ec41bce4 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java @@ -20,13 +20,13 @@ package org.eclipse.jetty.embedded; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; /** * Example of setting up a Jetty WebSocket server @@ -61,20 +61,29 @@ public class WebSocketServer } } - public static void main(String[] args) throws Exception + public static Server createServer(int port) { - Server server = new Server(8080); + Server server = new Server(port); - ServletContextHandler context = new ServletContextHandler( - ServletContextHandler.SESSIONS); + ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); server.setHandler(context); // Add the echo socket servlet to the /echo path map - context.addServlet(new ServletHolder(EchoServlet.class), "/echo"); + context.addServlet(EchoServlet.class, "/echo"); + + // Configure context to support WebSocket + JettyWebSocketServletContainerInitializer.configure(context, null); + + return server; + } + + public static void main(String[] args) throws Exception + { + int port = ExampleUtil.getPort(args, "jetty.http.port", 8080); + Server server = createServer(port); server.start(); - context.dumpStdErr(); server.join(); } } diff --git a/examples/embedded/src/main/resources/content.jar b/examples/embedded/src/main/other/content.jar similarity index 100% rename from examples/embedded/src/main/resources/content.jar rename to examples/embedded/src/main/other/content.jar diff --git a/examples/embedded/src/main/resources/etc/keystore b/examples/embedded/src/main/resources/etc/keystore new file mode 100644 index 00000000000..d6592f95ee9 Binary files /dev/null and b/examples/embedded/src/main/resources/etc/keystore differ diff --git a/examples/embedded/src/main/resources/etc/keystore.pkf b/examples/embedded/src/main/resources/etc/keystore.pkf new file mode 100644 index 00000000000..443818e87df --- /dev/null +++ b/examples/embedded/src/main/resources/etc/keystore.pkf @@ -0,0 +1,20 @@ +Bag Attributes + friendlyName: jetty + localKeyID: 54 69 6D 65 20 31 34 32 33 31 39 38 30 39 33 31 31 35 +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIPh4Q0t4xklXTzX +N2VAb47r5n7idAupp4CTNEhhT6lS70iA+A8i4+0lSEHWAogvd9jl3H7SvScr30QM +4ieC0JCGSOwGc8f+yqKrO56PPd5OuqW380BJ0r74jJczU9CcsuavHD7e6mRLUnmj +xM20NSxrcicMiPUHY1mJZtN9swtxAgMBAAECgYADS9P6Jll0uXBZIu/pgfDH27GJ +HlPULstW9VbrMDNzgfUlFMQebLrRpIrnyleJ29Xc//HA4beEkR4lb0T/w88+pEkt +7fhYeqRLPIfpDOgzloynnsoPcd8f/PypbimQrNLmBiG1178nVcy4Yoh5lYVIJwtU +3VriqDlvAfTLrrx8AQJBAMLWuh27Hb8xs3LRg4UD7hcv8tJejstm08Y+czRz7cO0 +RENa3aDjGFSegc+IUfdez7BP8uDw+PwE+jybmTvaliECQQCtR/anCY1WS28/bKvy +lmIwoI15eraBdVFkN0Hfxh+9PfR3rMD5uyvukT5GgTtY/XxADyafSTaipDJiZHJI +EitRAkBjeCBYYVjUbVlBuvi8Bb+dktsSzzdzXDGtueAy3SR7jyJyiIcxRf775Fg9 +TUkbUwoQ5yAF+sACWcAvBPj796JBAkAEZEeHEkHnxv+pztpIyrDwZJFRW9/WRh/q +90+PGVlilXhltBYr/idt43Z9mPblGX+VrAyhitx8oMa6IauX0gYRAkEAgnyVeXrD +jDLUZRA3P8Gu27k1k6GjbTYiUz3HKCz2/6+MZ2MK2qqwafgqocji029Q6dHdPD7a +4QnRlvraUnyQLA== +-----END PRIVATE KEY----- diff --git a/examples/embedded/src/main/resources/etc/realm.properties b/examples/embedded/src/main/resources/etc/realm.properties new file mode 100644 index 00000000000..f4b3490e910 --- /dev/null +++ b/examples/embedded/src/main/resources/etc/realm.properties @@ -0,0 +1,20 @@ +# +# This file defines users passwords and roles for a HashUserRealm +# +# The format is +# : [, ...] +# +# Passwords may be clear text, obfuscated or checksummed. The class +# org.eclipse.jetty.util.security.Password should be used to generate obfuscated +# passwords or password checksums +# +# If DIGEST Authentication is used, the password must be in a recoverable +# format, either plain text or OBF:. +# +jetty:MD5:164c88b302622e17050af52c89945d44,user +admin:CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user +other:OBF:1xmk1w261u9r1w1c1xmq,user +plain:plain,user +user:password,user +# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password +digest:MD5:6e120743ad67abfbc385bc2bb754e297,user diff --git a/examples/embedded/src/main/resources/exampleserver.xml b/examples/embedded/src/main/resources/exampleserver.xml index 6dbbd6a07f5..138bdfd53b0 100644 --- a/examples/embedded/src/main/resources/exampleserver.xml +++ b/examples/embedded/src/main/resources/exampleserver.xml @@ -8,7 +8,9 @@ - 8080 + + + diff --git a/examples/embedded/src/main/resources/fileserver.xml b/examples/embedded/src/main/resources/fileserver.xml index 8562cb00071..0f9be210fce 100644 --- a/examples/embedded/src/main/resources/fileserver.xml +++ b/examples/embedded/src/main/resources/fileserver.xml @@ -7,7 +7,9 @@ - 8080 + + + @@ -22,7 +24,9 @@ index.html - . + + + diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/AbstractEmbeddedTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/AbstractEmbeddedTest.java new file mode 100644 index 00000000000..80afde9eafb --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/AbstractEmbeddedTest.java @@ -0,0 +1,64 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public abstract class AbstractEmbeddedTest +{ + public HttpClient client; + + @BeforeEach + public void startClient() throws Exception + { + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + sslContextFactory.setTrustAll(true); + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSelectors(1); + clientConnector.setSslContextFactory(sslContextFactory); + + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + + clientConnector.setExecutor(clientThreads); + + client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); + client.start(); + } + + @AfterEach + public void stopClient() throws Exception + { + client.stop(); + } + + protected void dumpResponseHeaders(ContentResponse response) + { + System.out.printf("%s %s %s%n", response.getVersion(), response.getStatus(), response.getReason()); + System.out.println(response.getHeaders()); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java new file mode 100644 index 00000000000..367b521b28e --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ExampleServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ExampleServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } + + @Test + public void testGetEcho() throws Exception + { + URI uri = server.getURI().resolve("/echo/a/greeting"); + + String postBody = "Greetings from " + ExampleServerTest.class; + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.POST) + .content(new StringContentProvider(postBody)) + .send(); + + // Check the response status code + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString(postBody)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerXmlTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerXmlTest.java new file mode 100644 index 00000000000..23b28198e05 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerXmlTest.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ExampleServerXmlTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ExampleServerXml.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FastFileServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FastFileServerTest.java new file mode 100644 index 00000000000..861ad3c8814 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FastFileServerTest.java @@ -0,0 +1,92 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.io.BufferedWriter; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@ExtendWith(WorkDirExtension.class) +public class FastFileServerTest extends AbstractEmbeddedTest +{ + private static final String TEXT_CONTENT = "I am an old man and I have known a great " + + "many troubles, but most of them never happened. - Mark Twain"; + public WorkDir workDir; + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path baseDir = workDir.getEmptyPathDir(); + + Path textFile = baseDir.resolve("simple.txt"); + try (BufferedWriter writer = Files.newBufferedWriter(textFile, UTF_8)) + { + writer.write(TEXT_CONTENT); + } + + server = FastFileServer.createServer(0, baseDir.toFile()); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetSimpleText() throws Exception + { + URI uri = server.getURI().resolve("/simple.txt"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + HttpFields responseHeaders = response.getHeaders(); + + assertThat("Content-Type", responseHeaders.get("Content-Type"), is("text/plain")); + assertThat("Content-Length", responseHeaders.getLongField("Content-Length"), + is((long)TEXT_CONTENT.length())); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response body", responseBody, is(TEXT_CONTENT)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerTest.java new file mode 100644 index 00000000000..14ac4e5a53d --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerTest.java @@ -0,0 +1,93 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.io.BufferedWriter; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.resource.PathResource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@ExtendWith(WorkDirExtension.class) +public class FileServerTest extends AbstractEmbeddedTest +{ + private static final String TEXT_CONTENT = "I am an old man and I have known a great " + + "many troubles, but most of them never happened. - Mark Twain"; + public WorkDir workDir; + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path baseDir = workDir.getEmptyPathDir(); + + Path textFile = baseDir.resolve("simple.txt"); + try (BufferedWriter writer = Files.newBufferedWriter(textFile, UTF_8)) + { + writer.write(TEXT_CONTENT); + } + + server = FileServer.createServer(0, new PathResource(baseDir)); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetSimpleText() throws Exception + { + URI uri = server.getURI().resolve("/simple.txt"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + HttpFields responseHeaders = response.getHeaders(); + + assertThat("Content-Type", responseHeaders.get("Content-Type"), is("text/plain")); + assertThat("Content-Length", responseHeaders.getLongField("Content-Length"), + is((long)TEXT_CONTENT.length())); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response body", responseBody, is(TEXT_CONTENT)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerXmlTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerXmlTest.java new file mode 100644 index 00000000000..813879e36c4 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/FileServerXmlTest.java @@ -0,0 +1,92 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.io.BufferedWriter; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@ExtendWith(WorkDirExtension.class) +public class FileServerXmlTest extends AbstractEmbeddedTest +{ + private static final String TEXT_CONTENT = "I am an old man and I have known a great " + + "many troubles, but most of them never happened. - Mark Twain"; + public WorkDir workDir; + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path baseDir = workDir.getEmptyPathDir(); + + Path textFile = baseDir.resolve("simple.txt"); + try (BufferedWriter writer = Files.newBufferedWriter(textFile, UTF_8)) + { + writer.write(TEXT_CONTENT); + } + + server = FileServerXml.createServer(0, baseDir); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetSimpleText() throws Exception + { + URI uri = server.getURI().resolve("/simple.txt"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + HttpFields responseHeaders = response.getHeaders(); + + assertThat("Content-Type", responseHeaders.get("Content-Type"), is("text/plain")); + assertThat("Content-Length", responseHeaders.getLongField("Content-Length"), + is((long)TEXT_CONTENT.length())); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response body", responseBody, is(TEXT_CONTENT)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/JarServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/JarServerTest.java new file mode 100644 index 00000000000..4e4bf368547 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/JarServerTest.java @@ -0,0 +1,83 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class JarServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = JarServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetDir0Test0() throws Exception + { + URI uri = server.getURI().resolve("/dir0/test0.txt"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("test0")); + } + + @Test + public void testGetDir1Test1() throws Exception + { + URI uri = server.getURI().resolve("/dir1/test1.txt"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("test1")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/LikeJettyXmlTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/LikeJettyXmlTest.java new file mode 100644 index 00000000000..445ebe9d4db --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/LikeJettyXmlTest.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.Map; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class LikeJettyXmlTest extends AbstractEmbeddedTest +{ + private Server server; + private URI serverPlainUri; + private URI serverSslUri; + + @BeforeEach + public void startServer() throws Exception + { + assumeTrue(JettyDistribution.DISTRIBUTION != null, "jetty-distribution not found"); + + server = LikeJettyXml.createServer(0, 0, false); + server.start(); + + Map ports = ServerUtil.fixDynamicPortConfigurations(server); + + // Establish base URI's that use "localhost" to prevent tripping over + // the "REMOTE ACCESS" warnings in demo-base + serverPlainUri = URI.create("http://localhost:" + ports.get("plain") + "/"); + serverSslUri = URI.create("https://localhost:" + ports.get("secure") + "/"); + } + + @AfterEach + public void stopServer() throws Exception + { + LifeCycle.stop(server); + } + + @Test + public void testGetTest() throws Exception + { + URI uri = serverPlainUri.resolve("/test/"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } + + @Test + public void testGetTestSsl() throws Exception + { + URI uri = serverSslUri.resolve("/test/"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyConnectorsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyConnectorsTest.java new file mode 100644 index 00000000000..0a19dac2859 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyConnectorsTest.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.Map; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ManyConnectorsTest extends AbstractEmbeddedTest +{ + private Server server; + private URI serverPlainUri; + private URI serverSslUri; + + @BeforeEach + public void startServer() throws Exception + { + server = ManyConnectors.createServer(0, 0); + server.start(); + + Map ports = ServerUtil.fixDynamicPortConfigurations(server); + + // Establish base URI's that use "localhost" to prevent tripping over + // the "REMOTE ACCESS" warnings in demo-base + serverPlainUri = URI.create("http://localhost:" + ports.get("plain") + "/"); + serverSslUri = URI.create("https://localhost:" + ports.get("secure") + "/"); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testPlainGetHello() throws Exception + { + URI uri = serverPlainUri.resolve("/hello"); + + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } + + @Test + public void testSecureGetHello() throws Exception + { + URI uri = serverSslUri.resolve("/hello"); + + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyContextsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyContextsTest.java new file mode 100644 index 00000000000..bd439aee552 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyContextsTest.java @@ -0,0 +1,117 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ManyContextsTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ManyContexts.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetRootHello() throws Exception + { + URI uri = server.getURI().resolve("/"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Root Hello")); + } + + @Test + public void testGetFrenchHello() throws Exception + { + URI uri = server.getURI().resolve("/fr"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Bonjour")); + } + + @Test + public void testGetItalianGoodMorning() throws Exception + { + URI uri = server.getURI().resolve("/it"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Buongiorno")); + } + + @Test + public void testGetVirtualHostHello() throws Exception + { + int port = server.getURI().getPort(); + + URI uri = URI.create("http://127.0.0.2:" + port + "/"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Virtual Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyHandlersTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyHandlersTest.java new file mode 100644 index 00000000000..a6641d7c3d7 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyHandlersTest.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.Map; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.ajax.JSON; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ManyHandlersTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ManyHandlers.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetParams() throws Exception + { + URI uri = server.getURI().resolve("/params?a=b&foo=bar"); + + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .header(HttpHeader.ACCEPT_ENCODING, "gzip") + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test gzip + // Test that Gzip was used to produce the response + String contentEncoding = response.getHeaders().get(HttpHeader.CONTENT_ENCODING); + assertThat("Content-Encoding", contentEncoding, containsString("gzip")); + + // test response content + String responseBody = response.getContentAsString(); + Object jsonObj = JSON.parse(responseBody); + Map jsonMap = (Map)jsonObj; + assertThat("Response JSON keys.size", jsonMap.keySet().size(), is(2)); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .header(HttpHeader.ACCEPT_ENCODING, "gzip") + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test gzip + // Test that Gzip was used to produce the response + String contentEncoding = response.getHeaders().get(HttpHeader.CONTENT_ENCODING); + assertThat("Content-Encoding", contentEncoding, containsString("gzip")); + + // test expected header from wrapper + String welcome = response.getHeaders().get("X-Welcome"); + assertThat("X-Welcome header", welcome, containsString("Greetings from WelcomeWrapHandler")); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyServletContextsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyServletContextsTest.java new file mode 100644 index 00000000000..6cc21115957 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ManyServletContextsTest.java @@ -0,0 +1,115 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ManyServletContextsTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ManyServletContexts.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } + + @Test + public void testGetItalianHello() throws Exception + { + URI uri = server.getURI().resolve("/it/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Ciao")); + } + + @Test + public void testGetFrenchHello() throws Exception + { + URI uri = server.getURI().resolve("/fr/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Bonjour")); + } + + @Test + public void testGetOtherYo() throws Exception + { + URI uri = server.getURI().resolve("/other/hello.yo"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("YO!")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/MinimalServletsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/MinimalServletsTest.java new file mode 100644 index 00000000000..18354a1f07c --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/MinimalServletsTest.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class MinimalServletsTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = MinimalServlets.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneConnectorTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneConnectorTest.java new file mode 100644 index 00000000000..d700bb0272b --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneConnectorTest.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class OneConnectorTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = OneConnector.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneContextTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneContextTest.java new file mode 100644 index 00000000000..9d12b7f1a36 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneContextTest.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class OneContextTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = OneContext.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneHandlerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneHandlerTest.java new file mode 100644 index 00000000000..920e24c59f4 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneHandlerTest.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class OneHandlerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = OneHandler.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextJmxStatsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextJmxStatsTest.java new file mode 100644 index 00000000000..ae627ca6c9c --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextJmxStatsTest.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectInstance; +import javax.management.ObjectName; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ConnectionStatistics; +import org.eclipse.jetty.jmx.MBeanContainer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.opentest4j.AssertionFailedError; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +@ExtendWith(WorkDirExtension.class) +public class OneServletContextJmxStatsTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = OneServletContextJmxStats.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetDumpViaPathInfo() throws Exception + { + URI uri = server.getURI().resolve("/dump/something"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("DumpServlet"), + containsString("servletPath=/dump"), + containsString("pathInfo=/something") + ) + ); + } + + @Test + public void testJmxConnectStatsPresent() throws Exception + { + MBeanContainer mbeanContainer = server.getBean(MBeanContainer.class); + MBeanServer mbeanServer = mbeanContainer.getMBeanServer(); + + String domain = ConnectionStatistics.class.getPackage().getName(); + Set mbeanNames = mbeanServer.queryNames(ObjectName.getInstance(domain + ":type=connectionstatistics,*"), null); + ObjectName connStatsName = mbeanNames.stream().findFirst().orElseThrow(AssertionFailedError::new); + ObjectInstance mbeanConnStats = mbeanServer.getObjectInstance(connStatsName); + Number connections = (Number)mbeanServer.getAttribute(connStatsName, "connections"); + assertThat("stats[connections]", connections, is(notNullValue())); + assertThat("stats[connections]", connections.longValue(), greaterThanOrEqualTo(0L)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextTest.java new file mode 100644 index 00000000000..5ed2433a912 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextTest.java @@ -0,0 +1,158 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.io.BufferedWriter; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.resource.PathResource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +@ExtendWith(WorkDirExtension.class) +public class OneServletContextTest extends AbstractEmbeddedTest +{ + private static final String TEXT_CONTENT = "The secret of getting ahead is getting started. - Mark Twain"; + public WorkDir workDir; + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path baseDir = workDir.getEmptyPathDir(); + + Path textFile = baseDir.resolve("simple.txt"); + try (BufferedWriter writer = Files.newBufferedWriter(textFile, UTF_8)) + { + writer.write(TEXT_CONTENT); + } + + server = OneServletContext.createServer(0, new PathResource(baseDir)); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/hello/there"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hello")); + } + + @Test + public void testGetDumpViaPathInfo() throws Exception + { + URI uri = server.getURI().resolve("/dump/something"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("DumpServlet"), + containsString("servletPath=/dump"), + containsString("pathInfo=/something") + ) + ); + } + + @Test + public void testGetDumpSuffix() throws Exception + { + URI uri = server.getURI().resolve("/another.dump"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("DumpServlet"), + containsString("servletPath=/another.dump"), + containsString("pathInfo=null") + ) + ); + } + + @Test + public void testGetTestDumpSuffix() throws Exception + { + URI uri = server.getURI().resolve("/test/another.dump"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + String filterResponseHeader = response.getHeaders().get("X-TestFilter"); + assertThat("X-TestFilter header", filterResponseHeader, is("true")); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("DumpServlet"), + containsString("servletPath=/test/another.dump"), + containsString("pathInfo=null"), + containsString("request.attribute[X-ReqListener]=true"), + containsString("servletContext.attribute[X-Init]=true") + ) + ); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextWithSessionTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextWithSessionTest.java new file mode 100644 index 00000000000..0b96d4dedbc --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneServletContextWithSessionTest.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.io.BufferedWriter; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.resource.PathResource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +@ExtendWith(WorkDirExtension.class) +public class OneServletContextWithSessionTest extends AbstractEmbeddedTest +{ + private static final String TEXT_CONTENT = "Do the right thing. It will gratify some people and astonish the rest. - Mark Twain"; + public WorkDir workDir; + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path baseDir = workDir.getEmptyPathDir(); + + Path textFile = baseDir.resolve("simple.txt"); + try (BufferedWriter writer = Files.newBufferedWriter(textFile, UTF_8)) + { + writer.write(TEXT_CONTENT); + } + + server = OneServletContextWithSession.createServer(0, new PathResource(baseDir)); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetHello() throws Exception + { + URI uri = server.getURI().resolve("/"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + String setCookieValue = response.getHeaders().get(HttpHeader.SET_COOKIE); + assertThat("Set-Cookie value", setCookieValue, containsString("JSESSIONID=")); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("session.getId() = "), + containsString("session.isNew() = true") + ) + ); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java new file mode 100644 index 00000000000..7f9eae15003 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java @@ -0,0 +1,71 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class OneWebAppTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + assumeTrue(JettyDistribution.DISTRIBUTION != null, "jetty-distribution not found"); + + server = OneWebApp.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + LifeCycle.stop(server); + } + + @Test + public void testGetAsyncRest() throws Exception + { + URI uri = server.getURI().resolve("/testAsync?items=mouse,beer,gnome"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Asynchronous: mouse,beer,gnome")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppWithJspTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppWithJspTest.java new file mode 100644 index 00000000000..9f858925596 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppWithJspTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class OneWebAppWithJspTest extends AbstractEmbeddedTest +{ + private Server server; + private URI serverLocalUri; + + @BeforeEach + public void startServer() throws Exception + { + assumeTrue(JettyDistribution.DISTRIBUTION != null, "jetty-distribution not found"); + + server = OneWebAppWithJsp.createServer(0); + server.start(); + + // Use URI based on "localhost" to get past "REMOTE ACCESS!" protection of demo war + serverLocalUri = URI.create("http://localhost:" + server.getURI().getPort() + "/"); + } + + @AfterEach + public void stopServer() throws Exception + { + LifeCycle.stop(server); + } + + @Test + public void testGetDumpInfo() throws Exception + { + URI uri = serverLocalUri.resolve("/dump/info"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("getProtocol: HTTP/1.1")); + } + + @Test + public void testGetJspExpr() throws Exception + { + URI uri = serverLocalUri.resolve("/jsp/expr.jsp?A=1"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + String userAgent = client.getUserAgentField().getValue(); + assertThat("Response Content", responseBody, containsString("" + userAgent + "")); + } + + @Test + public void testGetJstlExpr() throws Exception + { + URI uri = serverLocalUri.resolve("/jsp/jstl.jsp"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("

JSTL Example

")); + for (int i = 1; i <= 10; i++) + { + assertThat("Reponse content (counting)", responseBody, containsString("" + i)); + } + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ProxyServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ProxyServerTest.java new file mode 100644 index 00000000000..279cb339347 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ProxyServerTest.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class ProxyServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ProxyServer.createServer(0); + server.start(); + + URI uri = server.getURI(); + client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", uri.getPort())); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Tag("external") + @Test + public void testGetProxiedRFC() throws Exception + { + URI uri = URI.create("https://tools.ietf.org/rfc/rfc7230.txt"); + + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/RewriteServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/RewriteServerTest.java new file mode 100644 index 00000000000..775f550208c --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/RewriteServerTest.java @@ -0,0 +1,83 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class RewriteServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = RewriteServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetRewriteFooInName() throws Exception + { + URI uri = server.getURI().resolve("/do-be-foo-be-do"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("requestURI=/do-be-FOO-be-do")); + } + + @Test + public void testGetRewriteFooInPath() throws Exception + { + URI uri = server.getURI().resolve("/do/be/foo/be/do.it"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("requestURI=/do/be/FOO/be/do.it")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SecuredHelloHandlerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SecuredHelloHandlerTest.java new file mode 100644 index 00000000000..892330b392c --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SecuredHelloHandlerTest.java @@ -0,0 +1,85 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.Base64; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class SecuredHelloHandlerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = SecuredHelloHandler.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetWithoutAuth() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.UNAUTHORIZED_401)); + + // dumpResponseHeaders(response); + } + + @Test + public void testGetWithAuth() throws Exception + { + URI uri = server.getURI().resolve("/hello"); + + String authEncoded = Base64.getEncoder().encodeToString("user:password".getBytes(UTF_8)); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.GET) + .header(HttpHeader.AUTHORIZATION, "Basic " + authEncoded) + .send(); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("

Hello World

")); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerUtil.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerUtil.java new file mode 100644 index 00000000000..ad41662e45c --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerUtil.java @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ServerUtil +{ + /** + * Fix the HttpConfiguration entries for securePort after the dynamic ports have been bound. + * + * @param server the server to correct. + */ + public static Map fixDynamicPortConfigurations(Server server) + { + // Fix ports in HttpConfiguration (since we are using dynamic port assignment for this testcase) + HttpConfiguration plainHttpConfiguration = null; + HttpConfiguration secureHttpConfiguration = null; + int plainHttpPort = -1; + int secureHttpPort = -1; + + for (Connector connector : server.getConnectors()) + { + if (connector instanceof ServerConnector) + { + ServerConnector serverConnector = (ServerConnector)connector; + SslConnectionFactory sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class); + HttpConnectionFactory httpConnectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class); + if (httpConnectionFactory != null) + { + HttpConfiguration configuration = httpConnectionFactory.getHttpConfiguration(); + if (sslConnectionFactory != null) + { + secureHttpConfiguration = configuration; + secureHttpPort = serverConnector.getLocalPort(); + } + else + { + plainHttpConfiguration = configuration; + plainHttpPort = serverConnector.getLocalPort(); + } + } + } + } + + assertNotNull(plainHttpConfiguration, "Plain HTTP Configuration"); + assertNotEquals(plainHttpPort, -1, "Dynamic Plain HTTP Port"); + + assertNotNull(secureHttpConfiguration, "Secure HTTP Configuration"); + assertNotEquals(secureHttpPort, -1, "Dynamic Secure Port"); + + plainHttpConfiguration.setSecurePort(secureHttpPort); + secureHttpConfiguration.setSecurePort(secureHttpPort); + + Map ports = new HashMap<>(); + ports.put("plain", plainHttpPort); + ports.put("secure", secureHttpPort); + + return ports; + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithAnnotationsTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithAnnotationsTest.java new file mode 100644 index 00000000000..3662334ea26 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithAnnotationsTest.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class ServerWithAnnotationsTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + assumeTrue(JettyDistribution.DISTRIBUTION != null, "jetty-distribution not found"); + + server = ServerWithAnnotations.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + LifeCycle.stop(server); + } + + @Test + public void testGetTest() throws Exception + { + URI uri = server.getURI().resolve("/test"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("maxAmount=55.0")); + assertThat("Response Content", responseBody, not(containsString(""))); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJMXTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJMXTest.java new file mode 100644 index 00000000000..9a5d3ab9d2a --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJMXTest.java @@ -0,0 +1,62 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.util.Optional; +import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.eclipse.jetty.jmx.MBeanContainer; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerWithJMXTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = ServerWithJMX.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetTest() throws Exception + { + MBeanContainer mbeanContainer = server.getBean(MBeanContainer.class); + MBeanServer mbeanServer = mbeanContainer.getMBeanServer(); + + String name = "org.eclipse.jetty.jmx:name=rmiconnectorserver,*"; + Set mbeanNames = mbeanServer.queryNames(ObjectName.getInstance(name), null); + Optional rmiConnectorNameOptional = mbeanNames.stream().findFirst(); + assertTrue(rmiConnectorNameOptional.isPresent(), "Has RMI Connector Server"); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJNDITest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJNDITest.java new file mode 100644 index 00000000000..603b4ae8023 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ServerWithJNDITest.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class ServerWithJNDITest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + assumeTrue(JettyDistribution.DISTRIBUTION != null, "jetty-distribution not found"); + + server = ServerWithJNDI.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + LifeCycle.stop(server); + } + + @Test + public void testGetTest() throws Exception + { + URI uri = server.getURI().resolve("/test"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, + allOf( + containsString("java:comp/env/woggle"), + containsString("java:comp/env/gargle"), + containsString("java:comp/env/wiggle") + ) + ); + + assertThat("Response Content", responseBody, not(containsString(""))); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SimplestServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SimplestServerTest.java new file mode 100644 index 00000000000..a21a8e438e6 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SimplestServerTest.java @@ -0,0 +1,57 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SimplestServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = SimplestServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetTest() throws Exception + { + URI uri = server.getURI().resolve("/test"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SplitFileServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SplitFileServerTest.java new file mode 100644 index 00000000000..5e5411260fc --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/SplitFileServerTest.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.resource.PathResource; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class SplitFileServerTest extends AbstractEmbeddedTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + Path path0 = Paths.get("src/test/resources/dir0"); + Path path1 = Paths.get("src/test/resources/dir1"); + Resource resource0 = new PathResource(path0); + Resource resource1 = new PathResource(path1); + + server = SplitFileServer.createServer(0, resource0, resource1); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetTest0() throws Exception + { + URI uri = server.getURI().resolve("/test0.txt"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("test0")); + } + + @Test + public void testGetTest1() throws Exception + { + URI uri = server.getURI().resolve("/test1.txt"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.OK_200)); + + // dumpResponseHeaders(response); + + // test response content + String responseBody = response.getContentAsString(); + assertThat("Response Content", responseBody, containsString("test1")); + } + + @Test + public void testGetTest2() throws Exception + { + URI uri = server.getURI().resolve("/test2.txt"); + ContentResponse response = client.GET(uri); + assertThat("HTTP Response Status", response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketJsrServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketJsrServerTest.java new file mode 100644 index 00000000000..34a74d8a24a --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketJsrServerTest.java @@ -0,0 +1,110 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.util.concurrent.LinkedBlockingQueue; +import javax.websocket.CloseReason; +import javax.websocket.ContainerProvider; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class WebSocketJsrServerTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = WebSocketJsrServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetEcho() throws Exception + { + WebSocketContainer javaxWebSocketClient = ContainerProvider.getWebSocketContainer(); + javaxWebSocketClient.setDefaultMaxSessionIdleTimeout(2000); + try + { + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + + TrackingClientEndpoint clientEndpoint = new TrackingClientEndpoint(); + + Session session = javaxWebSocketClient.connectToServer(clientEndpoint, wsUri); + session.getBasicRemote().sendText("Hello World"); + + String response = clientEndpoint.messages.poll(2, SECONDS); + assertThat("Response", response, is("Hello World")); + } + finally + { + LifeCycle.stop(javaxWebSocketClient); + } + } + + public static class TrackingClientEndpoint extends Endpoint implements MessageHandler.Whole + { + public LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); + + @Override + public void onMessage(String message) + { + messages.offer(message); + } + + @Override + public void onOpen(Session session, EndpointConfig config) + { + session.addMessageHandler(this); + } + + @Override + public void onError(Session session, Throwable thr) + { + super.onError(session, thr); + } + + @Override + public void onClose(Session session, CloseReason closeReason) + { + super.onClose(session, closeReason); + } + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketServerTest.java new file mode 100644 index 00000000000..26f2e42d899 --- /dev/null +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/WebSocketServerTest.java @@ -0,0 +1,111 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.embedded; + +import java.net.URI; +import java.time.Duration; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class WebSocketServerTest +{ + private Server server; + + @BeforeEach + public void startServer() throws Exception + { + server = WebSocketServer.createServer(0); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testGetEcho() throws Exception + { + WebSocketClient webSocketClient = new WebSocketClient(); + webSocketClient.setIdleTimeout(Duration.ofSeconds(2)); + try + { + webSocketClient.start(); + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + + TrackingClientEndpoint clientEndpoint = new TrackingClientEndpoint(); + + Future sessionFut = webSocketClient.connect(clientEndpoint, wsUri); + Session session = sessionFut.get(2, SECONDS); + session.getRemote().sendString("Hello World"); + + String response = clientEndpoint.messages.poll(2, SECONDS); + assertThat("Response", response, is("Hello World")); + } + finally + { + LifeCycle.stop(webSocketClient); + } + } + + @WebSocket + public static class TrackingClientEndpoint + { + private static final Logger LOG = Log.getLogger(TrackingClientEndpoint.class); + public LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); + + @OnWebSocketMessage + public void onMessage(String message) + { + messages.offer(message); + } + + @OnWebSocketError + public void onError(Throwable cause) + { + LOG.warn(cause); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) + { + LOG.debug("Closed({}, {})", statusCode, reason); + } + } +} diff --git a/examples/embedded/src/test/resources/jetty-logging.properties b/examples/embedded/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..b17c80c3330 --- /dev/null +++ b/examples/embedded/src/test/resources/jetty-logging.properties @@ -0,0 +1,11 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.LEVEL=WARN +org.eclipse.jetty.embedded.JettyDistribution.LEVEL=DEBUG +#org.eclipse.jetty.STACKS=true +#org.eclipse.jetty.STACKS=false +#org.eclipse.jetty.io.LEVEL=DEBUG +#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 +#org.eclipse.jetty.jmx.LEVEL=DEBUG diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 64844bddf11..fcdf3fe7d3d 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -284,6 +284,11 @@ jetty-security 10.0.0-SNAPSHOT + + org.eclipse.jetty + jetty-openid + 10.0.0-SNAPSHOT + org.eclipse.jetty jetty-server 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 3d6035dc92e..3b093055f93 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 @@ -497,27 +497,6 @@ public class HttpClient extends ContainerLifeCycle return uri; } - /** - * Returns a {@link Destination} for the given scheme, host and port. - * Applications may use {@link Destination}s to create {@link Connection}s - * that will be outside HttpClient's pooling mechanism, to explicitly - * control the connection lifecycle (in particular their termination with - * {@link Connection#close()}). - * - * @param scheme the destination scheme - * @param host the destination host - * @param port the destination port - * @return the destination - * @see #getDestinations() - * @deprecated use {@link #resolveDestination(Request)} instead - */ - @Deprecated - public Destination getDestination(String scheme, String host, int port) - { - Origin origin = createOrigin(scheme, host, port); - return resolveDestination(new HttpDestination.Key(origin, null)); - } - public Destination resolveDestination(Request request) { Origin origin = createOrigin(request.getScheme(), request.getHost(), request.getPort()); @@ -544,25 +523,15 @@ public class HttpClient extends ContainerLifeCycle HttpDestination resolveDestination(HttpDestination.Key key) { - HttpDestination destination = destinations.get(key); - if (destination == null) + return destinations.computeIfAbsent(key, k -> { - destination = getTransport().newHttpDestination(key); + HttpDestination destination = getTransport().newHttpDestination(k); // Start the destination before it's published to other threads. addManaged(destination); - HttpDestination existing = destinations.putIfAbsent(key, destination); - if (existing != null) - { - removeBean(destination); - destination = existing; - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Created {}", destination); - } - } - return destination; + if (LOG.isDebugEnabled()) + LOG.debug("Created {}", destination); + return destination; + }); } protected boolean removeDestination(HttpDestination destination) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 1f073351179..755f44ec618 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -410,6 +410,9 @@ public abstract class HttpReceiver if (exchange == null) return false; + if (LOG.isDebugEnabled()) + LOG.debug("Response failure " + exchange.getResponse(), failure); + // Mark atomically the response as completed, with respect // to concurrency between response success and response failure. if (exchange.responseComplete(failure)) @@ -514,7 +517,7 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response failure {} {} on {}: {}", response, exchange, getHttpChannel(), failure); + LOG.debug("Response abort {} {} on {}: {}", response, exchange, getHttpChannel(), failure); List listeners = exchange.getConversation().getResponseListeners(); ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); notifier.notifyFailure(listeners, response, failure); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index e9e74b7c6f4..5e430d39515 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -344,6 +344,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener if (exchange == null) return; + if (LOG.isDebugEnabled()) + LOG.debug("Request failure " + exchange.getRequest(), failure); + // Mark atomically the request as completed, with respect // to concurrency between request success and request failure. if (exchange.requestComplete(failure)) @@ -559,7 +562,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener Request request = exchange.getRequest(); if (LOG.isDebugEnabled()) - LOG.debug("Request failure {} {} on {}: {}", request, exchange, getHttpChannel(), failure); + LOG.debug("Request abort {} {} on {}: {}", request, exchange, getHttpChannel(), failure); HttpDestination destination = getHttpChannel().getHttpDestination(); destination.getRequestNotifier().notifyFailure(request, failure); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java index 12e98ade896..1b9a0c545e9 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java @@ -152,17 +152,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne @Override public void onFillable() { - HttpExchange exchange = channel.getHttpExchange(); - if (exchange != null) - { - channel.receive(); - } - else - { - // If there is no exchange, then could be either a remote close, - // or garbage bytes; in both cases we close the connection - close(); - } + channel.receive(); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index d0a153c6c6f..310b0db9e8e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -173,10 +173,10 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res boolean complete = this.complete; this.complete = false; if (LOG.isDebugEnabled()) - LOG.debug("Parsed {}, remaining {} {}", handle, buffer.remaining(), parser); + LOG.debug("Parsed {}, remaining {} {}", handle, BufferUtil.length(buffer), parser); if (handle) return true; - if (!buffer.hasRemaining()) + if (!BufferUtil.hasContent(buffer)) return false; if (complete) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java index b8e23ef058d..e27f35574b2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java @@ -288,7 +288,6 @@ public class HttpSenderOverHTTP extends HttpSender public void failed(Throwable x) { release(); - callback.failed(x); super.failed(x); } @@ -299,6 +298,13 @@ public class HttpSenderOverHTTP extends HttpSender callback.succeeded(); } + @Override + protected void onCompleteFailure(Throwable cause) + { + super.onCompleteFailure(cause); + callback.failed(cause); + } + private void release() { ByteBufferPool bufferPool = httpClient.getByteBufferPool(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java index 50befdceabf..34545f0fbd9 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java @@ -37,7 +37,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -48,7 +47,6 @@ import static org.junit.jupiter.api.Assertions.fail; * This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt * section 3.1) is configurable in SslContextFactory and works as expected. */ -@Disabled public class HostnameVerificationTest { private SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(); @@ -100,18 +98,15 @@ public class HostnameVerificationTest { client.stop(); server.stop(); - server.join(); } /** * This test is supposed to verify that hostname verification works as described in: * http://www.ietf.org/rfc/rfc2818.txt section 3.1. It uses a certificate with a common name different to localhost * and sends a request to localhost. This should fail with an SSLHandshakeException. - * - * @throws Exception on test failure */ @Test - public void simpleGetWithHostnameVerificationEnabledTest() throws Exception + public void simpleGetWithHostnameVerificationEnabledTest() { clientSslContextFactory.setEndpointIdentificationAlgorithm("HTTPS"); String uri = "https://localhost:" + connector.getLocalPort() + "/"; @@ -119,8 +114,16 @@ public class HostnameVerificationTest ExecutionException x = assertThrows(ExecutionException.class, () -> client.GET(uri)); Throwable cause = x.getCause(); assertThat(cause, Matchers.instanceOf(SSLHandshakeException.class)); - Throwable root = cause.getCause().getCause(); - assertThat(root, Matchers.instanceOf(CertificateException.class)); + + // Search for the CertificateException. + Throwable certificateException = cause.getCause(); + while (certificateException != null) + { + if (certificateException instanceof CertificateException) + break; + certificateException = certificateException.getCause(); + } + assertThat(certificateException, Matchers.instanceOf(CertificateException.class)); } /** diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml index c279a388389..d244576c146 100644 --- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml +++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml @@ -36,9 +36,9 @@ --> - + - + jetty.deploy.monitoredPath diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index 64de4a6444e..5e53d5f8930 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -172,7 +172,7 @@ public class DeploymentManager extends ContainerLifeCycle for (AppProvider provider : providers) { if (_providers.add(provider)) - addBean(provider); + addBean(provider, true); } } @@ -186,7 +186,7 @@ public class DeploymentManager extends ContainerLifeCycle if (isRunning()) throw new IllegalStateException(); _providers.add(provider); - addBean(provider); + addBean(provider, true); } public void setLifeCycleBindings(Collection bindings) @@ -523,6 +523,7 @@ public class DeploymentManager extends ContainerLifeCycle catch (Throwable t) { LOG.warn("Unable to reach node goal: " + nodeName, t); + // migrate to FAILED node Node failed = _lifecycle.getNodeByName(AppLifeCycle.FAILED); appentry.setLifeCycleNode(failed); diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java index e7708db5b08..4bd3c538afb 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.deploy; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.MalformedURLException; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -29,6 +28,7 @@ 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.annotation.Name; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.resource.Resource; /** @@ -37,21 +37,33 @@ import org.eclipse.jetty.util.resource.Resource; * Supplies properties defined in a file. */ @ManagedObject("Configure deployed webapps via properties") -public class PropertiesConfigurationManager implements ConfigurationManager +public class PropertiesConfigurationManager implements ConfigurationManager, Dumpable { private String _properties; - private final Map _map = new HashMap(); + private final Map _map = new HashMap<>(); public PropertiesConfigurationManager(String properties) { + if (properties != null) + { + try + { + setFile(properties); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } } public PropertiesConfigurationManager() { + this(null); } @ManagedAttribute("A file or URL of properties") - public void setFile(String resource) throws MalformedURLException, IOException + public void setFile(String resource) throws IOException { _properties = resource; _map.clear(); @@ -75,7 +87,7 @@ public class PropertiesConfigurationManager implements ConfigurationManager @Override public Map getProperties() { - return new HashMap<>(_map); + return _map; } private void loadProperties(String resource) throws FileNotFoundException, IOException @@ -86,9 +98,25 @@ public class PropertiesConfigurationManager implements ConfigurationManager Properties properties = new Properties(); properties.load(file.getInputStream()); for (Map.Entry entry : properties.entrySet()) - { _map.put(entry.getKey().toString(), String.valueOf(entry.getValue())); - } } } + + @Override + public String toString() + { + return String.format("%s@%x{%s}", this.getClass(), hashCode(), _properties); + } + + @Override + public String dump() + { + return Dumpable.dump(this); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + Dumpable.dumpObjects(out, indent, toString(), _map); + } } diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java index 17f8353a26a..b57afd183c1 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java @@ -20,7 +20,9 @@ package org.eclipse.jetty.deploy.bindings; import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.AppLifeCycle; +import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.graph.Node; +import org.eclipse.jetty.deploy.providers.WebAppProvider; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -63,7 +65,7 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding { return new String[]{"deploying"}; } - + @Override public void processBinding(Node node, App app) throws Exception { @@ -94,6 +96,13 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding XmlConfiguration jettyXmlConfig = new XmlConfiguration(globalContextSettings); Resource resource = Resource.newResource(app.getOriginId()); app.getDeploymentManager().scope(jettyXmlConfig, resource); + AppProvider appProvider = app.getAppProvider(); + if (appProvider instanceof WebAppProvider) + { + WebAppProvider webAppProvider = ((WebAppProvider)appProvider); + if (webAppProvider.getConfigurationManager() != null) + jettyXmlConfig.getProperties().putAll(webAppProvider.getConfigurationManager().getProperties()); + } WebAppClassLoader.runWithServerClassAccess(() -> { jettyXmlConfig.configure(context); diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index 0507816c76b..dae686bf0ea 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -36,7 +36,7 @@ import org.eclipse.jetty.util.Scanner; 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.AbstractLifeCycle; +import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -45,7 +45,7 @@ import org.eclipse.jetty.util.resource.Resource; * */ @ManagedObject("Abstract Provider for loading webapps") -public abstract class ScanningAppProvider extends AbstractLifeCycle implements AppProvider +public abstract class ScanningAppProvider extends ContainerLifeCycle implements AppProvider { private static final Logger LOG = Log.getLogger(ScanningAppProvider.class); @@ -81,11 +81,13 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A protected ScanningAppProvider() { + this(null); } protected ScanningAppProvider(FilenameFilter filter) { _filenameFilter = filter; + addBean(_appMap); } protected void setFilenameFilter(FilenameFilter filter) @@ -142,15 +144,19 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A _scanner.setFilenameFilter(_filenameFilter); _scanner.setReportDirs(true); _scanner.addListener(_scannerListener); - _scanner.start(); + + addBean(_scanner); + + super.doStart(); } @Override protected void doStop() throws Exception { + super.doStop(); if (_scanner != null) { - _scanner.stop(); + removeBean(_scanner); _scanner.removeListener(_scannerListener); _scanner = null; } @@ -307,4 +313,10 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A ); _scanner.scan(); } + + @Override + public String toString() + { + return String.format("%s@%x%s", this.getClass(), hashCode(), _monitored); + } } diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java index 5217e862ce9..8c26d9e4da8 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java @@ -206,6 +206,7 @@ public class WebAppProvider extends ScanningAppProvider */ public void setConfigurationManager(ConfigurationManager configurationManager) { + updateBean(_configurationManager, configurationManager); _configurationManager = configurationManager; } diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-sessioncache.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-sessioncache.adoc index d06c0f74f28..cd7ebfa5a0e 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-sessioncache.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-sessioncache.adoc @@ -34,6 +34,7 @@ Once the `session-cache-hash` module has been enabled, you can view a list of al #jetty.session.saveOnInactiveEvict=false #jetty.session.saveOnCreate=false #jetty.session.removeUnloadableSessions=false +#jetty.session.flushOnResponseCommit=false ---- jetty.session.evictionPolicy:: @@ -63,6 +64,12 @@ 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`. +jetty.session.flushOnResponseCommit:: +Boolean, default `false`. +If true, if a session is "dirty" - ie its attributes have changed - it will be written to the backing store as the response is about to commit. +This ensures that all subsequent requests whether to the same or different node will see the updated session data. +If false, a dirty session will only be written to the backing store when the last simultaneous request for it leaves the session. + For more general information on the uses of these configuration properties, see link:#sessions-details[Session Components]. @@ -72,4 +79,21 @@ The `NullSessionCache` is a trivial implementation of the `SessionCache` that do 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]. +To enable the `NullSessionCache`, enable the `sesssion-cache-null` link:#startup-modules[module]. +Configuration options are: + +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`. + +jetty.session.flushOnResponseCommit:: +Boolean, default `false`. +If true, if a session is "dirty" - ie its attributes have changed - it will be written to the backing store as the response is about to commit. +This ensures that all subsequent requests whether to the same or different node will see the updated session data. +If false, a dirty session will only be written to the backing store when the last simultaneous request for it leaves the session. + +For more general information on the uses of these configuration properties, see link:#sessions-details[Session Components]. diff --git a/jetty-documentation/src/main/asciidoc/quickstart-guide/introduction/what-version.adoc b/jetty-documentation/src/main/asciidoc/quickstart-guide/introduction/what-version.adoc index b4164bb19c7..a9a35434445 100644 --- a/jetty-documentation/src/main/asciidoc/quickstart-guide/introduction/what-version.adoc +++ b/jetty-documentation/src/main/asciidoc/quickstart-guide/introduction/what-version.adoc @@ -32,11 +32,14 @@ _____ .Jetty Versions [width="100%",cols="12%,9%,15%,6%,21%,10%,6%,21%",options="header",] |======================================================================= -|Version |Year |Home |JVM |Protocols |Servlet |JSP |Status +|Version |Year |Home |Min JVM |Protocols |Servlet |JSP |Status +|10 |2019- |Eclipse |11 ^(1)^ |HTTP/1.1 (RFC 7230), HTTP/2 (RFC 7540), WebSocket (RFC 6455, JSR 356), FastCGI |4.0.2 |2.3 |*UNSTABLE / Alpha* |9.4 |2016- |Eclipse |1.8 |HTTP/1.1 (RFC 7230), HTTP/2 (RFC 7540), WebSocket (RFC 6455, JSR 356), FastCGI |3.1 |2.3 |Stable -|9.3 |2015- |Eclipse |1.8 |HTTP/1.1 (RFC 7230), HTTP/2 (RFC 7540), WebSocket (RFC 6455, JSR 356), FastCGI |3.1 |2.3 |Stable -|9.2 |2014-2018 |Eclipse |1.7 |HTTP/1.1 RFC2616, javax.websocket, SPDY v3 |3.1 |2.3 |Deprecated / *End of Life January 2018* -|8 |2009-2014 |Eclipse/Codehaus |1.6 |HTTP/1.1 RFC2616, WebSocket RFC 6455, SPDY v3 |3.0 |2.2 |Deprecated / *End of Life November 2014* +|9.3 |2015- |Eclipse |1.8 ^(2)^ |HTTP/1.1 (RFC 7230), HTTP/2 (RFC 7540), WebSocket (RFC 6455, JSR 356), FastCGI |3.1 |2.3 |Stable +|9.2 |2014-2018 |Eclipse |1.7 ^(2)^ |HTTP/1.1 RFC2616, javax.websocket, SPDY v3 |3.1 |2.3 |Deprecated / *End of Life January 2018* +|9.1 |2013-2014 |Eclipse |1.7 ^(2)^ |HTTP/1.1 RFC2616 |3.1 |2.3 |Deprecated / *End of Life May 2014* +|9.0 |2013-2013 |Eclipse |1.7 ^(2)^ |HTTP/1.1 RFC2616 |3.1-beta |2.3 |Deprecated / *End of Life November 2013* +|8 |2009-2014 |Eclipse/Codehaus |1.6 ^(2)^ |HTTP/1.1 RFC2616, WebSocket RFC 6455, SPDY v3 |3.0 |2.2 |Deprecated / *End of Life November 2014* |7 |2008-2014 |Eclipse/Codehaus |1.5 |HTTP/1.1 RFC2616, WebSocket RFC 6455, SPDY v3 |2.5 |2.1 |Deprecated / *End of Life November 2014* |6 |2006-2010 |Codehaus |1.4-1.5 |HTTP/1.1 RFC2616 |2.5 |2.0 |Deprecated / *End of Life November 2010* |5 |2003-2009 |Sourceforge |1.2-1.5 |HTTP/1.1 RFC2616 |2.4 |2.0 |Antique @@ -45,3 +48,6 @@ _____ |2 |1998-2000 |Mortbay |1.1 |HTTP/1.0 RFC1945 |2.1 |1.0 |Legendary |1 |1995-1998 |Mortbay |1.0 |HTTP/1.0 RFC1945 |- |- |Mythical |======================================================================= + + 1. JPMS module support is optional + 2. JDK9 and newer is not supported if using MultiRelease JAR Files, or Bytecode / Annotation scanning. \ No newline at end of file diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java index 1e12e7a6406..f1a654d2338 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -50,12 +50,6 @@ public class HttpTransportOverFCGI implements HttpTransport this.request = request; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return false; - } - @Override public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) { diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 3bef176d2db..08acbaa9a8b 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -723,6 +723,11 @@ jetty-alpn-java-server ${project.version} + + org.eclipse.jetty + jetty-openid + ${project.version} + org.eclipse.jetty jetty-alpn-conscrypt-server diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index f55569858c1..e1d4e13b497 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -78,7 +78,7 @@ public class HttpGenerator FLUSH, // The buffers previously generated should be flushed CONTINUE, // Continue generating the message SHUTDOWN_OUT, // Need EOF to be signaled - DONE // Message generation complete + DONE // The current phase of generation is complete } // other statics diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java index aea328cca0a..8f570f63b81 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -20,9 +20,14 @@ package org.eclipse.jetty.http.pathmap; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class ServletPathSpec extends PathSpec { + + private static final Logger LOG = Log.getLogger(ServletPathSpec.class); + /** * If a servlet or filter path mapping isn't a suffix mapping, ensure * it starts with '/' @@ -213,13 +218,13 @@ public class ServletPathSpec extends PathSpec super.pathDepth = 0; char lastChar = servletPathSpec.charAt(specLength - 1); // prefix based - if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*')) + if (servletPathSpec.charAt(0) == '/' && servletPathSpec.endsWith("/*")) { this.group = PathSpecGroup.PREFIX_GLOB; this.prefix = servletPathSpec.substring(0, specLength - 2); } // suffix based - else if (servletPathSpec.charAt(0) == '*') + else if (servletPathSpec.charAt(0) == '*' && servletPathSpec.length() > 1) { this.group = PathSpecGroup.SUFFIX_GLOB; this.suffix = servletPathSpec.substring(2, specLength); @@ -228,6 +233,11 @@ public class ServletPathSpec extends PathSpec { this.group = PathSpecGroup.EXACT; this.prefix = servletPathSpec; + if (servletPathSpec.endsWith("*")) + { + LOG.warn("Suspicious URL pattern: '{}'; see sections 12.1 and 12.2 of the Servlet specification", + servletPathSpec); + } } for (int i = 0; i < specLength; i++) @@ -276,11 +286,6 @@ public class ServletPathSpec extends PathSpec { throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \"" + servletPathSpec + "\""); } - - if (idx < 1 || servletPathSpec.charAt(idx - 1) != '/') - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix glob '*' can only exist after '/': bad spec \"" + servletPathSpec + "\""); - } } else if (servletPathSpec.startsWith("*.")) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 018dc430f43..a609f55d8ba 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -231,6 +231,8 @@ public class PathMappingsTest assertTrue(!new ServletPathSpec("/foo/*").matches("/bar/anything"), "!match /foo/*"); assertTrue(new ServletPathSpec("*.foo").matches("anything.foo"), "match *.foo"); assertTrue(!new ServletPathSpec("*.foo").matches("anything.bar"), "!match *.foo"); + assertTrue(new ServletPathSpec("/On*").matches("/On*"), "match /On*"); + assertTrue(!new ServletPathSpec("/On*").matches("/One"), "!match /One"); assertEquals("10", p.getMatch("/").getResource(), "match / with ''"); @@ -287,7 +289,6 @@ public class PathMappingsTest @ValueSource(strings = { "*", "/foo/*/bar", - "/foo*", "*/foo", "*.foo/*" }) 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 2e1238c1731..6aaedb6e5c6 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 @@ -56,6 +56,8 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private final ISession session; private final int bufferSize; private final ExecutionStrategy strategy; + private boolean useInputDirectByteBuffers; + private boolean useOutputDirectByteBuffers; public HTTP2Connection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize) { @@ -99,6 +101,26 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. producer.setInputBuffer(buffer); } + public boolean isUseInputDirectByteBuffers() + { + return useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + this.useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + public boolean isUseOutputDirectByteBuffers() + { + return useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + @Override public void onOpen() { @@ -389,7 +411,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. { private NetworkBuffer() { - super(byteBufferPool, bufferSize, false); + super(byteBufferPool, bufferSize, isUseInputDirectByteBuffers()); } private void put(ByteBuffer source) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java index 7b90ad1926e..178bb92cb05 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java @@ -493,12 +493,6 @@ public abstract class HTTP2StreamEndPoint implements EndPoint LOG.debug("onClose {}", this); } - @Override - public boolean isOptimizedForDirectBuffers() - { - return true; - } - @Override public void upgrade(Connection newConnection) { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java index 0194beccc27..ede232e2f25 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java @@ -44,4 +44,9 @@ public abstract class FrameGenerator { return headerGenerator.getMaxFrameSize(); } + + public boolean isUseDirectByteBuffers() + { + return headerGenerator.isUseDirectByteBuffers(); + } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java index b7288d3897a..b71b136335b 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java @@ -38,10 +38,15 @@ public class Generator } public Generator(ByteBufferPool byteBufferPool, int maxDynamicTableSize, int maxHeaderBlockFragment) + { + this(byteBufferPool, true, maxDynamicTableSize, maxHeaderBlockFragment); + } + + public Generator(ByteBufferPool byteBufferPool, boolean useDirectByteBuffers, int maxDynamicTableSize, int maxHeaderBlockFragment) { this.byteBufferPool = byteBufferPool; - headerGenerator = new HeaderGenerator(); + headerGenerator = new HeaderGenerator(useDirectByteBuffers); hpackEncoder = new HpackEncoder(maxDynamicTableSize); this.generators = new FrameGenerator[FrameType.values().length]; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeaderGenerator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeaderGenerator.java index 33283a72389..404fb6d72bc 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeaderGenerator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeaderGenerator.java @@ -27,10 +27,26 @@ import org.eclipse.jetty.io.ByteBufferPool; public class HeaderGenerator { private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH; + private final boolean useDirectByteBuffers; + + public HeaderGenerator() + { + this(true); + } + + public HeaderGenerator(boolean useDirectByteBuffers) + { + this.useDirectByteBuffers = useDirectByteBuffers; + } + + public boolean isUseDirectByteBuffers() + { + return useDirectByteBuffers; + } public ByteBuffer generate(ByteBufferPool.Lease lease, FrameType frameType, int capacity, int length, int flags, int streamId) { - ByteBuffer header = lease.acquire(capacity, true); + ByteBuffer header = lease.acquire(capacity, isUseDirectByteBuffers()); header.put((byte)((length & 0x00_FF_00_00) >>> 16)); header.put((byte)((length & 0x00_00_FF_00) >>> 8)); header.put((byte)((length & 0x00_00_00_FF))); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java index 4ff03bd28e5..f3e0b5cd271 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java @@ -67,7 +67,7 @@ public class HeadersGenerator extends FrameGenerator flags = Flags.PRIORITY; int maxFrameSize = getMaxFrameSize(); - ByteBuffer hpacked = lease.acquire(maxFrameSize, false); + ByteBuffer hpacked = lease.acquire(maxFrameSize, isUseDirectByteBuffers()); BufferUtil.clearToFill(hpacked); encoder.encode(hpacked, metaData); int hpackedLength = hpacked.position(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java index d4fe2640ef9..d3a4033b587 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java @@ -58,7 +58,7 @@ public class PushPromiseGenerator extends FrameGenerator int extraSpace = 4; maxFrameSize -= extraSpace; - ByteBuffer hpacked = lease.acquire(maxFrameSize, false); + ByteBuffer hpacked = lease.acquire(maxFrameSize, isUseDirectByteBuffers()); BufferUtil.clearToFill(hpacked); encoder.encode(hpacked, metaData); int hpackedLength = hpacked.position(); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 675d8269e94..9943f463c7b 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -461,6 +461,8 @@ public class HpackContext if (value != null && value.length() > 0) { int huffmanLen = Huffman.octetsNeeded(value); + if (huffmanLen < 0) + throw new IllegalStateException("bad value"); int lenLen = NBitInteger.octectsNeeded(7, huffmanLen); _huffmanValue = new byte[1 + lenLen + huffmanLen]; ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index bb1d3ede5fb..db4eaeb892c 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -25,7 +25,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTokens; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; -import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -77,13 +77,8 @@ public class HpackDecoder while (buffer.hasRemaining()) { - if (LOG.isDebugEnabled() && buffer.hasArray()) - { - int l = Math.min(buffer.remaining(), 32); - LOG.debug("decode {}{}", - TypeUtil.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l), - l < buffer.remaining() ? "..." : ""); - } + if (LOG.isDebugEnabled()) + LOG.debug("decode {}", BufferUtil.toHexString(buffer)); byte b = buffer.get(); if (b < 0) @@ -177,7 +172,7 @@ public class HpackDecoder else name = toASCIIString(buffer, length); check: - for (int i = name.length(); i-- > 0;) + for (int i = name.length(); i-- > 0; ) { char c = name.charAt(i); if (c > 0xff) @@ -280,14 +275,9 @@ public class HpackDecoder public static String toASCIIString(ByteBuffer buffer, int length) { StringBuilder builder = new StringBuilder(length); - int position = buffer.position(); - int start = buffer.arrayOffset() + position; - int end = start + length; - buffer.position(position + length); - byte[] array = buffer.array(); - for (int i = start; i < end; i++) + for (int i = 0; i < length; ++i) { - builder.append((char)(0x7f & array[i])); + builder.append((char)(0x7F & buffer.get())); } return builder.toString(); } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index bff99f5ba1a..04734317942 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -19,9 +19,10 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.EnumSet; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; @@ -34,24 +35,23 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry; -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.log.Log; import org.eclipse.jetty.util.log.Logger; public class HpackEncoder { - public static final Logger LOG = Log.getLogger(HpackEncoder.class); - private static final HttpField[] __status = new HttpField[599]; - static final EnumSet __DO_NOT_HUFFMAN = + private static final Logger LOG = Log.getLogger(HpackEncoder.class); + private static final HttpField[] STATUSES = new HttpField[599]; + static final EnumSet DO_NOT_HUFFMAN = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.CONTENT_MD5, HttpHeader.PROXY_AUTHENTICATE, HttpHeader.PROXY_AUTHORIZATION); - static final EnumSet __DO_NOT_INDEX = + static final EnumSet DO_NOT_INDEX = EnumSet.of( // HttpHeader.C_PATH, // TODO more data needed // HttpHeader.DATE, // TODO more data needed @@ -71,23 +71,21 @@ public class HpackEncoder HttpHeader.LAST_MODIFIED, HttpHeader.SET_COOKIE, HttpHeader.SET_COOKIE2); - static final EnumSet __NEVER_INDEX = + static final EnumSet NEVER_INDEX = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.SET_COOKIE, HttpHeader.SET_COOKIE2); - private static final PreEncodedHttpField CONNECTION_TE = new PreEncodedHttpField(HttpHeader.CONNECTION, "te"); + private static final EnumSet IGNORED_HEADERS = EnumSet.of(HttpHeader.CONNECTION, HttpHeader.KEEP_ALIVE, + HttpHeader.PROXY_CONNECTION, HttpHeader.TRANSFER_ENCODING, HttpHeader.UPGRADE); private static final PreEncodedHttpField TE_TRAILERS = new PreEncodedHttpField(HttpHeader.TE, "trailers"); - private static final Trie specialHopHeaders = new ArrayTrie<>(6); static { for (HttpStatus.Code code : HttpStatus.Code.values()) { - __status[code.getCode()] = new PreEncodedHttpField(HttpHeader.C_STATUS, Integer.toString(code.getCode())); + STATUSES[code.getCode()] = new PreEncodedHttpField(HttpHeader.C_STATUS, Integer.toString(code.getCode())); } - specialHopHeaders.put("close", true); - specialHopHeaders.put("te", true); } private final HpackContext _context; @@ -182,33 +180,37 @@ public class HpackEncoder { MetaData.Response response = (MetaData.Response)metadata; int code = response.getStatus(); - HttpField status = code < __status.length ? __status[code] : null; + HttpField status = code < STATUSES.length ? STATUSES[code] : null; if (status == null) status = new HttpField.IntValueHttpField(HttpHeader.C_STATUS, code); encode(buffer, status); } - // Add all non-connection fields. + // Remove fields as specified in RFC 7540, 8.1.2.2. HttpFields fields = metadata.getFields(); if (fields != null) { - Set hopHeaders = fields.getCSV(HttpHeader.CONNECTION, false).stream() - .filter(v -> specialHopHeaders.get(v) == Boolean.TRUE) - .map(StringUtil::asciiToLowerCase) - .collect(Collectors.toSet()); + // For example: Connection: Close, TE, Upgrade, Custom. + Set hopHeaders = null; + for (String value : fields.getCSV(HttpHeader.CONNECTION, false)) + { + if (hopHeaders == null) + hopHeaders = new HashSet<>(); + hopHeaders.add(StringUtil.asciiToLowerCase(value)); + } for (HttpField field : fields) { - if (field.getHeader() == HttpHeader.CONNECTION) + HttpHeader header = field.getHeader(); + if (header != null && IGNORED_HEADERS.contains(header)) continue; - if (!hopHeaders.isEmpty() && hopHeaders.contains(StringUtil.asciiToLowerCase(field.getName()))) - continue; - if (field.getHeader() == HttpHeader.TE) + if (header == HttpHeader.TE) { - if (!field.contains("trailers")) - continue; - encode(buffer, CONNECTION_TE); - encode(buffer, TE_TRAILERS); + if (field.contains("trailers")) + encode(buffer, TE_TRAILERS); + continue; } + if (hopHeaders != null && hopHeaders.contains(StringUtil.asciiToLowerCase(field.getName()))) + continue; encode(buffer, field); } } @@ -318,12 +320,12 @@ public class HpackEncoder if (_debug) encoding = indexed ? "PreEncodedIdx" : "PreEncoded"; } - else if (__DO_NOT_INDEX.contains(header)) + else if (DO_NOT_INDEX.contains(header)) { // Non indexed field indexed = false; - boolean neverIndex = __NEVER_INDEX.contains(header); - boolean huffman = !__DO_NOT_HUFFMAN.contains(header); + boolean neverIndex = NEVER_INDEX.contains(header); + boolean huffman = !DO_NOT_HUFFMAN.contains(header); encodeName(buffer, neverIndex ? (byte)0x10 : (byte)0x00, 4, header.asString(), name); encodeValue(buffer, huffman, field.getValue()); @@ -346,7 +348,7 @@ public class HpackEncoder { // indexed indexed = true; - boolean huffman = !__DO_NOT_HUFFMAN.contains(header); + boolean huffman = !DO_NOT_HUFFMAN.contains(header); encodeName(buffer, (byte)0x40, 6, header.asString(), name); encodeValue(buffer, huffman, field.getValue()); if (_debug) @@ -362,9 +364,8 @@ public class HpackEncoder if (_debug) { - int e = buffer.position(); if (LOG.isDebugEnabled()) - LOG.debug("encode {}:'{}' to '{}'", encoding, field, TypeUtil.toHexString(buffer.array(), buffer.arrayOffset() + p, e - p)); + LOG.debug("encode {}:'{}' to '{}'", encoding, field, BufferUtil.toHexString(buffer)); } } @@ -400,19 +401,38 @@ public class HpackEncoder { // huffman literal value buffer.put((byte)0x80); - NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(value)); - Huffman.encode(buffer, value); + + int needed = Huffman.octetsNeeded(value); + if (needed >= 0) + { + NBitInteger.encode(buffer, 7, needed); + Huffman.encode(buffer, value); + } + else + { + // Not iso_8859_1 + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(bytes)); + Huffman.encode(buffer, bytes); + } } else { // add literal assuming iso_8859_1 - buffer.put((byte)0x00); + buffer.put((byte)0x00).mark(); NBitInteger.encode(buffer, 7, value.length()); for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (c < ' ' || c > 127) - throw new IllegalArgumentException(); + { + // Not iso_8859_1, so re-encode as UTF-8 + buffer.reset(); + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + NBitInteger.encode(buffer, 7, bytes.length); + buffer.put(bytes, 0, bytes.length); + return; + } buffer.put((byte)c); } } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java index 2b3d30dc345..ba54ecb2dad 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java @@ -46,7 +46,7 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder @Override public byte[] getEncodedField(HttpHeader header, String name, String value) { - boolean notIndexed = HpackEncoder.__DO_NOT_INDEX.contains(header); + boolean notIndexed = HpackEncoder.DO_NOT_INDEX.contains(header); ByteBuffer buffer = BufferUtil.allocate(name.length() + value.length() + 10); BufferUtil.clearToFill(buffer); @@ -56,8 +56,8 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder if (notIndexed) { // Non indexed field - boolean neverIndex = HpackEncoder.__NEVER_INDEX.contains(header); - huffman = !HpackEncoder.__DO_NOT_HUFFMAN.contains(header); + boolean neverIndex = HpackEncoder.NEVER_INDEX.contains(header); + huffman = !HpackEncoder.DO_NOT_HUFFMAN.contains(header); buffer.put(neverIndex ? (byte)0x10 : (byte)0x00); bits = 4; } @@ -72,7 +72,7 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder { // indexed buffer.put((byte)0x40); - huffman = !HpackEncoder.__DO_NOT_HUFFMAN.contains(header); + huffman = !HpackEncoder.DO_NOT_HUFFMAN.contains(header); bits = 6; } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java index 0bbe4f661d4..9edd8a0fd7f 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java @@ -20,6 +20,8 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; +import org.eclipse.jetty.util.Utf8StringBuilder; + public class Huffman { @@ -358,7 +360,7 @@ public class Huffman public static String decode(ByteBuffer buffer, int length) throws HpackException.CompressionException { - StringBuilder out = new StringBuilder(length * 2); + Utf8StringBuilder utf8 = new Utf8StringBuilder(length * 2); int node = 0; int current = 0; int bits = 0; @@ -378,7 +380,7 @@ public class Huffman throw new HpackException.CompressionException("EOS in content"); // terminal node - out.append(rowsym[node]); + utf8.append((byte)(0xFF & rowsym[node])); bits -= rowbits[node]; node = 0; } @@ -411,7 +413,7 @@ public class Huffman break; } - out.append(rowsym[node]); + utf8.append((byte)(0xFF & rowsym[node])); bits -= rowbits[node]; node = 0; } @@ -419,7 +421,27 @@ public class Huffman if (node != 0) throw new HpackException.CompressionException("Bad termination"); - return out.toString(); + return utf8.toString(); + } + + public static int octetsNeeded(String s) + { + return octetsNeeded(CODES, s); + } + + public static int octetsNeeded(byte[] b) + { + return octetsNeeded(CODES, b); + } + + public static void encode(ByteBuffer buffer, String s) + { + encode(CODES, buffer, s); + } + + public static void encode(ByteBuffer buffer, byte[] b) + { + encode(CODES, buffer, b); } public static int octetsNeededLC(String s) @@ -432,11 +454,6 @@ public class Huffman encode(LCCODES, buffer, s); } - public static int octetsNeeded(String s) - { - return octetsNeeded(CODES, s); - } - private static int octetsNeeded(final int[][] table, String s) { int needed = 0; @@ -445,18 +462,30 @@ public class Huffman { char c = s.charAt(i); if (c >= 128 || c < ' ') - throw new IllegalArgumentException(); + return -1; needed += table[c][1]; } return (needed + 7) / 8; } - public static void encode(ByteBuffer buffer, String s) + private static int octetsNeeded(final int[][] table, byte[] b) { - encode(CODES, buffer, s); + int needed = 0; + int len = b.length; + for (int i = 0; i < len; i++) + { + int c = 0xFF & b[i]; + needed += table[c][1]; + } + return (needed + 7) / 8; } + /** + * @param table The table to encode by + * @param buffer The buffer to encode to + * @param s The string to encode + */ private static void encode(final int[][] table, ByteBuffer buffer, String s) { long current = 0; @@ -488,4 +517,35 @@ public class Huffman buffer.put((byte)(current)); } } + + private static void encode(final int[][] table, ByteBuffer buffer, byte[] b) + { + long current = 0; + int n = 0; + + int len = b.length; + for (int i = 0; i < len; i++) + { + int c = 0xFF & b[i]; + int code = table[c][0]; + int bits = table[c][1]; + + current <<= bits; + current |= code; + n += bits; + + while (n >= 8) + { + n -= 8; + buffer.put((byte)(current >> n)); + } + } + + if (n > 0) + { + current <<= (8 - n); + current |= (0xFF >>> n); + buffer.put((byte)(current)); + } + } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java index 7b9218d3585..245f925cd2f 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java @@ -67,7 +67,7 @@ public class HpackTest BufferUtil.flipToFlush(buffer, 0); Response decoded0 = (Response)decoder.decode(buffer); original0.getFields().put(new HttpField(HttpHeader.CONTENT_ENCODING, "")); - assertMetadataSame(original0, decoded0); + assertMetaDataResponseSame(original0, decoded0); // Same again? BufferUtil.clearToFill(buffer); @@ -75,7 +75,7 @@ public class HpackTest BufferUtil.flipToFlush(buffer, 0); Response decoded0b = (Response)decoder.decode(buffer); - assertMetadataSame(original0, decoded0b); + assertMetaDataResponseSame(original0, decoded0b); HttpFields fields1 = new HttpFields(); fields1.add(HttpHeader.CONTENT_TYPE, "text/plain"); @@ -93,7 +93,7 @@ public class HpackTest BufferUtil.flipToFlush(buffer, 0); Response decoded1 = (Response)decoder.decode(buffer); - assertMetadataSame(original1, decoded1); + assertMetaDataResponseSame(original1, decoded1); assertEquals("custom-key", decoded1.getFields().getField("Custom-Key").getName()); } @@ -106,19 +106,19 @@ public class HpackTest HttpFields fields0 = new HttpFields(); fields0.add("1234567890", "1234567890123456789012345678901234567890"); - fields0.add("Cookie", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); + fields0.add("Cookie", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); MetaData original0 = new MetaData(HttpVersion.HTTP_2, fields0); BufferUtil.clearToFill(buffer); encoder.encode(buffer, original0); BufferUtil.flipToFlush(buffer, 0); - MetaData decoded0 = (MetaData)decoder.decode(buffer); + MetaData decoded0 = decoder.decode(buffer); - assertMetadataSame(original0, decoded0); + assertMetaDataSame(original0, decoded0); HttpFields fields1 = new HttpFields(); fields1.add("1234567890", "1234567890123456789012345678901234567890"); - fields1.add("Cookie", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); + fields1.add("Cookie", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); fields1.add("x", "y"); MetaData original1 = new MetaData(HttpVersion.HTTP_2, fields1); @@ -136,6 +136,26 @@ public class HpackTest } } + @Test + public void encodeDecodeNonAscii() throws Exception + { + HpackEncoder encoder = new HpackEncoder(); + HpackDecoder decoder = new HpackDecoder(4096, 8192); + ByteBuffer buffer = BufferUtil.allocate(16 * 1024); + + HttpFields fields0 = new HttpFields(); + fields0.add("Cookie", "[\uD842\uDF9F]"); + fields0.add("custom-key", "[\uD842\uDF9F]"); + Response original0 = new MetaData.Response(HttpVersion.HTTP_2, 200, fields0); + + BufferUtil.clearToFill(buffer); + encoder.encode(buffer, original0); + BufferUtil.flipToFlush(buffer, 0); + Response decoded0 = (Response)decoder.decode(buffer); + + assertMetaDataSame(original0, decoded0); + } + @Test public void evictReferencedFieldTest() throws Exception { @@ -143,57 +163,111 @@ public class HpackTest HpackDecoder decoder = new HpackDecoder(200, 1024); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); + String longEnoughToBeEvicted = "012345678901234567890123456789012345678901234567890"; + HttpFields fields0 = new HttpFields(); - fields0.add("123456789012345678901234567890123456788901234567890", "value"); - fields0.add("foo", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); + fields0.add(longEnoughToBeEvicted, "value"); + fields0.add("foo", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); MetaData original0 = new MetaData(HttpVersion.HTTP_2, fields0); BufferUtil.clearToFill(buffer); encoder.encode(buffer, original0); BufferUtil.flipToFlush(buffer, 0); - MetaData decoded0 = (MetaData)decoder.decode(buffer); + MetaData decoded0 = decoder.decode(buffer); assertEquals(2, encoder.getHpackContext().size()); assertEquals(2, decoder.getHpackContext().size()); - assertEquals("123456789012345678901234567890123456788901234567890", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 1).getHttpField().getName()); - assertEquals("foo", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 0).getHttpField().getName()); + assertEquals(longEnoughToBeEvicted, encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 1).getHttpField().getName()); + assertEquals("foo", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length).getHttpField().getName()); - assertMetadataSame(original0, decoded0); + assertMetaDataSame(original0, decoded0); HttpFields fields1 = new HttpFields(); - fields1.add("123456789012345678901234567890123456788901234567890", "other_value"); + fields1.add(longEnoughToBeEvicted, "other_value"); fields1.add("x", "y"); MetaData original1 = new MetaData(HttpVersion.HTTP_2, fields1); BufferUtil.clearToFill(buffer); encoder.encode(buffer, original1); BufferUtil.flipToFlush(buffer, 0); - MetaData decoded1 = (MetaData)decoder.decode(buffer); - assertMetadataSame(original1, decoded1); + MetaData decoded1 = decoder.decode(buffer); + assertMetaDataSame(original1, decoded1); assertEquals(2, encoder.getHpackContext().size()); assertEquals(2, decoder.getHpackContext().size()); - assertEquals("x", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 0).getHttpField().getName()); + assertEquals("x", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length).getHttpField().getName()); assertEquals("foo", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 1).getHttpField().getName()); } - private void assertMetadataSame(MetaData.Response expected, MetaData.Response actual) + @Test + public void testHopHeadersAreRemoved() throws Exception + { + HpackEncoder encoder = new HpackEncoder(); + HpackDecoder decoder = new HpackDecoder(4096, 16384); + + HttpFields input = new HttpFields(); + input.put(HttpHeader.ACCEPT, "*"); + input.put(HttpHeader.CONNECTION, "TE, Upgrade, Custom"); + input.put("Custom", "Pizza"); + input.put(HttpHeader.KEEP_ALIVE, "true"); + input.put(HttpHeader.PROXY_CONNECTION, "foo"); + input.put(HttpHeader.TE, "1234567890abcdef"); + input.put(HttpHeader.TRANSFER_ENCODING, "chunked"); + input.put(HttpHeader.UPGRADE, "gold"); + + ByteBuffer buffer = BufferUtil.allocate(2048); + BufferUtil.clearToFill(buffer); + encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input)); + BufferUtil.flipToFlush(buffer, 0); + MetaData metaData = decoder.decode(buffer); + HttpFields output = metaData.getFields(); + + assertEquals(1, output.size()); + assertEquals("*", output.get(HttpHeader.ACCEPT)); + } + + @Test + public void testTETrailers() throws Exception + { + HpackEncoder encoder = new HpackEncoder(); + HpackDecoder decoder = new HpackDecoder(4096, 16384); + + HttpFields input = new HttpFields(); + input.put(HttpHeader.CONNECTION, "TE"); + String teValue = "trailers"; + input.put(HttpHeader.TE, teValue); + String trailerValue = "Custom"; + input.put(HttpHeader.TRAILER, trailerValue); + + ByteBuffer buffer = BufferUtil.allocate(2048); + BufferUtil.clearToFill(buffer); + encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input)); + BufferUtil.flipToFlush(buffer, 0); + MetaData metaData = decoder.decode(buffer); + HttpFields output = metaData.getFields(); + + assertEquals(2, output.size()); + assertEquals(teValue, output.get(HttpHeader.TE)); + assertEquals(trailerValue, output.get(HttpHeader.TRAILER)); + } + + private void assertMetaDataResponseSame(MetaData.Response expected, MetaData.Response actual) { assertThat("Response.status", actual.getStatus(), is(expected.getStatus())); assertThat("Response.reason", actual.getReason(), is(expected.getReason())); - assertMetadataSame((MetaData)expected, (MetaData)actual); + assertMetaDataSame(expected, actual); } - private void assertMetadataSame(MetaData expected, MetaData actual) + private void assertMetaDataSame(MetaData expected, MetaData actual) { assertThat("Metadata.contentLength", actual.getContentLength(), is(expected.getContentLength())); assertThat("Metadata.version" + ".version", actual.getHttpVersion(), is(expected.getHttpVersion())); - assertHttpFieldsSame("Metadata.fields", expected.getFields(), actual.getFields()); + assertHttpFieldsSame(expected.getFields(), actual.getFields()); } - private void assertHttpFieldsSame(String msg, HttpFields expected, HttpFields actual) + private void assertHttpFieldsSame(HttpFields expected, HttpFields actual) { - assertThat(msg + ".size", actual.size(), is(expected.size())); + assertThat("metaData.fields.size", actual.size(), is(expected.size())); for (HttpField actualField : actual) { @@ -203,7 +277,7 @@ public class HpackTest // during testing. continue; } - assertThat(msg + ".contains(" + actualField + ")", expected.contains(actualField), is(true)); + assertThat("metaData.fields.contains(" + actualField + ")", expected.contains(actualField), is(true)); } } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java index 802427f1866..5a947b8fd78 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java @@ -25,11 +25,13 @@ import java.util.stream.Stream; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.TypeUtil; +import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -77,8 +79,7 @@ public class HuffmanTest { String s = "bad '" + bad + "'"; - assertThrows(IllegalArgumentException.class, - () -> Huffman.octetsNeeded(s)); + assertThat(Huffman.octetsNeeded(s), Matchers.is(-1)); assertThrows(BufferOverflowException.class, () -> Huffman.encode(BufferUtil.allocate(32), s)); 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 5b458e07d71..d8ff0c3ee62 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 @@ -64,6 +64,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne private RateControl rateControl = new WindowRateControl(20, Duration.ofSeconds(1)); private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); private long streamIdleTimeout; + private boolean _useInputDirectByteBuffers; + private boolean _useOutputDirectByteBuffers; public AbstractHTTP2ServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration) { @@ -82,6 +84,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne this.httpConfiguration = Objects.requireNonNull(httpConfiguration); addBean(httpConfiguration); setInputBufferSize(Frame.DEFAULT_MAX_LENGTH + Frame.HEADER_LENGTH); + setUseInputDirectByteBuffers(httpConfiguration.isUseInputDirectByteBuffers()); + setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers()); } @ManagedAttribute("The HPACK dynamic table maximum size") @@ -192,6 +196,26 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne this.rateControl = rateControl; } + public boolean isUseInputDirectByteBuffers() + { + return _useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + _useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + public boolean isUseOutputDirectByteBuffers() + { + return _useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + _useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + public HttpConfiguration getHttpConfiguration() { return httpConfiguration; @@ -214,7 +238,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne { ServerSessionListener listener = newSessionListener(connector, endPoint); - Generator generator = new Generator(connector.getByteBufferPool(), getMaxDynamicTableSize(), getMaxHeaderBlockFragment()); + Generator generator = new Generator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), getMaxDynamicTableSize(), getMaxHeaderBlockFragment()); FlowControlStrategy flowControl = getFlowControlStrategyFactory().newFlowControlStrategy(); HTTP2ServerSession session = new HTTP2ServerSession(connector.getScheduler(), endPoint, generator, listener, flowControl); session.setMaxLocalStreams(getMaxConcurrentStreams()); @@ -235,6 +259,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener); + connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); + connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); connection.addListener(sessionContainer); return configure(connection, connector, endPoint); } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java index ed374cbd12d..5dee8dab902 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java @@ -289,6 +289,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection HttpTransportOverHTTP2 transport = new HttpTransportOverHTTP2(connector, this); transport.setStream(stream); channel = newServerHttpChannelOverHTTP2(connector, httpConfig, transport); + channel.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); if (LOG.isDebugEnabled()) LOG.debug("Creating channel {} for {}", channel, this); } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index da64dfd58aa..93ca8fd6626 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -57,6 +57,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ private boolean _expect100Continue; private boolean _delayedUntilContent; + private boolean _useOutputDirectByteBuffers; public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport) { @@ -68,6 +69,17 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ return getHttpTransport().getStream(); } + @Override + public boolean isUseOutputDirectByteBuffers() + { + return _useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + _useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + @Override public boolean isExpecting100Continue() { diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index 0dac59d2f06..3a99edb6a5e 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -62,14 +62,6 @@ public class HttpTransportOverHTTP2 implements HttpTransport this.connection = connection; } - @Override - public boolean isOptimizedForDirectBuffers() - { - // Because sent buffers are passed directly to the endpoint without - // copying we can defer to the endpoint - return connection.getEndPoint().isOptimizedForDirectBuffers(); - } - public IStream getStream() { return stream; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index d247a9ed5dd..2731a523519 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -327,12 +327,6 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint _connection = connection; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return false; - } - protected void reset() { _state.set(State.OPEN); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java index 14948563718..cdfd2fac200 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java @@ -36,7 +36,7 @@ import java.util.concurrent.locks.Condition; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.Locker; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Scheduler; /** @@ -68,24 +68,13 @@ public class ByteArrayEndPoint extends AbstractEndPoint private static final ByteBuffer EOF = BufferUtil.allocate(0); - private final Runnable _runFillable = new Runnable() - { - @Override - public void run() - { - getFillInterest().fillable(); - } - }; - - private final Locker _locker = new Locker(); - private final Condition _hasOutput = _locker.newCondition(); + private final Runnable _runFillable = () -> getFillInterest().fillable(); + private final AutoLock _lock = new AutoLock(); + private final Condition _hasOutput = _lock.newCondition(); private final Queue _inQ = new ArrayDeque<>(); private ByteBuffer _out; private boolean _growOutput; - /** - * - */ public ByteArrayEndPoint() { this(null, 0, null, null); @@ -138,7 +127,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void doShutdownOutput() { super.doShutdownOutput(); - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _hasOutput.signalAll(); } @@ -148,7 +137,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void doClose() { super.doClose(); - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _hasOutput.signalAll(); } @@ -180,7 +169,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint @Override protected void needsFillInterest() throws IOException { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (!isOpen()) throw new ClosedChannelException(); @@ -205,7 +194,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void addInput(ByteBuffer in) { boolean fillable = false; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (isEOF(_inQ.peek())) throw new RuntimeIOException(new EOFException()); @@ -238,7 +227,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void addInputAndExecute(ByteBuffer in) { boolean fillable = false; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (isEOF(_inQ.peek())) throw new RuntimeIOException(new EOFException()); @@ -263,7 +252,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint */ public ByteBuffer getOutput() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _out; } @@ -293,7 +282,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint { ByteBuffer b; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { b = _out; _out = BufferUtil.allocate(b.capacity()); @@ -314,7 +303,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint { ByteBuffer b; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { while (BufferUtil.isEmpty(_out) && !isOutputShutdown()) { @@ -351,7 +340,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint */ public void setOutput(ByteBuffer out) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _out = out; } @@ -359,7 +348,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint } /** - * @return true if there are bytes remaining to be read from the encoded input + * @return {@code true} if there are bytes remaining to be read from the encoded input */ public boolean hasMore() { @@ -373,7 +362,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public int fill(ByteBuffer buffer) throws IOException { int filled = 0; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { while (true) { @@ -418,7 +407,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public boolean flush(ByteBuffer... buffers) throws IOException { boolean flushed = true; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (!isOpen()) throw new IOException("CLOSED"); @@ -467,7 +456,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint @Override public void reset() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _inQ.clear(); _hasOutput.signalAll(); @@ -507,7 +496,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint int q; ByteBuffer b; String o; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { q = _inQ.size(); b = _inQ.peek(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java index 0d58fde8245..b4c40418676 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java @@ -175,12 +175,6 @@ public abstract class ChannelEndPoint extends AbstractEndPoint implements Manage _gather = (channel instanceof GatheringByteChannel) ? (GatheringByteChannel)channel : null; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return true; - } - @Override public boolean isOpen() { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 875ad2521da..74212828d9d 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -269,13 +269,6 @@ public interface EndPoint extends Closeable */ void onClose(Throwable cause); - /** - * Is the endpoint optimized for DirectBuffer usage - * - * @return True if direct buffers can be used optimally. - */ - boolean isOptimizedForDirectBuffers(); - /** * Upgrade connections. * Close the old connection, update the endpoint and open the new connection. 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 da76d773ed9..36057b2c9cb 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 @@ -43,6 +43,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; @@ -122,12 +123,20 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable start._started.await(); } + protected void onSelectFailed(Throwable cause) + { + // override to change behavior + } + public int size() { Selector s = _selector; if (s == null) return 0; - return s.keys().size(); + Set keys = s.keys(); + if (keys == null) + return 0; + return keys.size(); } @Override @@ -135,7 +144,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { // doStop might be called for a failed managedSelector, // We do not want to wait twice, so we only stop once for each start - if (_started.compareAndSet(true, false)) + if (_started.compareAndSet(true, false) && _selector != null) { // Close connections, but only wait a single selector cycle for it to take effect CloseConnections closeConnections = new CloseConnections(); @@ -210,7 +219,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable catch (RejectedExecutionException x) { if (task instanceof Closeable) - closeNoExceptions((Closeable)task); + IO.close((Closeable)task); } } @@ -246,17 +255,14 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } } - private static void closeNoExceptions(Closeable closeable) + protected void endPointOpened(EndPoint endPoint) { - try - { - if (closeable != null) - closeable.close(); - } - catch (Throwable x) - { - LOG.ignore(x); - } + _selectorManager.endPointOpened(endPoint); + } + + protected void endPointClosed(EndPoint endPoint) + { + _selectorManager.endPointClosed(endPoint); } private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException @@ -266,7 +272,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable endPoint.setConnection(connection); selectionKey.attach(endPoint); endPoint.onOpen(); - _selectorManager.endPointOpened(endPoint); + endPointOpened(endPoint); _selectorManager.connectionOpened(connection); if (LOG.isDebugEnabled()) LOG.debug("Created {}", endPoint); @@ -496,15 +502,19 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } catch (Throwable x) { + IO.close(_selector); _selector = null; + if (isRunning()) - LOG.warn(x); + { + LOG.warn("Fatal select() failure", x); + onSelectFailed(x); + } else { LOG.warn(x.toString()); LOG.debug(x); } - closeNoExceptions(_selector); } return false; } @@ -541,13 +551,13 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { LOG.debug("Ignoring cancelled key for channel {}", key.channel()); if (attachment instanceof EndPoint) - closeNoExceptions((EndPoint)attachment); + IO.close((EndPoint)attachment); } catch (Throwable x) { LOG.warn("Could not process key for channel " + key.channel(), x); if (attachment instanceof EndPoint) - closeNoExceptions((EndPoint)attachment); + IO.close((EndPoint)attachment); } } else @@ -556,7 +566,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel()); Object attachment = key.attachment(); if (attachment instanceof EndPoint) - closeNoExceptions((EndPoint)attachment); + IO.close((EndPoint)attachment); } } return null; @@ -661,7 +671,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } catch (Throwable x) { - closeNoExceptions(_channel); + IO.close(_channel); LOG.warn(x); } } @@ -683,7 +693,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } catch (Throwable x) { - closeNoExceptions(channel); + IO.close(channel); LOG.warn("Accept failed for channel " + channel, x); } @@ -722,7 +732,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable public void close() { LOG.debug("closed accept of {}", channel); - closeNoExceptions(channel); + IO.close(channel); } @Override @@ -735,7 +745,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } catch (Throwable x) { - closeNoExceptions(channel); + IO.close(channel); _selectorManager.onAcceptFailed(channel, x); LOG.debug(x); } @@ -758,7 +768,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable protected void failed(Throwable failure) { - closeNoExceptions(channel); + IO.close(channel); LOG.warn(String.valueOf(failure)); LOG.debug(failure); _selectorManager.onAcceptFailed(channel, failure); @@ -808,7 +818,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable if (failed.compareAndSet(false, true)) { timeout.cancel(); - closeNoExceptions(channel); + IO.close(channel); ManagedSelector.this._selectorManager.connectionFailed(channel, failure, attachment); } } @@ -864,12 +874,12 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { if (_closed == null) { - closeNoExceptions(closeable); + IO.close(closeable); } else if (!_closed.contains(closeable)) { _closed.add(closeable); - closeNoExceptions(closeable); + IO.close(closeable); } } } @@ -894,12 +904,12 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { Object attachment = key.attachment(); if (attachment instanceof EndPoint) - closeNoExceptions((EndPoint)attachment); + IO.close((EndPoint)attachment); } } _selector = null; - closeNoExceptions(selector); + IO.close(selector); _stopped.countDown(); } } @@ -924,7 +934,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } catch (Throwable failure) { - closeNoExceptions(_connect.channel); + IO.close(_connect.channel); LOG.warn(String.valueOf(failure)); LOG.debug(failure); _connect.failed(failure); @@ -957,7 +967,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable Connection connection = endPoint.getConnection(); if (connection != null) _selectorManager.connectionClosed(connection, cause); - _selectorManager.endPointClosed(endPoint); + ManagedSelector.this.endPointClosed(endPoint); } @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java index 45b0be514fd..bab0b867fba 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java @@ -95,7 +95,6 @@ public class SslClientConnectionFactory implements ClientConnectionFactory context.put(SSL_ENGINE_CONTEXT_KEY, engine); SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine); - endPoint.setConnection(sslConnection); EndPoint appEndPoint = sslConnection.getDecryptedEndPoint(); appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context)); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index f20d0b03f18..4e830f3fc3f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -146,7 +146,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr @Override public InvocationType getInvocationType() { - return getDecryptedEndPoint().getFillInterest().getCallbackInvocationType(); + return _decryptedEndPoint.getFillInterest().getCallbackInvocationType(); } }; @@ -364,6 +364,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr public class DecryptedEndPoint extends AbstractEndPoint { private final Callback _incompleteWriteCallback = new IncompleteWriteCallback(); + private Throwable _failure; public DecryptedEndPoint() { @@ -451,14 +452,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr LOG.debug("onFillableFail {}", SslConnection.this, failure); _fillState = FillState.IDLE; - switch (_flushState) + if (_flushState == FlushState.WAIT_FOR_FILL) { - case WAIT_FOR_FILL: - _flushState = FlushState.IDLE; - fail = true; - break; - default: - break; + _flushState = FlushState.IDLE; + fail = true; } } @@ -529,12 +526,14 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr case NEED_WRAP: if (_flushState == FlushState.IDLE && flush(BufferUtil.EMPTY_BUFFER)) { + Throwable failure = _failure; + if (failure != null) + rethrow(failure); if (_sslEngine.isInboundDone()) - // TODO this is probably a JVM bug, work around it by -1 - return -1; + return filled = -1; continue; } - // handle in needsFillInterest + // Handle in needsFillInterest(). return filled = 0; default: @@ -598,6 +597,9 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr switch (unwrap) { case CLOSED: + Throwable failure = _failure; + if (failure != null) + rethrow(failure); return filled = -1; case BUFFER_UNDERFLOW: @@ -606,7 +608,14 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr _underflown = true; if (netFilled < 0 && _sslEngine.getUseClientMode()) { - closeInbound(); + Throwable closeFailure = closeInbound(); + if (_flushState == FlushState.WAIT_FOR_FILL) + { + Throwable handshakeFailure = new SSLHandshakeException("Abruptly closed by peer"); + if (closeFailure != null) + handshakeFailure.initCause(closeFailure); + throw handshakeFailure; + } return filled = -1; } return filled = netFilled; @@ -639,15 +648,14 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } catch (Throwable x) { - handshakeFailed(x); - + Throwable failure = handleException(x, "fill"); + handshakeFailed(failure); if (_flushState == FlushState.WAIT_FOR_FILL) { _flushState = FlushState.IDLE; - getExecutor().execute(() -> _decryptedEndPoint.getWriteFlusher().onFail(x)); + getExecutor().execute(() -> _decryptedEndPoint.getWriteFlusher().onFail(failure)); } - - throw x; + throw failure; } finally { @@ -676,10 +684,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug(SslConnection.this.toString(), x); close(x); - throw x; + rethrow(x); + // Never reached. + throw new AssertionError(); } } @@ -694,15 +702,18 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr synchronized (_decryptedEndPoint) { if (LOG.isDebugEnabled()) - { - LOG.debug(">needFillInterest uf={} {}", _underflown, SslConnection.this); - LOG.debug("ei={} di={}", BufferUtil.toDetailString(_encryptedInput), BufferUtil.toDetailString(_decryptedInput)); - } + LOG.debug(">needFillInterest s={}/{} uf={} ei={} di={} {}", + _flushState, + _fillState, + _underflown, + BufferUtil.toDetailString(_encryptedInput), + BufferUtil.toDetailString(_decryptedInput), + SslConnection.this); if (_fillState != FillState.IDLE) return; - // Fillable if we have decrypted Input OR encrypted input that has not yet been underflown. + // Fillable if we have decrypted input OR enough encrypted input. fillable = BufferUtil.hasContent(_decryptedInput) || (BufferUtil.hasContent(_encryptedInput) && !_underflown); HandshakeStatus status = _sslEngine.getHandshakeStatus(); @@ -799,23 +810,25 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } } - private void closeInbound() throws SSLException + private Throwable closeInbound() throws SSLException { HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus(); try { _sslEngine.closeInbound(); + return null; } catch (SSLException x) { if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && !isAllowMissingCloseMessage()) throw x; - else - LOG.ignore(x); + LOG.ignore(x); + return x; } catch (Throwable x) { LOG.ignore(x); + return x; } } @@ -966,8 +979,9 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } catch (Throwable x) { - handshakeFailed(x); - throw x; + Throwable failure = handleException(x, "flush"); + handshakeFailed(failure); + throw failure; } finally { @@ -979,10 +993,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug(SslConnection.this.toString(), x); close(x); - throw x; + rethrow(x); + // Never reached. + throw new AssertionError(); } } @@ -1092,7 +1106,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr boolean ishut = endp.isInputShutdown(); boolean oshut = endp.isOutputShutdown(); if (LOG.isDebugEnabled()) - LOG.debug("shutdownOutput: {} oshut={}, ishut={} {}", SslConnection.this, oshut, ishut); + LOG.debug("shutdownOutput: {} oshut={}, ishut={}", SslConnection.this, oshut, ishut); closeOutbound(); @@ -1110,15 +1124,11 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { if (!flush(BufferUtil.EMPTY_BUFFER) && !close) { - Thread.yield(); - // if we still can't flush, but we are not closing the endpoint, + // If we still can't flush, but we are not closing the endpoint, // let's just flush the encrypted output in the background. - // and continue as if we are closed. The assumption here is that - // the encrypted buffer will contain the entire close handshake - // and that a call to flush(EMPTY_BUFFER) is not needed. - endp.write(Callback.from(() -> - { - }, t -> endp.close()), _encryptedOutput); + ByteBuffer write = _encryptedOutput; + if (BufferUtil.hasContent(write)) + endp.write(Callback.from(Callback.NOOP::succeeded, t -> endp.close()), write); } } @@ -1284,6 +1294,37 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr return TLS_1_3.equals(protocol); } + private Throwable handleException(Throwable x, String context) + { + synchronized (_decryptedEndPoint) + { + if (_failure == null) + { + _failure = x; + if (LOG.isDebugEnabled()) + LOG.debug(this + " stored " + context + " exception", x); + } + else if (x != _failure) + { + _failure.addSuppressed(x); + if (LOG.isDebugEnabled()) + LOG.debug(this + " suppressed " + context + " exception", x); + } + return _failure; + } + } + + private void rethrow(Throwable x) throws IOException + { + if (x instanceof RuntimeException) + throw (RuntimeException)x; + if (x instanceof Error) + throw (Error)x; + if (x instanceof IOException) + throw (IOException)x; + throw new IOException(x); + } + @Override public String toString() { diff --git a/jetty-io/src/test/resources/jetty-logging.properties b/jetty-io/src/test/resources/jetty-logging.properties index 257743ed54b..0e7fd71dc25 100644 --- a/jetty-io/src/test/resources/jetty-logging.properties +++ b/jetty-io/src/test/resources/jetty-logging.properties @@ -1,5 +1,5 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=INFO +#org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG #org.eclipse.jetty.io.ManagedSelector.LEVEL=DEBUG #org.eclipse.jetty.io.ssl.SslConnection.LEVEL=DEBUG diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java similarity index 97% rename from jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java rename to jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java index 16ed2106783..d00b0f63d88 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.security.jaspi.modules; +package org.eclipse.jetty.security.jaspi; import java.io.IOException; import java.util.Map; @@ -31,11 +31,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.security.jaspi.modules.BaseAuthModule; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.security.Constraint; -@Deprecated public class BasicAuthModule extends BaseAuthModule { private static final Logger LOG = Log.getLogger(BasicAuthModule.class); diff --git a/jetty-jaspi/src/test/resources/jaspi.xml b/jetty-jaspi/src/test/resources/jaspi.xml index 23a2ba5c7ed..1e31d2c996b 100644 --- a/jetty-jaspi/src/test/resources/jaspi.xml +++ b/jetty-jaspi/src/test/resources/jaspi.xml @@ -10,7 +10,7 @@ true - org.eclipse.jetty.security.jaspi.modules.BasicAuthModule + org.eclipse.jetty.security.jaspi.BasicAuthModule org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java index 9749cb16c04..ca2f83df9aa 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java @@ -28,8 +28,9 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.quickstart.QuickStartConfiguration; +import org.eclipse.jetty.quickstart.QuickStartConfiguration.Mode; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -92,30 +93,23 @@ public class JettyEffectiveWebXml extends JettyRunMojo configureWebApplication(); //set the webapp up to do very little other than generate the quickstart-web.xml + if (effectiveWebXml == null) + { + deleteOnExit = true; + effectiveWebXml = new File(target, "effective-web.xml"); + effectiveWebXml.deleteOnExit(); + } + Resource descriptor = Resource.newResource(effectiveWebXml); + if (!effectiveWebXml.getParentFile().exists()) + effectiveWebXml.getParentFile().mkdirs(); + if (!effectiveWebXml.exists()) + effectiveWebXml.createNewFile(); + webApp.setCopyWebDir(false); webApp.setCopyWebInf(false); - webApp.setGenerateQuickStart(true); - - //if the user didn't nominate a file to generate into, pick the name and - //make sure that it is deleted on exit - if (webApp.getQuickStartWebDescriptor() == null) - { - if (effectiveWebXml == null) - { - deleteOnExit = true; - effectiveWebXml = new File(target, "effective-web.xml"); - effectiveWebXml.deleteOnExit(); - } - - Resource descriptor = Resource.newResource(effectiveWebXml); - - if (!effectiveWebXml.getParentFile().exists()) - effectiveWebXml.getParentFile().mkdirs(); - if (!effectiveWebXml.exists()) - effectiveWebXml.createNewFile(); - - webApp.setQuickStartWebDescriptor(descriptor); - } + webApp.addConfiguration(new QuickStartConfiguration()); + webApp.setAttribute(QuickStartConfiguration.MODE, Mode.GENERATE); + webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, descriptor); ServerSupport.addWebApplication(server, webApp); @@ -158,7 +152,7 @@ public class JettyEffectiveWebXml extends JettyRunMojo try { //just show the result in the log - getLog().info(IO.toString(webApp.getQuickStartWebDescriptor().getInputStream())); + getLog().info(IO.toString(((Resource)webApp.getAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML)).getInputStream())); } catch (Exception e) { diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java index cd255b6962c..feada7f24e0 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java @@ -43,6 +43,8 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.quickstart.QuickStartConfiguration; +import org.eclipse.jetty.quickstart.QuickStartConfiguration.Mode; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; @@ -209,22 +211,19 @@ public class JettyRunForkedMojo extends JettyRunMojo configureWebApplication(); //set the webapp up to do very little other than generate the quickstart-web.xml + if (forkWebXml == null) + forkWebXml = new File(target, "fork-web.xml"); + + if (!forkWebXml.getParentFile().exists()) + forkWebXml.getParentFile().mkdirs(); + if (!forkWebXml.exists()) + forkWebXml.createNewFile(); + + webApp.addConfiguration(new MavenQuickStartConfiguration()); + webApp.setAttribute(QuickStartConfiguration.MODE, Mode.GENERATE); + webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, Resource.newResource(forkWebXml)); webApp.setCopyWebDir(false); - webApp.setCopyWebInf(false); - webApp.setGenerateQuickStart(true); - - if (webApp.getQuickStartWebDescriptor() == null) - { - if (forkWebXml == null) - forkWebXml = new File(target, "fork-web.xml"); - - if (!forkWebXml.getParentFile().exists()) - forkWebXml.getParentFile().mkdirs(); - if (!forkWebXml.exists()) - forkWebXml.createNewFile(); - - webApp.setQuickStartWebDescriptor(Resource.newResource(forkWebXml)); - } + webApp.setCopyWebInf(false); //add webapp to our fake server instance ServerSupport.addWebApplication(server, webApp); @@ -240,11 +239,10 @@ public class JettyRunForkedMojo extends JettyRunMojo //leave everything unpacked for the forked process to use webApp.setPersistTempDirectory(true); + File props = null; webApp.start(); //just enough to generate the quickstart - //save config of the webapp BEFORE we stop - final File props = prepareConfiguration(); - + props = prepareConfiguration(); webApp.stop(); if (tpool != null) @@ -311,7 +309,7 @@ public class JettyRunForkedMojo extends JettyRunMojo if (PluginLog.getLog().isDebugEnabled()) PluginLog.getLog().debug("Forked cli:" + Arrays.toString(cmd.toArray())); - + PluginLog.getLog().info("Forked process starting"); //set up extra environment vars if there are any diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java index 79c8509ad70..302d267f00d 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java @@ -34,7 +34,6 @@ import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.quickstart.QuickStartConfiguration; -import org.eclipse.jetty.quickstart.QuickStartConfiguration.Mode; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletHolder; @@ -43,10 +42,10 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.Configurations; import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; @@ -105,8 +104,6 @@ public class JettyWebAppContext extends WebAppContext */ private boolean _baseAppFirst = true; - private boolean _isGenerateQuickStart; - public JettyWebAppContext() throws Exception { super(); @@ -117,6 +114,8 @@ public class JettyWebAppContext extends WebAppContext addConfiguration(new EnvConfiguration()); addConfiguration(new PlusConfiguration()); addConfiguration(new AnnotationConfiguration()); + + setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "origin"); } public void setContainerIncludeJarPattern(String pattern) @@ -210,27 +209,6 @@ public class JettyWebAppContext extends WebAppContext return attr == null ? null : attr.toString(); } - /** - * Toggle whether or not the origin attribute will be generated into the - * xml. - * - * @param generateOrigin if true then the origin of each xml element is - * added, otherwise it is omitted. - */ - public void setGenerateOrigin(boolean generateOrigin) - { - setAttribute(QuickStartConfiguration.GENERATE_ORIGIN, generateOrigin); - } - - /** - * @return true if the origin attribute will be generated, false otherwise - */ - public boolean isGenerateOrigin() - { - Object attr = getAttribute(QuickStartConfiguration.GENERATE_ORIGIN); - return attr == null ? false : Boolean.valueOf(attr.toString()); - } - public List getOverlays() { return _overlays; @@ -246,35 +224,6 @@ public class JettyWebAppContext extends WebAppContext return _baseAppFirst; } - /** - * Set the file to use into which to generate the quickstart output. - * - * @param quickStartWebXml the full path to the file to use - */ - public void setQuickStartWebDescriptor(String quickStartWebXml) throws Exception - { - setQuickStartWebDescriptor(Resource.newResource(quickStartWebXml)); - } - - /** - * Set the Resource to use into which to generate the quickstart output. - */ - protected void setQuickStartWebDescriptor(Resource quickStartWebXml) - { - setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, quickStartWebXml.toString()); - } - - public Resource getQuickStartWebDescriptor() throws Exception - { - Object o = getAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML); - if (o == null) - return null; - else if (o instanceof Resource) - return (Resource)o; - else - return Resource.newResource((String)o); - } - /** * This method is provided as a convenience for jetty maven plugin * configuration @@ -307,41 +256,9 @@ public class JettyWebAppContext extends WebAppContext return _webInfClasses; } - /** - * If true, a quickstart for the webapp is generated. - * - * @param quickStart if true the quickstart is generated, false otherwise - */ - public void setGenerateQuickStart(boolean quickStart) - { - _isGenerateQuickStart = quickStart; - } - - public boolean isGenerateQuickStart() - { - return _isGenerateQuickStart; - } - @Override public void doStart() throws Exception { - - // choose if this will be a quickstart or normal start - if (!isGenerateQuickStart() && getQuickStartWebDescriptor() != null) - { - MavenQuickStartConfiguration quickStart = new MavenQuickStartConfiguration(); - quickStart.setMode(Mode.QUICKSTART); - quickStart.setQuickStartWebXml(getQuickStartWebDescriptor()); - addConfiguration(quickStart); - } - else if (isGenerateQuickStart()) - { - MavenQuickStartConfiguration quickStart = new MavenQuickStartConfiguration(); - quickStart.setMode(Mode.GENERATE); - quickStart.setQuickStartWebXml(getQuickStartWebDescriptor()); - addConfiguration(quickStart); - } - // Set up the pattern that tells us where the jars are that need // scanning @@ -393,22 +310,27 @@ public class JettyWebAppContext extends WebAppContext } @Override - protected void loadConfigurations() + protected Configurations newConfigurations() { - super.loadConfigurations(); - try + Configurations configurations = super.newConfigurations(); + if (getJettyEnvXml() != null) { - // inject configurations with config from maven plugin - for (Configuration c : getWebAppConfigurations()) + try { - if (c instanceof EnvConfiguration && getJettyEnvXml() != null) - ((EnvConfiguration)c).setJettyEnvResource(new PathResource(new File(getJettyEnvXml()))); + // inject configurations with config from maven plugin + for (Configuration c : configurations) + { + if (c instanceof EnvConfiguration) + ((EnvConfiguration)c).setJettyEnvResource(Resource.newResource(getJettyEnvXml())); + } + } + catch (IOException e) + { + throw new RuntimeException(e); } } - catch (Exception e) - { - throw new RuntimeException(e); - } + + return configurations; } @Override diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java index 597c313555e..63d643a7a24 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java @@ -18,15 +18,12 @@ package org.eclipse.jetty.maven.plugin; -import java.io.File; - import org.eclipse.jetty.quickstart.QuickStartConfiguration; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; -import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; /** @@ -36,56 +33,6 @@ public class MavenQuickStartConfiguration extends QuickStartConfiguration { private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class); - private Resource _quickStartWebXml; //the descriptor to use for starting/generating quickstart - - public void setQuickStartWebXml(Resource quickStartWebXml) - { - _quickStartWebXml = quickStartWebXml; - } - - @Override - public Resource getQuickStartWebXml(WebAppContext context) throws Exception - { - if (_quickStartWebXml == null) - return super.getQuickStartWebXml(context); - - return _quickStartWebXml; - } - - @Override - public void preConfigure(WebAppContext context) throws Exception - { - //check that webapp is suitable for quick start - if (context.getBaseResource() == null) - throw new IllegalStateException("No location for webapp"); - - //look for quickstart-web.xml in WEB-INF of webapp - Resource quickStartWebXml = getQuickStartWebXml(context); - if (LOG.isDebugEnabled()) - LOG.debug("quickStartWebXml={}", quickStartWebXml); - super.preConfigure(context); - } - - @Override - public void configure(WebAppContext context) throws Exception - { - JettyWebAppContext jwac = (JettyWebAppContext)context; - - //put the classes dir and all dependencies into the classpath - if (jwac.getClassPathFiles() != null) - { - if (LOG.isDebugEnabled()) - LOG.debug("Setting up classpath ..."); - for (File classPathFile : jwac.getClassPathFiles()) - { - ((WebAppClassLoader)context.getClassLoader()).addClassPath(classPathFile.getCanonicalPath()); - } - } - - //Set up the quickstart environment for the context - super.configure(context); - } - @Override public void deconfigure(WebAppContext context) throws Exception { diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java index 7d76d8124a5..9c25a5c8328 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.eclipse.jetty.quickstart.QuickStartConfiguration; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ShutdownMonitor; @@ -71,14 +72,9 @@ public class Starter //configure webapp from properties file describing unassembled webapp configureWebApp(); - - //make it a quickstart if the quickstart-web.xml file exists - if (webApp.getTempDirectory() != null) - { - File qs = new File(webApp.getTempDirectory(), "quickstart-web.xml"); - if (qs.exists() && qs.isFile()) - webApp.setQuickStartWebDescriptor(Resource.newResource(qs)); - } + + webApp.addConfiguration(new QuickStartConfiguration()); + webApp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); ServerSupport.addWebApplication(server, webApp); @@ -232,7 +228,9 @@ public class Starter public static final void main(String[] args) { if (args == null) + { System.exit(1); + } Starter starter = null; try diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java index d5ca0c745e6..fed62949ce8 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.eclipse.jetty.quickstart.QuickStartConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; @@ -71,9 +72,10 @@ public class WebAppPropertyConverter props.put("web.xml", webApp.getDescriptor()); } - if (webApp.getQuickStartWebDescriptor() != null) + Object tmp = webApp.getAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML); + if (tmp != null) { - props.put("quickstart.web.xml", webApp.getQuickStartWebDescriptor().getFile().getAbsolutePath()); + props.put("quickstart.web.xml", tmp.toString()); } //sort out the context path @@ -183,11 +185,10 @@ public class WebAppPropertyConverter if (!StringUtil.isBlank(str)) webApp.setDescriptor(str); - //TODO the WebAppStarter class doesn't set up the QUICKSTART_CONFIGURATION_CLASSES, but the Starter class does!!! str = props.getProperty("quickstart.web.xml"); if (!StringUtil.isBlank(str)) { - webApp.setQuickStartWebDescriptor(Resource.newResource(new File(str))); + webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, Resource.newResource(str)); } // - the tmp directory diff --git a/jetty-openid/pom.xml b/jetty-openid/pom.xml new file mode 100644 index 00000000000..48dcc6adaf1 --- /dev/null +++ b/jetty-openid/pom.xml @@ -0,0 +1,64 @@ + + + org.eclipse.jetty + jetty-project + 10.0.0-SNAPSHOT + + + 4.0.0 + jetty-openid + Jetty :: OpenID + Jetty OpenID Connect infrastructure + http://www.eclipse.org/jetty + + + ${project.groupId}.openid + + + + + + org.codehaus.mojo + findbugs-maven-plugin + + org.eclipse.jetty.security.openid.* + + + + + + + + org.eclipse.jetty + jetty-server + ${project.version} + + + org.eclipse.jetty + jetty-security + ${project.version} + + + org.eclipse.jetty + jetty-util-ajax + ${project.version} + + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + test + + + org.eclipse.jetty + jetty-client + ${project.version} + test + + + diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml new file mode 100644 index 00000000000..65506e85ca8 --- /dev/null +++ b/jetty-openid/src/main/config/etc/jetty-openid.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod new file mode 100644 index 00000000000..c9a4cc7476e --- /dev/null +++ b/jetty-openid/src/main/config/modules/openid.mod @@ -0,0 +1,34 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Adds OpenId Connect authentication. + +[depend] +security + +[lib] +lib/jetty-openid-${jetty.version}.jar +lib/jetty-util-ajax-${jetty.version}.jar + +[files] +basehome:modules/openid/openid-baseloginservice.xml|etc/openid-baseloginservice.xml + +[xml] +etc/openid-baseloginservice.xml +etc/jetty-openid.xml + +[ini-template] +## The OpenID Identity Provider +# jetty.openid.openIdProvider=https://accounts.google.com/ + +## The Client Identifier +# jetty.openid.clientId=test1234.apps.googleusercontent.com + +## The Client Secret +# jetty.openid.clientSecret=XT_Mafv_aUCGheuCaKY8P + +## Additional Scopes to Request +# jetty.openid.scopes=email,profile + +## Whether to Authenticate users not found by base LoginService +# jetty.openid.authenticateNewUsers=false \ No newline at end of file diff --git a/jetty-openid/src/main/config/modules/openid/openid-baseloginservice.xml b/jetty-openid/src/main/config/modules/openid/openid-baseloginservice.xml new file mode 100644 index 00000000000..d87b88f7016 --- /dev/null +++ b/jetty-openid/src/main/config/modules/openid/openid-baseloginservice.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java new file mode 100644 index 00000000000..41fce0be5ee --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java @@ -0,0 +1,491 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.security.UserAuthentication; +import org.eclipse.jetty.security.authentication.DeferredAuthentication; +import org.eclipse.jetty.security.authentication.LoginAuthenticator; +import org.eclipse.jetty.security.authentication.SessionAuthentication; +import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.server.Authentication.User; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Constraint; + +/** + *

Implements authentication using OpenId Connect on top of OAuth 2.0. + * + *

The OpenIdAuthenticator redirects unauthenticated requests to the OpenID Connect Provider. The End-User is + * eventually redirected back with an Authorization Code to the /j_security_check URI within the context. + * The Authorization Code is then used to authenticate the user through the {@link OpenIdCredentials} and {@link OpenIdLoginService}. + *

+ *

+ * Once a user is authenticated the OpenID Claims can be retrieved through an attribute on the session with the key {@link #CLAIMS}. + * The full response containing the OAuth 2.0 Access Token can be obtained with the session attribute {@link #RESPONSE}. + *

+ *

{@link SessionAuthentication} is then used to wrap Authentication results so that they are associated with the session.

+ */ +public class OpenIdAuthenticator extends LoginAuthenticator +{ + private static final Logger LOG = Log.getLogger(OpenIdAuthenticator.class); + + public static final String CLAIMS = "org.eclipse.jetty.security.openid.claims"; + public static final String RESPONSE = "org.eclipse.jetty.security.openid.response"; + public static final String ERROR_PAGE = "org.eclipse.jetty.security.openid.error_page"; + public static final String J_URI = "org.eclipse.jetty.security.openid.URI"; + public static final String J_POST = "org.eclipse.jetty.security.openid.POST"; + public static final String J_METHOD = "org.eclipse.jetty.security.openid.METHOD"; + public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token"; + public static final String J_SECURITY_CHECK = "/j_security_check"; + + private OpenIdConfiguration _configuration; + private String _errorPage; + private String _errorPath; + private boolean _alwaysSaveUri; + + public OpenIdAuthenticator() + { + } + + public OpenIdAuthenticator(OpenIdConfiguration configuration, String errorPage) + { + this._configuration = configuration; + if (errorPage != null) + setErrorPage(errorPage); + } + + @Override + public void setConfiguration(AuthConfiguration configuration) + { + super.setConfiguration(configuration); + + String error = configuration.getInitParameter(ERROR_PAGE); + if (error != null) + setErrorPage(error); + + if (_configuration != null) + return; + + LoginService loginService = configuration.getLoginService(); + if (!(loginService instanceof OpenIdLoginService)) + throw new IllegalArgumentException("invalid LoginService"); + this._configuration = ((OpenIdLoginService)loginService).getConfiguration(); + } + + @Override + public String getAuthMethod() + { + return Constraint.__OPENID_AUTH; + } + + /** + * If true, uris that cause a redirect to a login page will always + * be remembered. If false, only the first uri that leads to a login + * page redirect is remembered. + * + * @param alwaysSave true to always save the uri + */ + public void setAlwaysSaveUri(boolean alwaysSave) + { + _alwaysSaveUri = alwaysSave; + } + + public boolean isAlwaysSaveUri() + { + return _alwaysSaveUri; + } + + private void setErrorPage(String path) + { + if (path == null || path.trim().length() == 0) + { + _errorPath = null; + _errorPage = null; + } + else + { + if (!path.startsWith("/")) + { + LOG.warn("error-page must start with /"); + path = "/" + path; + } + _errorPage = path; + _errorPath = path; + + if (_errorPath.indexOf('?') > 0) + _errorPath = _errorPath.substring(0, _errorPath.indexOf('?')); + } + } + + @Override + public UserIdentity login(String username, Object credentials, ServletRequest request) + { + if (LOG.isDebugEnabled()) + LOG.debug("login {} {} {}", username, credentials, request); + + UserIdentity user = super.login(username, credentials, request); + if (user != null) + { + HttpSession session = ((HttpServletRequest)request).getSession(); + Authentication cached = new SessionAuthentication(getAuthMethod(), user, credentials); + session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached); + session.setAttribute(CLAIMS, ((OpenIdCredentials)credentials).getClaims()); + session.setAttribute(RESPONSE, ((OpenIdCredentials)credentials).getResponse()); + } + return user; + } + + @Override + public void logout(ServletRequest request) + { + super.logout(request); + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpSession session = httpRequest.getSession(false); + + if (session == null) + return; + + //clean up session + session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED); + session.removeAttribute(CLAIMS); + session.removeAttribute(RESPONSE); + } + + @Override + public void prepareRequest(ServletRequest request) + { + //if this is a request resulting from a redirect after auth is complete + //(ie its from a redirect to the original request uri) then due to + //browser handling of 302 redirects, the method may not be the same as + //that of the original request. Replace the method and original post + //params (if it was a post). + // + //See Servlet Spec 3.1 sec 13.6.3 + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpSession session = httpRequest.getSession(false); + if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null) + return; //not authenticated yet + + String juri = (String)session.getAttribute(J_URI); + if (juri == null || juri.length() == 0) + return; //no original uri saved + + String method = (String)session.getAttribute(J_METHOD); + if (method == null || method.length() == 0) + return; //didn't save original request method + + StringBuffer buf = httpRequest.getRequestURL(); + if (httpRequest.getQueryString() != null) + buf.append("?").append(httpRequest.getQueryString()); + + if (!juri.equals(buf.toString())) + return; //this request is not for the same url as the original + + //restore the original request's method on this request + if (LOG.isDebugEnabled()) + LOG.debug("Restoring original method {} for {} with method {}", method, juri, httpRequest.getMethod()); + Request baseRequest = Request.getBaseRequest(request); + baseRequest.setMethod(method); + } + + @Override + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException + { + final HttpServletRequest request = (HttpServletRequest)req; + final HttpServletResponse response = (HttpServletResponse)res; + final Request baseRequest = Request.getBaseRequest(request); + final Response baseResponse = baseRequest.getResponse(); + + String uri = request.getRequestURI(); + if (uri == null) + uri = URIUtil.SLASH; + + mandatory |= isJSecurityCheck(uri); + if (!mandatory) + return new DeferredAuthentication(this); + + if (isErrorPage(URIUtil.addPaths(request.getServletPath(), request.getPathInfo())) && !DeferredAuthentication.isDeferred(response)) + return new DeferredAuthentication(this); + + try + { + // Handle a request for authentication. + if (isJSecurityCheck(uri)) + { + String authCode = request.getParameter("code"); + if (authCode != null) + { + // Verify anti-forgery state token + String state = request.getParameter("state"); + String antiForgeryToken = (String)request.getSession().getAttribute(CSRF_TOKEN); + if (antiForgeryToken == null || !antiForgeryToken.equals(state)) + { + LOG.warn("auth failed 403: invalid state parameter"); + if (response != null) + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return Authentication.SEND_FAILURE; + } + + // Attempt to login with the provided authCode + OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration); + UserIdentity user = login(null, credentials, request); + HttpSession session = request.getSession(false); + if (user != null) + { + // Redirect to original request + String nuri; + synchronized (session) + { + nuri = (String)session.getAttribute(J_URI); + + if (nuri == null || nuri.length() == 0) + { + nuri = request.getContextPath(); + if (nuri.length() == 0) + nuri = URIUtil.SLASH; + } + } + OpenIdAuthentication openIdAuth = new OpenIdAuthentication(getAuthMethod(), user); + if (LOG.isDebugEnabled()) + LOG.debug("authenticated {}->{}", openIdAuth, nuri); + + response.setContentLength(0); + int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); + baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(nuri)); + return openIdAuth; + } + } + + // not authenticated + if (LOG.isDebugEnabled()) + LOG.debug("OpenId authentication FAILED"); + if (_errorPage == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("auth failed 403"); + if (response != null) + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("auth failed {}", _errorPage); + int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); + baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _errorPage))); + } + + return Authentication.SEND_FAILURE; + } + + // Look for cached authentication + HttpSession session = request.getSession(false); + Authentication authentication = session == null ? null : (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED); + if (authentication != null) + { + // Has authentication been revoked? + if (authentication instanceof Authentication.User && _loginService != null && + !_loginService.validate(((Authentication.User)authentication).getUserIdentity())) + { + if (LOG.isDebugEnabled()) + LOG.debug("auth revoked {}", authentication); + session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED); + } + else + { + synchronized (session) + { + String jUri = (String)session.getAttribute(J_URI); + if (jUri != null) + { + //check if the request is for the same url as the original and restore + //params if it was a post + if (LOG.isDebugEnabled()) + LOG.debug("auth retry {}->{}", authentication, jUri); + StringBuffer buf = request.getRequestURL(); + if (request.getQueryString() != null) + buf.append("?").append(request.getQueryString()); + + if (jUri.equals(buf.toString())) + { + @SuppressWarnings("unchecked") + MultiMap jPost = (MultiMap)session.getAttribute(J_POST); + if (jPost != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("auth rePOST {}->{}", authentication, jUri); + baseRequest.setContentParameters(jPost); + } + session.removeAttribute(J_URI); + session.removeAttribute(J_METHOD); + session.removeAttribute(J_POST); + } + } + } + if (LOG.isDebugEnabled()) + LOG.debug("auth {}", authentication); + return authentication; + } + } + + // if we can't send challenge + if (DeferredAuthentication.isDeferred(response)) + { + if (LOG.isDebugEnabled()) + LOG.debug("auth deferred {}", session == null ? null : session.getId()); + return Authentication.UNAUTHENTICATED; + } + + // remember the current URI + session = (session != null ? session : request.getSession(true)); + synchronized (session) + { + // But only if it is not set already, or we save every uri that leads to a login redirect + if (session.getAttribute(J_URI) == null || isAlwaysSaveUri()) + { + StringBuffer buf = request.getRequestURL(); + if (request.getQueryString() != null) + buf.append("?").append(request.getQueryString()); + session.setAttribute(J_URI, buf.toString()); + session.setAttribute(J_METHOD, request.getMethod()); + + if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod())) + { + MultiMap formParameters = new MultiMap<>(); + baseRequest.extractFormParameters(formParameters); + session.setAttribute(J_POST, formParameters); + } + } + } + + // send the the challenge + String challengeUri = getChallengeUri(request); + if (LOG.isDebugEnabled()) + LOG.debug("challenge {}->{}", session.getId(), challengeUri); + int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); + baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(challengeUri)); + + return Authentication.SEND_CONTINUE; + } + catch (IOException e) + { + throw new ServerAuthException(e); + } + } + + public boolean isJSecurityCheck(String uri) + { + int jsc = uri.indexOf(J_SECURITY_CHECK); + + if (jsc < 0) + return false; + int e = jsc + J_SECURITY_CHECK.length(); + if (e == uri.length()) + return true; + char c = uri.charAt(e); + return c == ';' || c == '#' || c == '/' || c == '?'; + } + + public boolean isErrorPage(String pathInContext) + { + return pathInContext != null && (pathInContext.equals(_errorPath)); + } + + private String getRedirectUri(HttpServletRequest request) + { + final StringBuffer redirectUri = new StringBuffer(128); + URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(), + request.getServerName(), request.getServerPort()); + redirectUri.append(request.getContextPath()); + redirectUri.append(J_SECURITY_CHECK); + return redirectUri.toString(); + } + + protected String getChallengeUri(HttpServletRequest request) + { + HttpSession session = request.getSession(); + String antiForgeryToken; + synchronized (session) + { + antiForgeryToken = (session.getAttribute(CSRF_TOKEN) == null) + ? new BigInteger(130, new SecureRandom()).toString(32) + : (String)session.getAttribute(CSRF_TOKEN); + session.setAttribute(CSRF_TOKEN, antiForgeryToken); + } + + // any custom scopes requested from configuration + StringBuilder scopes = new StringBuilder(); + for (String s : _configuration.getScopes()) + { + scopes.append(" ").append(s); + } + + return _configuration.getAuthEndpoint() + + "?client_id=" + UrlEncoded.encodeString(_configuration.getClientId(), StandardCharsets.UTF_8) + + "&redirect_uri=" + UrlEncoded.encodeString(getRedirectUri(request), StandardCharsets.UTF_8) + + "&scope=openid" + UrlEncoded.encodeString(scopes.toString(), StandardCharsets.UTF_8) + + "&state=" + antiForgeryToken + + "&response_type=code"; + } + + @Override + public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) + { + return true; + } + + /** + * This Authentication represents a just completed OpenId Connect authentication. + * Subsequent requests from the same user are authenticated by the presents + * of a {@link SessionAuthentication} instance in their session. + */ + public static class OpenIdAuthentication extends UserAuthentication implements Authentication.ResponseSent + { + public OpenIdAuthentication(String method, UserIdentity userIdentity) + { + super(method, userIdentity); + } + + @Override + public String toString() + { + return "OpenId" + super.toString(); + } + } +} diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java new file mode 100644 index 00000000000..86eea6cdbde --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import javax.servlet.ServletContext; + +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultAuthenticatorFactory; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.security.Constraint; + +public class OpenIdAuthenticatorFactory extends DefaultAuthenticatorFactory +{ + @Override + public Authenticator getAuthenticator(Server server, ServletContext context, Authenticator.AuthConfiguration configuration, IdentityService identityService, LoginService loginService) + { + String auth = configuration.getAuthMethod(); + if (Constraint.__OPENID_AUTH.equalsIgnoreCase(auth)) + return new OpenIdAuthenticator(); + return super.getAuthenticator(server, context, configuration, identityService, loginService); + } +} \ No newline at end of file diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java new file mode 100644 index 00000000000..f3c9134b4a0 --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java @@ -0,0 +1,142 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.io.InputStream; +import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.ajax.JSON; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Holds the configuration for an OpenID Connect service. + * + * This uses the OpenID Provider URL with the path {@link #CONFIG_PATH} to discover + * the required information about the OIDC service. + */ +public class OpenIdConfiguration implements Serializable +{ + private static final Logger LOG = Log.getLogger(OpenIdConfiguration.class); + private static final long serialVersionUID = 2227941990601349102L; + private static final String CONFIG_PATH = "/.well-known/openid-configuration"; + + private final String openIdProvider; + private final String issuer; + private final String authEndpoint; + private final String tokenEndpoint; + private final String clientId; + private final String clientSecret; + private final Map discoveryDocument; + private final List scopes = new ArrayList<>(); + + /** + * Create an OpenID configuration for a specific OIDC provider. + * @param provider The URL of the OpenID provider. + * @param clientId OAuth 2.0 Client Identifier valid at the Authorization Server. + * @param clientSecret The client secret known only by the Client and the Authorization Server. + */ + public OpenIdConfiguration(String provider, String clientId, String clientSecret) + { + this.openIdProvider = provider; + this.clientId = clientId; + this.clientSecret = clientSecret; + + try + { + if (provider.endsWith("/")) + provider = provider.substring(0, provider.length() - 1); + + URI providerUri = URI.create(provider + CONFIG_PATH); + InputStream inputStream = providerUri.toURL().openConnection().getInputStream(); + String content = IO.toString(inputStream); + discoveryDocument = (Map)JSON.parse(content); + if (LOG.isDebugEnabled()) + LOG.debug("discovery document {}", discoveryDocument); + } + catch (Throwable e) + { + throw new IllegalArgumentException("invalid identity provider", e); + } + + issuer = (String)discoveryDocument.get("issuer"); + if (issuer == null) + throw new IllegalArgumentException(); + + authEndpoint = (String)discoveryDocument.get("authorization_endpoint"); + if (authEndpoint == null) + throw new IllegalArgumentException("authorization_endpoint"); + + tokenEndpoint = (String)discoveryDocument.get("token_endpoint"); + if (tokenEndpoint == null) + throw new IllegalArgumentException("token_endpoint"); + } + + public Map getDiscoveryDocument() + { + return discoveryDocument; + } + + public String getAuthEndpoint() + { + return authEndpoint; + } + + public String getClientId() + { + return clientId; + } + + public String getClientSecret() + { + return clientSecret; + } + + public String getIssuer() + { + return issuer; + } + + public String getOpenIdProvider() + { + return openIdProvider; + } + + public String getTokenEndpoint() + { + return tokenEndpoint; + } + + public void addScopes(String... scopes) + { + if (scopes != null) + Collections.addAll(this.scopes, scopes); + } + + public List getScopes() + { + return scopes; + } +} diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java new file mode 100644 index 00000000000..85df9e28b04 --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -0,0 +1,214 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.ajax.JSON; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

The credentials of an user to be authenticated with OpenID Connect. This will contain + * the OpenID ID Token and the OAuth 2.0 Access Token.

+ * + *

+ * This is constructed with an authorization code from the authentication request. This authorization code + * is then exchanged using {@link #redeemAuthCode()} for a response containing the ID Token and Access Token. + * The response is then validated against the {@link OpenIdConfiguration}. + *

+ */ +public class OpenIdCredentials implements Serializable +{ + private static final Logger LOG = Log.getLogger(OpenIdCredentials.class); + private static final long serialVersionUID = 4766053233370044796L; + + private final String redirectUri; + private final OpenIdConfiguration configuration; + private String authCode; + private Map response; + private Map claims; + + public OpenIdCredentials(String authCode, String redirectUri, OpenIdConfiguration configuration) + { + this.authCode = authCode; + this.redirectUri = redirectUri; + this.configuration = configuration; + } + + public String getUserId() + { + return (String)claims.get("sub"); + } + + public Map getClaims() + { + return claims; + } + + public Map getResponse() + { + return response; + } + + public void redeemAuthCode() throws IOException + { + if (LOG.isDebugEnabled()) + LOG.debug("redeemAuthCode() {}", this); + + if (authCode != null) + { + try + { + response = claimAuthCode(authCode); + if (LOG.isDebugEnabled()) + LOG.debug("response: {}", response); + + String idToken = (String)response.get("id_token"); + if (idToken == null) + throw new IllegalArgumentException("no id_token"); + + String accessToken = (String)response.get("access_token"); + if (accessToken == null) + throw new IllegalArgumentException("no access_token"); + + String tokenType = (String)response.get("token_type"); + if (!"Bearer".equalsIgnoreCase(tokenType)) + throw new IllegalArgumentException("invalid token_type"); + + claims = decodeJWT(idToken); + if (LOG.isDebugEnabled()) + LOG.debug("claims {}", claims); + validateClaims(); + } + finally + { + // reset authCode as it can only be used once + authCode = null; + } + } + } + + private void validateClaims() + { + // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. + if (!configuration.getIssuer().equals(claims.get("iss"))) + throw new IllegalArgumentException("Issuer Identifier MUST exactly match the iss Claim"); + + // The aud (audience) Claim MUST contain the client_id value. + if (!configuration.getClientId().equals(claims.get("aud"))) + throw new IllegalArgumentException("Audience Claim MUST contain the client_id value"); + + // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. + Object azp = claims.get("azp"); + if (azp != null && !configuration.getClientId().equals(azp)) + throw new IllegalArgumentException("Authorized party claim value should be the client_id"); + } + + public boolean isExpired() + { + if (authCode != null || claims == null) + return true; + + // Check expiry + long expiry = (Long)claims.get("exp"); + long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F); + if (currentTimeSeconds > expiry) + { + if (LOG.isDebugEnabled()) + LOG.debug("OpenId Credentials expired {}", this); + return true; + } + + return false; + } + + protected Map decodeJWT(String jwt) throws IOException + { + if (LOG.isDebugEnabled()) + LOG.debug("decodeJWT {}", jwt); + + String[] sections = jwt.split("\\."); + if (sections.length != 3) + throw new IllegalArgumentException("JWT does not contain 3 sections"); + + Base64.Decoder decoder = Base64.getDecoder(); + String jwtHeaderString = new String(decoder.decode(sections[0]), StandardCharsets.UTF_8); + String jwtClaimString = new String(decoder.decode(sections[1]), StandardCharsets.UTF_8); + String jwtSignature = sections[2]; + + Map jwtHeader = (Map)JSON.parse(jwtHeaderString); + LOG.debug("JWT Header: {}", jwtHeader); + + /* If the ID Token is received via direct communication between the Client + and the Token Endpoint (which it is in this flow), the TLS server validation + MAY be used to validate the issuer in place of checking the token signature. */ + if (LOG.isDebugEnabled()) + LOG.debug("JWT signature not validated {}", jwtSignature); + + return (Map)JSON.parse(jwtClaimString); + } + + private Map claimAuthCode(String authCode) throws IOException + { + if (LOG.isDebugEnabled()) + LOG.debug("claimAuthCode {}", authCode); + + // Use the authorization code to get the id_token from the OpenID Provider + String urlParameters = "code=" + authCode + + "&client_id=" + UrlEncoded.encodeString(configuration.getClientId(), StandardCharsets.UTF_8) + + "&client_secret=" + UrlEncoded.encodeString(configuration.getClientSecret(), StandardCharsets.UTF_8) + + "&redirect_uri=" + UrlEncoded.encodeString(redirectUri, StandardCharsets.UTF_8) + + "&grant_type=authorization_code"; + + URL url = new URL(configuration.getTokenEndpoint()); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + try + { + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Host", configuration.getOpenIdProvider()); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) + { + wr.write(urlParameters.getBytes(StandardCharsets.UTF_8)); + } + + try (InputStream content = (InputStream)connection.getContent()) + { + return (Map)JSON.parse(IO.toString(content)); + } + } + finally + { + connection.disconnect(); + } + } +} diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java new file mode 100644 index 00000000000..512ff474b71 --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java @@ -0,0 +1,170 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.security.Principal; +import javax.security.auth.Subject; +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * The implementation of {@link LoginService} required to use OpenID Connect. + * + *

+ * Can contain an optional wrapped {@link LoginService} which is used to store role information about users. + *

+ */ +public class OpenIdLoginService extends ContainerLifeCycle implements LoginService +{ + private static final Logger LOG = Log.getLogger(OpenIdLoginService.class); + + private final OpenIdConfiguration _configuration; + private final LoginService loginService; + private IdentityService identityService; + private boolean authenticateNewUsers; + + public OpenIdLoginService(OpenIdConfiguration configuration) + { + this(configuration, null); + } + + /** + * Use a wrapped {@link LoginService} to store information about user roles. + * Users in the wrapped loginService must be stored with their username as + * the value of the sub (subject) Claim, and a credentials value of the empty string. + * @param configuration the OpenID configuration to use. + * @param loginService the wrapped LoginService to defer to for user roles. + */ + public OpenIdLoginService(OpenIdConfiguration configuration, LoginService loginService) + { + _configuration = configuration; + this.loginService = loginService; + addBean(this.loginService); + } + + @Override + public String getName() + { + return _configuration.getOpenIdProvider(); + } + + public OpenIdConfiguration getConfiguration() + { + return _configuration; + } + + @Override + public UserIdentity login(String identifier, Object credentials, ServletRequest req) + { + if (LOG.isDebugEnabled()) + LOG.debug("login({}, {}, {})", identifier, credentials, req); + + OpenIdCredentials openIdCredentials = (OpenIdCredentials)credentials; + try + { + openIdCredentials.redeemAuthCode(); + if (openIdCredentials.isExpired()) + return null; + } + catch (Throwable e) + { + LOG.warn(e); + return null; + } + + OpenIdUserPrincipal userPrincipal = new OpenIdUserPrincipal(openIdCredentials); + Subject subject = new Subject(); + subject.getPrincipals().add(userPrincipal); + subject.getPrivateCredentials().add(credentials); + subject.setReadOnly(); + + if (loginService != null) + { + UserIdentity userIdentity = loginService.login(openIdCredentials.getUserId(), "", req); + if (userIdentity == null) + { + if (isAuthenticateNewUsers()) + return getIdentityService().newUserIdentity(subject, userPrincipal, new String[0]); + return null; + } + return new OpenIdUserIdentity(subject, userPrincipal, userIdentity); + } + + return identityService.newUserIdentity(subject, userPrincipal, new String[0]); + } + + public boolean isAuthenticateNewUsers() + { + return authenticateNewUsers; + } + + /** + * This setting is only meaningful if a wrapped {@link LoginService} has been set. + *

+ * If set to true, any users not found by the wrapped {@link LoginService} will still + * be authenticated but with no roles, if set to false users will not be + * authenticated unless they are discovered by the wrapped {@link LoginService}. + *

+ * @param authenticateNewUsers whether to authenticate users not found by a wrapping LoginService + */ + public void setAuthenticateNewUsers(boolean authenticateNewUsers) + { + this.authenticateNewUsers = authenticateNewUsers; + } + + @Override + public boolean validate(UserIdentity user) + { + Principal userPrincipal = user.getUserPrincipal(); + if (!(userPrincipal instanceof OpenIdUserPrincipal)) + return false; + + OpenIdCredentials credentials = ((OpenIdUserPrincipal)userPrincipal).getCredentials(); + return !credentials.isExpired(); + } + + @Override + public IdentityService getIdentityService() + { + return loginService == null ? identityService : loginService.getIdentityService(); + } + + @Override + public void setIdentityService(IdentityService service) + { + if (isRunning()) + throw new IllegalStateException("Running"); + + if (loginService != null) + loginService.setIdentityService(service); + else + identityService = service; + } + + @Override + public void logout(UserIdentity user) + { + } +} diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserIdentity.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserIdentity.java new file mode 100644 index 00000000000..f375e138c50 --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserIdentity.java @@ -0,0 +1,56 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.security.Principal; +import javax.security.auth.Subject; + +import org.eclipse.jetty.server.UserIdentity; + +public class OpenIdUserIdentity implements UserIdentity +{ + private final Subject subject; + private final Principal userPrincipal; + private final UserIdentity userIdentity; + + public OpenIdUserIdentity(Subject subject, Principal userPrincipal, UserIdentity userIdentity) + { + this.subject = subject; + this.userPrincipal = userPrincipal; + this.userIdentity = userIdentity; + } + + @Override + public Subject getSubject() + { + return subject; + } + + @Override + public Principal getUserPrincipal() + { + return userPrincipal; + } + + @Override + public boolean isUserInRole(String role, Scope scope) + { + return userIdentity != null && userIdentity.isUserInRole(role, scope); + } +} diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java similarity index 54% rename from examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java rename to jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java index bb45cf6c124..6ebb46df2a2 100644 --- a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java @@ -16,16 +16,35 @@ // ======================================================================== // -package org.eclipse.jetty.embedded; +package org.eclipse.jetty.security.openid; -import org.eclipse.jetty.xml.XmlConfiguration; +import java.io.Serializable; +import java.security.Principal; -public class TestXml +public class OpenIdUserPrincipal implements Principal, Serializable { - public static void main(String[] args) throws Exception + private static final long serialVersionUID = 1521094652756670469L; + private final OpenIdCredentials _credentials; + + public OpenIdUserPrincipal(OpenIdCredentials credentials) { - System.setProperty("jetty.home", "../jetty-distribution/target/distribution"); - XmlConfiguration.main("../jetty-jmx/src/main/config/etc/jetty-jmx.xml", - "../jetty-server/src/main/config/etc/jetty.xml"); + _credentials = credentials; } -} + + public OpenIdCredentials getCredentials() + { + return _credentials; + } + + @Override + public String getName() + { + return _credentials.getUserId(); + } + + @Override + public String toString() + { + return _credentials.getUserId(); + } +} \ No newline at end of file diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java new file mode 100644 index 00000000000..54dd613c52d --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java @@ -0,0 +1,226 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.io.IOException; +import java.security.Principal; +import java.util.Map; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.security.Constraint; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class OpenIdAuthenticationTest +{ + public static final String CLIENT_ID = "testClient101"; + public static final String CLIENT_SECRET = "secret37989798"; + + private OpenIdProvider openIdProvider; + private Server server; + private ServerConnector connector; + private HttpClient client; + + @BeforeEach + public void setup() throws Exception + { + openIdProvider = new OpenIdProvider(CLIENT_ID, CLIENT_SECRET); + openIdProvider.start(); + + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); + + // Add servlets + context.addServlet(LoginPage.class, "/login"); + context.addServlet(LogoutPage.class, "/logout"); + context.addServlet(HomePage.class, "/*"); + context.addServlet(ErrorPage.class, "/error"); + + // configure security constraints + Constraint constraint = new Constraint(); + constraint.setName(Constraint.__OPENID_AUTH); + constraint.setRoles(new String[]{"**"}); + constraint.setAuthenticate(true); + + Constraint adminConstraint = new Constraint(); + adminConstraint.setName(Constraint.__OPENID_AUTH); + adminConstraint.setRoles(new String[]{"admin"}); + adminConstraint.setAuthenticate(true); + + // constraint mappings + ConstraintMapping profileMapping = new ConstraintMapping(); + profileMapping.setConstraint(constraint); + profileMapping.setPathSpec("/profile"); + ConstraintMapping loginMapping = new ConstraintMapping(); + loginMapping.setConstraint(constraint); + loginMapping.setPathSpec("/login"); + ConstraintMapping adminMapping = new ConstraintMapping(); + adminMapping.setConstraint(adminConstraint); + adminMapping.setPathSpec("/admin"); + + // security handler + ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); + securityHandler.setRealmName("OpenID Connect Authentication"); + securityHandler.addConstraintMapping(profileMapping); + securityHandler.addConstraintMapping(loginMapping); + securityHandler.addConstraintMapping(adminMapping); + + // Authentication using local OIDC Provider + OpenIdConfiguration configuration = new OpenIdConfiguration(openIdProvider.getProvider(), CLIENT_ID, CLIENT_SECRET); + + // Configure OpenIdLoginService optionally providing a base LoginService to provide user roles + OpenIdLoginService loginService = new OpenIdLoginService(configuration);//, hashLoginService); + securityHandler.setLoginService(loginService); + + Authenticator authenticator = new OpenIdAuthenticator(configuration, "/error"); + securityHandler.setAuthenticator(authenticator); + context.setSecurityHandler(securityHandler); + + server.start(); + String redirectUri = "http://localhost:"+connector.getLocalPort() + "/j_security_check"; + openIdProvider.addRedirectUri(redirectUri); + + client = new HttpClient(); + client.start(); + } + + @AfterEach + public void stop() throws Exception + { + openIdProvider.stop(); + server.stop(); + } + + @Test + public void testLoginLogout() throws Exception + { + String appUriString = "http://localhost:"+connector.getLocalPort(); + + // Initially not authenticated + ContentResponse response = client.GET(appUriString + "/"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + String[] content = response.getContentAsString().split("\n"); + assertThat(content.length, is(1)); + assertThat(content[0], is("not authenticated")); + + // Request to login is success + response = client.GET(appUriString + "/login"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + content = response.getContentAsString().split("\n"); + assertThat(content.length, is(1)); + assertThat(content[0], is("success")); + + // Now authenticated we can get info + response = client.GET(appUriString + "/"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + content = response.getContentAsString().split("\n"); + assertThat(content.length, is(3)); + assertThat(content[0], is("userId: 123456789")); + assertThat(content[1], is("name: FirstName LastName")); + assertThat(content[2], is("email: FirstName@fake-email.com")); + + // Request to admin page gives 403 as we do not have admin role + response = client.GET(appUriString + "/admin"); + assertThat(response.getStatus(), is(HttpStatus.FORBIDDEN_403)); + + // We are no longer authenticated after logging out + response = client.GET(appUriString + "/logout"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + content = response.getContentAsString().split("\n"); + assertThat(content.length, is(1)); + assertThat(content[0], is("not authenticated")); + } + + public static class LoginPage extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.getWriter().println("success"); + } + } + + public static class LogoutPage extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + request.getSession().invalidate(); + response.sendRedirect("/"); + } + } + + public static class AdminPage extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); + response.getWriter().println(userInfo.get("sub") + ": success"); + } + } + + public static class HomePage extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setContentType("text/plain"); + Principal userPrincipal = request.getUserPrincipal(); + if (userPrincipal != null) + { + Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); + response.getWriter().println("userId: " + userInfo.get("sub")); + response.getWriter().println("name: " + userInfo.get("name")); + response.getWriter().println("email: " + userInfo.get("email")); + } + else + { + response.getWriter().println("not authenticated"); + } + } + } + + public static class ErrorPage extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setContentType("text/plain"); + response.getWriter().println("not authorized"); + } + } +} diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java new file mode 100644 index 00000000000..83bfb3441b7 --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java @@ -0,0 +1,254 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security.openid; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.component.ContainerLifeCycle; + +public class OpenIdProvider extends ContainerLifeCycle +{ + private static final String CONFIG_PATH = "/.well-known/openid-configuration"; + private static final String AUTH_PATH = "/auth"; + private static final String TOKEN_PATH = "/token"; + private final Map issuedAuthCodes = new HashMap<>(); + + protected final String clientId; + protected final String clientSecret; + protected final List redirectUris = new ArrayList<>(); + + private String provider; + private Server server; + private ServerConnector connector; + + public OpenIdProvider(String clientId, String clientSecret) + { + this.clientId = clientId; + this.clientSecret = clientSecret; + + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + contextHandler.addServlet(new ServletHolder(new OpenIdConfigServlet()), CONFIG_PATH); + contextHandler.addServlet(new ServletHolder(new OpenIdAuthEndpoint()), AUTH_PATH); + contextHandler.addServlet(new ServletHolder(new OpenIdTokenEndpoint()), TOKEN_PATH); + server.setHandler(contextHandler); + + addBean(server); + } + + @Override + protected void doStart() throws Exception + { + super.doStart(); + provider = "http://localhost:" + connector.getLocalPort(); + } + + public String getProvider() + { + if (!isStarted()) + throw new IllegalStateException(); + return provider; + } + + public void addRedirectUri(String uri) + { + redirectUris.add(uri); + } + + public class OpenIdAuthEndpoint extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + if (!clientId.equals(req.getParameter("client_id"))) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); + return; + } + + String redirectUri = req.getParameter("redirect_uri"); + if (!redirectUris.contains(redirectUri)) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); + return; + } + + String scopeString = req.getParameter("scope"); + List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(StringUtil.csvSplit(scopeString)); + if (!scopes.contains("openid")) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); + return; + } + + if (!"code".equals(req.getParameter("response_type"))) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); + return; + } + + String state = req.getParameter("state"); + if (state == null) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); + return; + } + + String authCode = UUID.randomUUID().toString().replace("-", ""); + User user = new User(123456789, "FirstName", "LastName"); + issuedAuthCodes.put(authCode, user); + + final Request baseRequest = Request.getBaseRequest(req); + final Response baseResponse = baseRequest.getResponse(); + redirectUri += "?code=" + authCode + "&state=" + state; + int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? + HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); + baseResponse.sendRedirect(redirectCode, resp.encodeRedirectURL(redirectUri)); + } + } + + public class OpenIdTokenEndpoint extends HttpServlet + { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + String code = req.getParameter("code"); + + if (!clientId.equals(req.getParameter("client_id")) || + !clientSecret.equals(req.getParameter("client_secret")) || + !redirectUris.contains(req.getParameter("redirect_uri")) || + !"authorization_code".equals(req.getParameter("grant_type")) || + code == null) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); + return; + } + + User user = issuedAuthCodes.remove(code); + if (user == null) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); + return; + } + + String jwtHeader = "{\"INFO\": \"this is not used or checked in our implementation\"}"; + String jwtBody = user.getIdToken(); + String jwtSignature = "we do not validate signature as we use the authorization code flow"; + + Base64.Encoder encoder = Base64.getEncoder(); + String jwt = encoder.encodeToString(jwtHeader.getBytes()) + "." + + encoder.encodeToString(jwtBody.getBytes()) + "." + + encoder.encodeToString(jwtSignature.getBytes()); + + String accessToken = "ABCDEFG"; + long expiry = System.currentTimeMillis() + Duration.ofMinutes(10).toMillis(); + String response = "{" + + "\"access_token\": \"" + accessToken + "\"," + + "\"id_token\": \"" + jwt + "\"," + + "\"expires_in\": " + expiry + "," + + "\"token_type\": \"Bearer\"" + + "}"; + + resp.setContentType("text/plain"); + resp.getWriter().print(response); + } + } + + public class OpenIdConfigServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + String discoveryDocument = "{" + + "\"issuer\": \"" + provider + "\"," + + "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + + "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + + "}"; + + resp.getWriter().write(discoveryDocument); + } + } + + public class User + { + private long subject; + private String firstName; + private String lastName; + + public User(String firstName, String lastName) + { + this(new Random().nextLong(), firstName, lastName); + } + + public User(long subject, String firstName, String lastName) + { + this.subject = subject; + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() + { + return firstName; + } + + public String getLastName() + { + return lastName; + } + + public String getIdToken() + { + return "{" + + "\"iss\": \"" + provider + "\"," + + "\"sub\": \"" + subject + "\"," + + "\"aud\": \"" + clientId + "\"," + + "\"exp\": " + System.currentTimeMillis() + Duration.ofMinutes(1).toMillis() + "," + + "\"name\": \"" + firstName + " " + lastName + "\"," + + "\"email\": \"" + firstName + "@fake-email.com" + "\"" + + "}"; + } + } +} diff --git a/jetty-openid/src/test/resources/jetty-logging.properties b/jetty-openid/src/test/resources/jetty-logging.properties new file mode 100755 index 00000000000..c73ac07f8ac --- /dev/null +++ b/jetty-openid/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.security.openid.LEVEL=DEBUG \ No newline at end of file diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java index c64d64fb571..a9dbb178f84 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java @@ -78,6 +78,7 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; @@ -88,6 +89,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +@Disabled("See issue #3974") public class AsyncMiddleManServletTest { private static final Logger LOG = Log.getLogger(AsyncMiddleManServletTest.class); diff --git a/jetty-quickstart/src/main/config/etc/example-quickstart.xml b/jetty-quickstart/src/main/config/etc/example-quickstart.xml deleted file mode 100644 index c042d89d724..00000000000 --- a/jetty-quickstart/src/main/config/etc/example-quickstart.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - true - / - /application.war - - diff --git a/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml b/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml new file mode 100644 index 00000000000..84d030260b0 --- /dev/null +++ b/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + /etc/quickstart-webapp.xml + + + + + + diff --git a/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml b/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml new file mode 100644 index 00000000000..b0f07545e95 --- /dev/null +++ b/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml @@ -0,0 +1,28 @@ + + + + + + org.eclipse.jetty.quickstart.origin + + + + + org.eclipse.jetty.quickstart.xml + + + + + org.eclipse.jetty.quickstart.mode + + + + + + + + true + false + false + + diff --git a/jetty-quickstart/src/main/config/modules/quickstart.mod b/jetty-quickstart/src/main/config/modules/quickstart.mod index 102801714b6..c531ea648d0 100644 --- a/jetty-quickstart/src/main/config/modules/quickstart.mod +++ b/jetty-quickstart/src/main/config/modules/quickstart.mod @@ -6,8 +6,21 @@ deployment of preconfigured webapplications. [depend] server -plus -annotations +deploy [lib] lib/jetty-quickstart-${jetty.version}.jar + +[xml] +etc/jetty-quickstart.xml + +[files] +basehome:modules/jetty-quickstart.d/quickstart-webapp.xml|etc/quickstart-webapp.xml + + +[ini-template] + +# Modes are AUTO, GENERATE, QUICKSTART +# jetty.quickstart.mode=AUTO +# jetty.quickstart.origin=origin +# jetty.quickstart.xml= diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java index 627f676588a..92814695c33 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java @@ -20,11 +20,15 @@ package org.eclipse.jetty.quickstart; import java.util.Locale; +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.plus.webapp.EnvConfiguration; +import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.JarResource; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlConfiguration; public class PreconfigureQuickStartWar @@ -98,7 +102,13 @@ public class PreconfigureQuickStartWar final Server server = new Server(); - QuickStartWebApp webapp = new QuickStartWebApp(); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration(), + new EnvConfiguration(), + new PlusConfiguration(), + new AnnotationConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE); + webapp.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, ""); if (xml != null) { @@ -108,10 +118,21 @@ public class PreconfigureQuickStartWar xmlConfiguration.configure(webapp); } webapp.setResourceBase(dir.getFile().getAbsolutePath()); - webapp.setMode(QuickStartConfiguration.Mode.GENERATE); server.setHandler(webapp); - server.start(); - server.stop(); + try + { + server.setDryRun(true); + server.start(); + } + catch (Exception e) + { + throw e; + } + finally + { + if (!server.isStopped()) + server.stop(); + } } private static void error(String message) diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java index 2462ad4e326..bb1a9c3d345 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.quickstart; +import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; @@ -26,6 +27,8 @@ import java.util.stream.Collectors; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.annotations.AnnotationDecorator; import org.eclipse.jetty.annotations.ServletContainerInitializersStarter; +import org.eclipse.jetty.server.Server; +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; @@ -39,17 +42,16 @@ import org.eclipse.jetty.webapp.WebXmlConfiguration; /** * QuickStartConfiguration *

- * Re-inflate a deployable webapp from a saved effective-web.xml - * which combines all pre-parsed web xml descriptors and annotations. + * Prepare for quickstart generation, or usage. */ public class QuickStartConfiguration extends AbstractConfiguration { private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class); public static final Set> __replacedConfigurations = new HashSet<>(); - public static final String ORIGIN_ATTRIBUTE = "org.eclipse.jetty.quickstart.ORIGIN_ATTRIBUTE"; - public static final String GENERATE_ORIGIN = "org.eclipse.jetty.quickstart.GENERATE_ORIGIN"; - public static final String QUICKSTART_WEB_XML = "org.eclipse.jetty.quickstart.QUICKSTART_WEB_XML"; + public static final String ORIGIN_ATTRIBUTE = "org.eclipse.jetty.quickstart.origin"; + public static final String QUICKSTART_WEB_XML = "org.eclipse.jetty.quickstart.xml"; + public static final String MODE = "org.eclipse.jetty.quickstart.mode"; static { @@ -59,18 +61,25 @@ public class QuickStartConfiguration extends AbstractConfiguration __replacedConfigurations.add(org.eclipse.jetty.annotations.AnnotationConfiguration.class); } - ; + /** Configure the server for the quickstart mode. + *

In practise this means calling server.setDryRun(true) for GENERATE mode

+ * @see Server#setDryRun(boolean) + * @param server The server to configure + * @param mode The quickstart mode + */ + public static void configureMode(Server server, String mode) + { + if (mode != null && Mode.valueOf(mode) == Mode.GENERATE) + server.setDryRun(true); + } public enum Mode { - DISABLED, // No Quick start GENERATE, // Generate quickstart-web.xml and then stop AUTO, // use or generate depending on the existance of quickstart-web.xml QUICKSTART // Use quickstart-web.xml } - ; - private Mode _mode = Mode.AUTO; private boolean _quickStart; @@ -81,19 +90,6 @@ public class QuickStartConfiguration extends AbstractConfiguration addDependents(WebXmlConfiguration.class); } - public void setMode(Mode mode) - { - _mode = mode; - } - - public Mode getMode() - { - return _mode; - } - - /** - * @see org.eclipse.jetty.webapp.AbstractConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) - */ @Override public void preConfigure(WebAppContext context) throws Exception { @@ -106,39 +102,46 @@ public class QuickStartConfiguration extends AbstractConfiguration Resource quickStartWebXml = getQuickStartWebXml(context); LOG.debug("quickStartWebXml={} exists={}", quickStartWebXml, quickStartWebXml.exists()); + //Get the mode + Mode mode = (Mode)context.getAttribute(MODE); + if (mode != null) + _mode = mode; + _quickStart = false; + switch (_mode) { - case DISABLED: - super.preConfigure(context); - break; - case GENERATE: { + if (quickStartWebXml.exists()) + LOG.info("Regenerating {}", quickStartWebXml); + else + LOG.info("Generating {}", quickStartWebXml); + super.preConfigure(context); + //generate the quickstart file then abort QuickStartGeneratorConfiguration generator = new QuickStartGeneratorConfiguration(true); configure(generator, context); context.addConfiguration(generator); break; } - case AUTO: { if (quickStartWebXml.exists()) - quickStart(context, quickStartWebXml); + { + quickStart(context); + } else { + if (LOG.isDebugEnabled()) + LOG.debug("No quickstart xml file, starting webapp {} normally", context); super.preConfigure(context); - QuickStartGeneratorConfiguration generator = new QuickStartGeneratorConfiguration(false); - configure(generator, context); - context.addConfiguration(generator); } break; } - case QUICKSTART: if (quickStartWebXml.exists()) - quickStart(context, quickStartWebXml); + quickStart(context); else throw new IllegalStateException("No " + quickStartWebXml); break; @@ -151,27 +154,20 @@ public class QuickStartConfiguration extends AbstractConfiguration protected void configure(QuickStartGeneratorConfiguration generator, WebAppContext context) throws IOException { Object attr; - attr = context.getAttribute(GENERATE_ORIGIN); - if (attr != null) - generator.setGenerateOrigin(Boolean.valueOf(attr.toString())); attr = context.getAttribute(ORIGIN_ATTRIBUTE); if (attr != null) generator.setOriginAttribute(attr.toString()); - attr = context.getAttribute(QUICKSTART_WEB_XML); - if (attr instanceof Resource) - generator.setQuickStartWebXml((Resource)attr); - else if (attr != null) - generator.setQuickStartWebXml(Resource.newResource(attr.toString())); + + generator.setQuickStartWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML)); } - /** - * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext) - */ @Override public void configure(WebAppContext context) throws Exception { if (!_quickStart) + { super.configure(context); + } else { //add the processor to handle normal web.xml content @@ -195,14 +191,27 @@ public class QuickStartConfiguration extends AbstractConfiguration } } - protected void quickStart(WebAppContext context, Resource quickStartWebXml) + @Override + public void postConfigure(WebAppContext context) throws Exception + { + super.postConfigure(context); + ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER); + if (starter != null) + { + context.removeBean(starter); + context.removeAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER); + } + } + + protected void quickStart(WebAppContext context) throws Exception { + LOG.info("Quickstarting {}", context); _quickStart = true; - context.setConfigurations(context.getWebAppConfigurations().stream() + context.setConfigurations(context.getConfigurations().stream() .filter(c -> !__replacedConfigurations.contains(c.replaces()) && !__replacedConfigurations.contains(c.getClass())) .collect(Collectors.toList()).toArray(new Configuration[]{})); - context.getMetaData().setWebXml(quickStartWebXml); + context.getMetaData().setWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML)); context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion()); context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion()); } @@ -216,12 +225,38 @@ public class QuickStartConfiguration extends AbstractConfiguration */ public Resource getQuickStartWebXml(WebAppContext context) throws Exception { + Object attr = context.getAttribute(QUICKSTART_WEB_XML); + if (attr instanceof Resource) + return (Resource)attr; + Resource webInf = context.getWebInf(); if (webInf == null || !webInf.exists()) - throw new IllegalStateException("No WEB-INF"); - LOG.debug("webinf={}", webInf); + { + File tmp = new File(context.getBaseResource().getFile(), "WEB-INF"); + tmp.mkdirs(); + webInf = context.getWebInf(); + } - Resource quickStartWebXml = webInf.addPath("quickstart-web.xml"); - return quickStartWebXml; + Resource qstart; + if (attr == null || StringUtil.isBlank(attr.toString())) + { + qstart = webInf.addPath("quickstart-web.xml"); + } + else + { + try + { + // Try a relative resolution + qstart = Resource.newResource(webInf.getFile().toPath().resolve(attr.toString())); + } + catch (Throwable th) + { + // try as a resource + qstart = (Resource.newResource(attr.toString())); + } + context.setAttribute(QUICKSTART_WEB_XML, qstart); + } + context.setAttribute(QUICKSTART_WEB_XML, qstart); + return qstart; } } diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java index 20cfab4535b..5588f4b907b 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java @@ -52,6 +52,7 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; import org.eclipse.jetty.util.QuotedStringTokenizer; +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; @@ -61,10 +62,11 @@ import org.eclipse.jetty.webapp.MetaData; import org.eclipse.jetty.webapp.MetaData.OriginInfo; import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.xml.XmlAppendable; /** - * QuickStartDescriptorGenerator + * QuickStartGeneratorConfiguration *

* Generate an effective web.xml from a WebAppContext, including all components * from web.xml, web-fragment.xmls annotations etc. @@ -81,10 +83,9 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration protected final boolean _abort; protected String _originAttribute; - protected boolean _generateOrigin; protected int _count; protected Resource _quickStartWebXml; - + public QuickStartGeneratorConfiguration() { this(false); @@ -92,7 +93,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration public QuickStartGeneratorConfiguration(boolean abort) { - super(true); + super(false); _count = 0; _abort = abort; } @@ -116,22 +117,6 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration return _originAttribute; } - /** - * @return the generateOrigin - */ - public boolean isGenerateOrigin() - { - return _generateOrigin; - } - - /** - * @param generateOrigin the generateOrigin to set - */ - public void setGenerateOrigin(boolean generateOrigin) - { - _generateOrigin = generateOrigin; - } - public Resource getQuickStartWebXml() { return _quickStartWebXml; @@ -163,8 +148,6 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration if (context.getBaseResource() == null) throw new IllegalArgumentException("No base resource for " + this); - LOG.info("Quickstart generating"); - MetaData md = context.getMetaData(); Map webappAttr = new HashMap<>(); @@ -195,13 +178,13 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration //the META-INF/resources discovered addContextParamFromAttribute(context, out, MetaInfConfiguration.METAINF_RESOURCES, normalizer); - // the default-context-path, if presernt + // the default-context-path, if present String defaultContextPath = (String)context.getAttribute("default-context-path"); if (defaultContextPath != null) out.tag("default-context-path", defaultContextPath); //add the name of the origin attribute, if it is being used - if (_generateOrigin) + if (StringUtil.isNotBlank(_originAttribute)) { out.openTag("context-param") .tag("param-name", ORIGIN) @@ -766,7 +749,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration */ public Map origin(MetaData md, String name) { - if (!(_generateOrigin || LOG.isDebugEnabled())) + if (StringUtil.isBlank(_originAttribute)) return Collections.emptyMap(); if (name == null) return Collections.emptyMap(); @@ -792,13 +775,19 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration { MetaData metadata = context.getMetaData(); metadata.resolve(context); - - Resource quickStartWebXml = _quickStartWebXml; - if (_quickStartWebXml == null) - quickStartWebXml = context.getWebInf().addPath("/quickstart-web.xml"); - try (FileOutputStream fos = new FileOutputStream(quickStartWebXml.getFile(), false)) + try (FileOutputStream fos = new FileOutputStream(_quickStartWebXml.getFile(), false)) { generateQuickStartWebXml(context, fos); + LOG.info("Generated {}", _quickStartWebXml); + if (context.getAttribute(WebInfConfiguration.TEMPORARY_RESOURCE_BASE) != null && !context.isPersistTempDirectory()) + LOG.warn("Generated to non persistent location: " + _quickStartWebXml); } } + + @Override + public void deconfigure(WebAppContext context) throws Exception + { + super.deconfigure(context); + } + } diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java deleted file mode 100644 index 0cf95f8f1b5..00000000000 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java +++ /dev/null @@ -1,90 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2019 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.quickstart; - -import org.eclipse.jetty.annotations.AnnotationConfiguration; -import org.eclipse.jetty.plus.webapp.EnvConfiguration; -import org.eclipse.jetty.plus.webapp.PlusConfiguration; -import org.eclipse.jetty.quickstart.QuickStartConfiguration.Mode; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * QuickStartWar - */ -public class QuickStartWebApp extends WebAppContext -{ - private final QuickStartConfiguration _quickStartConfiguration; - - private String _originAttribute; - private boolean _generateOrigin; - - public QuickStartWebApp() - { - super(); - addConfiguration( - _quickStartConfiguration = new QuickStartConfiguration(), - new EnvConfiguration(), - new PlusConfiguration(), - new AnnotationConfiguration()); - setExtractWAR(true); - setCopyWebDir(false); - setCopyWebInf(false); - } - - public void setOriginAttribute(String name) - { - setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, name); - } - - /** - * @return the originAttribute - */ - public String getOriginAttribute() - { - Object attr = getAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE); - return attr == null ? null : attr.toString(); - } - - /** - * @param generateOrigin the generateOrigin to set - */ - public void setGenerateOrigin(boolean generateOrigin) - { - setAttribute(QuickStartConfiguration.GENERATE_ORIGIN, generateOrigin); - } - - /** - * @return the generateOrigin - */ - public boolean isGenerateOrigin() - { - Object attr = getAttribute(QuickStartConfiguration.GENERATE_ORIGIN); - return attr == null ? false : Boolean.valueOf(attr.toString()); - } - - public Mode getMode() - { - return _quickStartConfiguration.getMode(); - } - - public void setMode(Mode mode) - { - _quickStartConfiguration.setMode(mode); - } -} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java index 6e7b3ed3834..82e0c376f8f 100644 --- a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.servlet.ListenerHolder; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.webapp.WebAppContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,10 +62,11 @@ public class TestQuickStart Server server = new Server(); //generate a quickstart-web.xml - QuickStartWebApp quickstart = new QuickStartWebApp(); + WebAppContext quickstart = new WebAppContext(); + quickstart.addConfiguration(new QuickStartConfiguration()); + quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE); + quickstart.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "origin"); quickstart.setResourceBase(testDir.getAbsolutePath()); - quickstart.setMode(QuickStartConfiguration.Mode.GENERATE); - quickstart.setGenerateOrigin(true); ServletHolder fooHolder = new ServletHolder(); fooHolder.setServlet(new FooServlet()); fooHolder.setName("foo"); @@ -73,19 +75,22 @@ public class TestQuickStart lholder.setListener(new FooContextListener()); quickstart.getServletHandler().addListener(lholder); server.setHandler(quickstart); + server.setDryRun(true); server.start(); - server.stop(); assertTrue(quickstartXml.exists()); //now run the webapp again purely from the generated quickstart - QuickStartWebApp webapp = new QuickStartWebApp(); + WebAppContext webapp = new WebAppContext(); webapp.setResourceBase(testDir.getAbsolutePath()); - webapp.setMode(QuickStartConfiguration.Mode.QUICKSTART); + webapp.addConfiguration(new QuickStartConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader())); server.setHandler(webapp); + server.setDryRun(false); server.start(); + server.dumpStdErr(); //verify that FooServlet is now mapped to / and not the DefaultServlet ServletHolder sh = webapp.getServletHandler().getMappedServlet("/").getResource(); @@ -104,28 +109,30 @@ public class TestQuickStart Server server = new Server(); // generate a quickstart-web.xml - QuickStartWebApp quickstart = new QuickStartWebApp(); + WebAppContext quickstart = new WebAppContext(); quickstart.setResourceBase(testDir.getAbsolutePath()); - quickstart.setMode(QuickStartConfiguration.Mode.GENERATE); - quickstart.setGenerateOrigin(true); + quickstart.addConfiguration(new QuickStartConfiguration()); + quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE); + quickstart.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "origin"); quickstart.setDescriptor(MavenTestingUtils.getTestResourceFile("web.xml").getAbsolutePath()); quickstart.setContextPath("/foo"); server.setHandler(quickstart); + server.setDryRun(true); server.start(); - assertEquals("/foo", quickstart.getContextPath()); assertFalse(quickstart.isContextPathDefault()); - server.stop(); assertTrue(quickstartXml.exists()); // quick start - QuickStartWebApp webapp = new QuickStartWebApp(); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration()); + quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setResourceBase(testDir.getAbsolutePath()); - webapp.setMode(QuickStartConfiguration.Mode.QUICKSTART); webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader())); server.setHandler(webapp); + server.setDryRun(false); server.start(); // verify the context path is the default-context-path diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java index 8b99979b18b..5543fe87158 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java @@ -23,7 +23,6 @@ import java.io.ObjectInputStream; import java.io.Serializable; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionEvent; @@ -113,16 +112,4 @@ public class SessionAuthentication extends AbstractUserAuthentication _session = se.getSession(); } } - - @Override - @Deprecated - public void valueBound(HttpSessionBindingEvent event) - { - } - - @Override - @Deprecated - public void valueUnbound(HttpSessionBindingEvent event) - { - } } diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java new file mode 100644 index 00000000000..a30d3337524 --- /dev/null +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java @@ -0,0 +1,93 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.security; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.eclipse.jetty.security.authentication.SessionAuthentication; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.security.Password; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * SessionAuthenticationTest + * + */ +public class SessionAuthenticationTest +{ + /** + * Check that a SessionAuthenticator is serializable, and that + * the deserialized SessionAuthenticator contains the same authentication + * and authorization information. + */ + @Test + public void testSessionAuthenticationSerialization() + throws Exception + { + + ContextHandler contextHandler = new ContextHandler(); + SecurityHandler securityHandler = new ConstraintSecurityHandler(); + contextHandler.setHandler(securityHandler); + TestLoginService loginService = new TestLoginService("SessionAuthTest"); + Password pwd = new Password("foo"); + loginService.putUser("foo", pwd, new String[]{"boss", "worker"}); + securityHandler.setLoginService(loginService); + securityHandler.setAuthMethod("FORM"); + UserIdentity user = loginService.login("foo", pwd, null); + assertNotNull(user); + assertNotNull(user.getUserPrincipal()); + assertEquals("foo", user.getUserPrincipal().getName()); + SessionAuthentication sessionAuth = new SessionAuthentication("FORM", user, pwd); + assertTrue(sessionAuth.isUserInRole(null, "boss")); + contextHandler.handle(new Runnable() + { + public void run() + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(sessionAuth); + oos.close(); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + SessionAuthentication reactivatedSessionAuth = (SessionAuthentication)ois.readObject(); + assertNotNull(reactivatedSessionAuth); + assertNotNull(reactivatedSessionAuth.getUserIdentity()); + assertNotNull(reactivatedSessionAuth.getUserIdentity().getUserPrincipal()); + assertEquals("foo", reactivatedSessionAuth.getUserIdentity().getUserPrincipal().getName()); + assertNotNull(reactivatedSessionAuth.getUserIdentity().getSubject()); + assertTrue(reactivatedSessionAuth.isUserInRole(null, "boss")); + } + catch (Exception e) + { + fail(e); + } + } + }); + } +} diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index f63bf25c846..f0a1cf0a4a0 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -35,7 +35,11 @@ - + + + + + diff --git a/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml b/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml index c40486e363e..35d7bfb28f0 100644 --- a/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml +++ b/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml @@ -5,15 +5,16 @@ - + - - - - + + + + + diff --git a/jetty-server/src/main/config/etc/sessions/session-cache-null.xml b/jetty-server/src/main/config/etc/sessions/session-cache-null.xml index 466402975aa..7de90393a52 100644 --- a/jetty-server/src/main/config/etc/sessions/session-cache-null.xml +++ b/jetty-server/src/main/config/etc/sessions/session-cache-null.xml @@ -10,13 +10,9 @@ - - - - - - - + + + diff --git a/jetty-server/src/main/config/modules/jdbc.mod b/jetty-server/src/main/config/modules/jdbc.mod new file mode 100644 index 00000000000..a78fcc256aa --- /dev/null +++ b/jetty-server/src/main/config/modules/jdbc.mod @@ -0,0 +1,4 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[jpms] +add-modules: java.sql diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod index be9bff04e0d..cb47011eb3e 100644 --- a/jetty-server/src/main/config/modules/server.mod +++ b/jetty-server/src/main/config/modules/server.mod @@ -80,3 +80,8 @@ etc/jetty.xml ## Dump the state of the Jetty server, components, and webapps before shutdown # jetty.server.dumpBeforeStop=false + +## Scheduler Configuration +# jetty.scheduler.name= +# jetty.scheduler.deamon=false +# jetty.scheduler.threads=-1 diff --git a/jetty-server/src/main/config/modules/session-cache-hash.mod b/jetty-server/src/main/config/modules/session-cache-hash.mod index 32ab705c7a2..2d336bc1d99 100644 --- a/jetty-server/src/main/config/modules/session-cache-hash.mod +++ b/jetty-server/src/main/config/modules/session-cache-hash.mod @@ -1,10 +1,9 @@ DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] -Enable first level session cache in ConcurrentHashMap. -If not enabled, sessions will use a HashSessionCache by default, so enabling -via this module is only needed if the configuration properties need to be -changed. +Enable first level session cache. If this module is not enabled, sessions will +use the DefaultSessionCache by default, so enabling via this module is only needed +if the configuration properties need to be changed from their defaults. [tags] session @@ -23,3 +22,4 @@ etc/sessions/session-cache-hash.xml #jetty.session.saveOnInactiveEvict=false #jetty.session.saveOnCreate=false #jetty.session.removeUnloadableSessions=false +#jetty.session.flushOnResponseCommit=false diff --git a/jetty-server/src/main/config/modules/session-cache-null.mod b/jetty-server/src/main/config/modules/session-cache-null.mod index 6069c8f8168..2a94f59cb82 100644 --- a/jetty-server/src/main/config/modules/session-cache-null.mod +++ b/jetty-server/src/main/config/modules/session-cache-null.mod @@ -18,4 +18,4 @@ etc/sessions/session-cache-null.xml [ini-template] #jetty.session.saveOnCreate=false #jetty.session.removeUnloadableSessions=false -#jetty.session.writeThroughMode=ON_EXIT +#jetty.session.flushOnResponseCommit=false diff --git a/jetty-server/src/main/config/modules/session-store-jdbc.mod b/jetty-server/src/main/config/modules/session-store-jdbc.mod index 297cf2c69a4..e97457d0781 100644 --- a/jetty-server/src/main/config/modules/session-store-jdbc.mod +++ b/jetty-server/src/main/config/modules/session-store-jdbc.mod @@ -10,6 +10,7 @@ session session-store [depend] +jdbc sessions sessions/jdbc/${db-connection-type} @@ -54,7 +55,3 @@ db-connection-type=datasource #jetty.session.jdbc.schema.maxIntervalColumn=maxInterval #jetty.session.jdbc.schema.mapColumn=map #jetty.session.jdbc.schema.table=JettySessions - - - - diff --git a/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod b/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod index be665ff9f3b..d30b911a3b9 100644 --- a/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod +++ b/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod @@ -3,5 +3,8 @@ DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-m [description] JDBC Datasource connections for session storage +[depends] +jdbc + [xml] etc/sessions/jdbc/datasource.xml diff --git a/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod b/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod index eb7391a807d..ad387114cd6 100644 --- a/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod +++ b/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod @@ -3,5 +3,8 @@ DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-m [description] JDBC Driver connections for session storage +[depend] +jdbc + [xml] etc/sessions/jdbc/driver.xml 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 1723bcc73df..d076742d121 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 @@ -52,7 +52,7 @@ import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.Locker; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.ThreadPoolBudget; @@ -144,8 +144,8 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co { protected static final Logger LOG = Log.getLogger(AbstractConnector.class); - private final Locker _locker = new Locker(); - private final Condition _setAccepting = _locker.newCondition(); + private final AutoLock _lock = new AutoLock(); + private final Condition _setAccepting = _lock.newCondition(); private final Map _factories = new LinkedHashMap<>(); // Order is important on server side, so we use a LinkedHashMap private final Server _server; private final Executor _executor; @@ -231,7 +231,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co * Get the {@link HttpChannel.Listener}s added to the connector * as a single combined Listener. * This is equivalent to a listener that iterates over the individual - * listeners returned from getBeans(HttpChannel.Listener.class);, + * listeners returned from {@code getBeans(HttpChannel.Listener.class);}, * except that:

    *
  • The result is precomputed, so it is more efficient
  • *
  • The result is ordered by the order added.
  • @@ -332,7 +332,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co protected void interruptAcceptors() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { for (Thread thread : _acceptors) { @@ -387,7 +387,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co public void join(long timeout) throws InterruptedException { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { for (Thread thread : _acceptors) { @@ -404,7 +404,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co */ public boolean isAccepting() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _accepting; } @@ -412,7 +412,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co public void setAccepting(boolean accepting) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _accepting = accepting; _setAccepting.signalAll(); @@ -422,7 +422,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co @Override public ConnectionFactory getConnectionFactory(String protocol) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _factories.get(StringUtil.asciiToLowerCase(protocol)); } @@ -431,7 +431,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co @Override public T getConnectionFactory(Class factoryType) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { for (ConnectionFactory f : _factories.values()) { @@ -683,7 +683,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co { while (isRunning()) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (!_accepting && isRunning()) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 55d155601ad..82801769c1a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -44,7 +44,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.QuietException; @@ -251,12 +250,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return _configuration; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return getHttpTransport().isOptimizedForDirectBuffers(); - } - public Server getServer() { return _connector.getServer(); @@ -858,15 +851,15 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor if (response == null) response = _response.newResponseMetaData(); commit(response); - + _combinedListener.onResponseBegin(_request); + _request.onResponseCommit(); + // wrap callback to process 100 responses final int status = response.getStatus(); final Callback committed = (status < HttpStatus.OK_200 && status >= HttpStatus.CONTINUE_100) ? new Send100Callback(callback) : new SendCallback(callback, content, true, complete); - _combinedListener.onResponseBegin(_request); - // committing write _transport.send(_request.getMetaData(), response, content, complete, committed); } @@ -971,12 +964,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return _connector.getScheduler(); } - /** - * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol) - */ - public boolean useDirectBuffers() + public boolean isUseOutputDirectByteBuffers() { - return getEndPoint() instanceof ChannelEndPoint; + return getHttpConfiguration().isUseOutputDirectByteBuffers(); } /** diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index c09d71b7893..7fd7799954f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -69,6 +69,12 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque _metadata.setURI(new HttpURI()); } + @Override + public boolean isUseOutputDirectByteBuffers() + { + return _httpConnection.isUseOutputDirectByteBuffers(); + } + @Override protected HttpInput newHttpInput(HttpChannelState state) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java index 619f5741a57..5452c7ffa8b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java @@ -67,7 +67,8 @@ public class HttpConfiguration implements Dumpable private boolean _delayDispatchUntilContent = true; private boolean _persistentConnectionsEnabled = true; private int _maxErrorDispatches = 10; - private boolean _useDirectByteBuffers = false; + private boolean _useInputDirectByteBuffers = true; + private boolean _useOutputDirectByteBuffers = true; private long _minRequestDataRate; private long _minResponseDataRate; private HttpCompliance _httpCompliance = HttpCompliance.RFC7230; @@ -134,7 +135,8 @@ public class HttpConfiguration implements Dumpable _delayDispatchUntilContent = config._delayDispatchUntilContent; _persistentConnectionsEnabled = config._persistentConnectionsEnabled; _maxErrorDispatches = config._maxErrorDispatches; - _useDirectByteBuffers = config._useDirectByteBuffers; + _useInputDirectByteBuffers = config._useInputDirectByteBuffers; + _useOutputDirectByteBuffers = config._useOutputDirectByteBuffers; _minRequestDataRate = config._minRequestDataRate; _minResponseDataRate = config._minResponseDataRate; _httpCompliance = config._httpCompliance; @@ -327,17 +329,31 @@ public class HttpConfiguration implements Dumpable } /** - * @param useDirectByteBuffers if true, use direct byte buffers for requests + * @param useInputDirectByteBuffers whether to use direct ByteBuffers for reading */ - public void setUseDirectByteBuffers(boolean useDirectByteBuffers) + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) { - _useDirectByteBuffers = useDirectByteBuffers; + _useInputDirectByteBuffers = useInputDirectByteBuffers; } - @ManagedAttribute("Whether to use direct byte buffers for requests") - public boolean isUseDirectByteBuffers() + @ManagedAttribute("Whether to use direct ByteBuffers for reading") + public boolean isUseInputDirectByteBuffers() { - return _useDirectByteBuffers; + return _useInputDirectByteBuffers; + } + + /** + * @param useOutputDirectByteBuffers whether to use direct ByteBuffers for writing + */ + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + _useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + + @ManagedAttribute("Whether to use direct ByteBuffers for writing") + public boolean isUseOutputDirectByteBuffers() + { + return _useOutputDirectByteBuffers; } /** diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 9d8ac9edc35..fc52d201d9f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -72,6 +72,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private final boolean _recordHttpComplianceViolations; private final LongAdder bytesIn = new LongAdder(); private final LongAdder bytesOut = new LongAdder(); + private boolean _useInputDirectByteBuffers; + private boolean _useOutputDirectByteBuffers; /** * Get the current connection that this thread is dispatched to. @@ -163,12 +165,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return _generator; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return getEndPoint().isOptimizedForDirectBuffers(); - } - @Override public long getMessagesIn() { @@ -181,6 +177,26 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return getHttpChannel().getRequests(); } + public boolean isUseInputDirectByteBuffers() + { + return _useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + _useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + public boolean isUseOutputDirectByteBuffers() + { + return _useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + _useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + @Override public ByteBuffer onUpgradeFrom() { @@ -223,7 +239,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public ByteBuffer getRequestBuffer() { if (_requestBuffer == null) - _requestBuffer = _bufferPool.acquire(getInputBufferSize(), _config.isUseDirectByteBuffers()); + { + boolean useDirectByteBuffers = isUseInputDirectByteBuffers(); + _requestBuffer = _bufferPool.acquire(getInputBufferSize(), useDirectByteBuffers); + } return _requestBuffer; } @@ -253,11 +272,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // Parse the request buffer. boolean handle = parseRequestBuffer(); - // If there was a connection upgrade, the other - // connection took over, nothing more to do here. - if (getEndPoint().getConnection() != this) - break; - // Handle channel event if (handle) { @@ -731,14 +745,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http if (_callback == null) throw new IllegalStateException(); + boolean useDirectByteBuffers = isUseOutputDirectByteBuffers(); ByteBuffer chunk = _chunk; while (true) { HttpGenerator.Result result = _generator.generateResponse(_info, _head, _header, chunk, _content, _lastContent); if (LOG.isDebugEnabled()) - LOG.debug("{} generate: {} ({},{},{})@{}", - this, + LOG.debug("generate: {} for {} ({},{},{})@{}", result, + this, BufferUtil.toSummaryString(_header), BufferUtil.toSummaryString(_content), _lastContent, @@ -751,19 +766,19 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http case NEED_HEADER: { - _header = _bufferPool.acquire(_config.getResponseHeaderSize(), _config.isUseDirectByteBuffers()); + _header = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers); continue; } case NEED_CHUNK: { - chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, _config.isUseDirectByteBuffers()); + chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers); continue; } case NEED_CHUNK_TRAILER: { if (_chunk != null) _bufferPool.release(_chunk); - chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), _config.isUseDirectByteBuffers()); + chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers); continue; } case FLUSH: @@ -829,8 +844,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } case DONE: { - // If shutdown after commit, we can still close here. - if (getConnector().isShutdown()) + // If this is the end of the response and the connector was shutdown after response was committed, + // we can't add the Connection:close header, but we are still allowed to close the connection + // by shutting down the output. + if (getConnector().isShutdown() && _generator.isEnd() && _generator.isPersistent()) _shutdownOut = true; return Action.SUCCEEDED; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java index beee4fbe7a5..fc078ab1d90 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.server; +import java.util.Objects; + import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -32,7 +34,9 @@ import org.eclipse.jetty.util.annotation.Name; public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory { private final HttpConfiguration _config; - private boolean _recordHttpComplianceViolations = false; + private boolean _recordHttpComplianceViolations; + private boolean _useInputDirectByteBuffers; + private boolean _useOutputDirectByteBuffers; public HttpConnectionFactory() { @@ -42,10 +46,10 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements public HttpConnectionFactory(@Name("config") HttpConfiguration config) { super(HttpVersion.HTTP_1_1.asString()); - _config = config; - if (config == null) - throw new IllegalArgumentException("Null HttpConfiguration"); + _config = Objects.requireNonNull(config); addBean(_config); + setUseInputDirectByteBuffers(_config.isUseInputDirectByteBuffers()); + setUseOutputDirectByteBuffers(_config.isUseOutputDirectByteBuffers()); } @Override @@ -59,15 +63,37 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements return _recordHttpComplianceViolations; } - @Override - public Connection newConnection(Connector connector, EndPoint endPoint) - { - HttpConnection conn = new HttpConnection(_config, connector, endPoint, isRecordHttpComplianceViolations()); - return configure(conn, connector, endPoint); - } - public void setRecordHttpComplianceViolations(boolean recordHttpComplianceViolations) { this._recordHttpComplianceViolations = recordHttpComplianceViolations; } + + public boolean isUseInputDirectByteBuffers() + { + return _useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + _useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + public boolean isUseOutputDirectByteBuffers() + { + return _useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + _useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + + @Override + public Connection newConnection(Connector connector, EndPoint endPoint) + { + HttpConnection connection = new HttpConnection(_config, connector, endPoint, isRecordHttpComplianceViolations()); + connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); + connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); + return configure(connection, connector, endPoint); + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 50089ae163c..88c7d839faf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -127,14 +127,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable */ Interceptor getNextInterceptor(); - /** - * @return True if the Interceptor is optimized to receive direct - * {@link ByteBuffer}s in the {@link #write(ByteBuffer, boolean, Callback)} - * method. If false is returned, then passing direct buffers may cause - * inefficiencies. - */ - boolean isOptimizedForDirectBuffers(); - /** * Reset the buffers. *

    If the Interceptor contains buffers then reset them. @@ -417,7 +409,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable public ByteBuffer acquireBuffer() { if (_aggregate == null) - _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers()); + _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.isUseOutputDirectByteBuffers()); return _aggregate; } @@ -591,17 +583,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable // handle blocking write // Should we aggregate? - int capacity = getBufferSize(); + // Yes - if the write is smaller than the commitSize (==aggregate buffer size) + // and the write is not the last one, or is last but will fit in an already allocated aggregate buffer. boolean last = isLastContentToWrite(len); - if (!last && len <= _commitSize) + if (len <= _commitSize && (!last || len <= BufferUtil.space(_aggregate))) { acquireBuffer(); // YES - fill the aggregate with content from the buffer int filled = BufferUtil.fill(_aggregate, b, off, len); - // return if we are not complete, not full and filled all the content - if (filled == len && !BufferUtil.isFull(_aggregate)) + // return if we are not the last write and have aggregated all of the content + if (!last && filled == len && !BufferUtil.isFull(_aggregate)) return; // adjust offset/length @@ -1065,7 +1058,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable break; } - ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null; + ByteBuffer buffer = _channel.isUseOutputDirectByteBuffers() ? httpContent.getDirectBuffer() : null; if (buffer == null) buffer = httpContent.getIndirectBuffer(); @@ -1483,6 +1476,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable { super(callback); _in = in; + // Reading from InputStream requires byte[], don't use direct buffers. _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false); } @@ -1535,7 +1529,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable * An iterating callback that will take content from a * ReadableByteChannel and write it to the {@link HttpChannel}. * A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if - * {@link HttpChannel#useDirectBuffers()} is true. + * {@link HttpChannel#isUseOutputDirectByteBuffers()} is true. * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to * be notified as each buffer is written and only once all the input is consumed will the * wrapped {@link Callback#succeeded()} method be called. @@ -1550,7 +1544,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable { super(callback); _in = in; - _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers()); + _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.isUseOutputDirectByteBuffers()); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java index ace983bd311..88d5b58d7c9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java @@ -73,11 +73,4 @@ public interface HttpTransport * @param failure the failure that caused the abort. */ void abort(Throwable failure); - - /** - * Is the underlying transport optimized for DirectBuffer usage - * - * @return True if direct buffers can be used optimally. - */ - boolean isOptimizedForDirectBuffers(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java index 43261cd5c1b..308be3bb929 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java @@ -601,12 +601,6 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory _local = local; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return _endp.isOptimizedForDirectBuffers(); - } - @Override public InetSocketAddress getLocalAddress() { 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 55444641720..4471850c5d6 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 @@ -230,7 +230,7 @@ public class Request implements HttpServletRequest private long _timeStamp; private MultiParts _multiParts; //if the request is a multi-part mime private AsyncContextState _async; - private List _sessions; //list of sessions used during lifetime of request + private List _sessions; //list of sessions used during lifetime of request public Request(HttpChannel channel, HttpInput input) { @@ -363,32 +363,41 @@ public class Request implements HttpServletRequest */ public void enterSession(HttpSession s) { - if (s == null) + if (!(s instanceof Session)) return; if (_sessions == null) _sessions = new ArrayList<>(); if (LOG.isDebugEnabled()) LOG.debug("Request {} entering session={}", this, s); - _sessions.add(s); + _sessions.add((Session)s); } /** * Complete this request's access to a session. * - * @param s the session + * @param session the session */ - private void leaveSession(HttpSession s) + private void leaveSession(Session session) { - if (s == null) - return; - - Session session = (Session)s; if (LOG.isDebugEnabled()) LOG.debug("Request {} leaving session {}", this, session); session.getSessionHandler().complete(session); } + /** + * A response is being committed for a session, + * potentially write the session out before the + * client receives the response. + * @param session the session + */ + private void commitSession(Session session) + { + if (LOG.isDebugEnabled()) + LOG.debug("Response {} committing for session {}", this, session); + session.getSessionHandler().commit(session); + } + private MultiMap getParameters() { if (!_contentParamsExtracted) @@ -1490,13 +1499,26 @@ public class Request implements HttpServletRequest */ public void onCompleted() { - if (_sessions != null && _sessions.size() > 0) + if (_sessions != null) { - for (HttpSession s:_sessions) + for (Session s:_sessions) leaveSession(s); } } + /** + * Called when a response is about to be committed, ie sent + * back to the client + */ + public void onResponseCommit() + { + if (_sessions != null) + { + for (Session s:_sessions) + commitSession(s); + } + } + /** * Find a session that this request has already entered for the * given SessionHandler 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 282baf968c1..8456899589a 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 @@ -57,7 +57,7 @@ import org.eclipse.jetty.util.component.AttributeContainerMap; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.Locker; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.util.thread.ThreadPool; @@ -79,12 +79,12 @@ public class Server extends HandlerWrapper implements Attributes private final List _connectors = new CopyOnWriteArrayList<>(); private SessionIdManager _sessionIdManager; private boolean _stopAtShutdown; - private boolean _dumpAfterStart = false; - private boolean _dumpBeforeStop = false; + private boolean _dumpAfterStart; + private boolean _dumpBeforeStop; private ErrorHandler _errorHandler; private RequestLog _requestLog; - - private final Locker _dateLocker = new Locker(); + private boolean _dryRun; + private final AutoLock _dateLock = new AutoLock(); private volatile DateField _dateField; public Server() @@ -131,6 +131,16 @@ public class Server extends HandlerWrapper implements Attributes setServer(this); } + public boolean isDryRun() + { + return _dryRun; + } + + public void setDryRun(boolean dryRun) + { + _dryRun = dryRun; + } + public RequestLog getRequestLog() { return _requestLog; @@ -315,7 +325,7 @@ public class Server extends HandlerWrapper implements Attributes if (df == null || df._seconds != seconds) { - try (Locker.Lock lock = _dateLocker.lock()) + try (AutoLock lock = _dateLock.lock()) { df = _dateField; if (df == null || df._seconds != seconds) @@ -367,25 +377,33 @@ public class Server extends HandlerWrapper implements Attributes MultiException mex = new MultiException(); // Open network connector to ensure ports are available - _connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector -> + if (!_dryRun) { - try + _connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector -> { - connector.open(); - } - catch (Throwable th) - { - mex.add(th); - } - }); - - // Throw now if verified start sequence and there was an open exception - mex.ifExceptionThrow(); + try + { + connector.open(); + } + catch (Throwable th) + { + mex.add(th); + } + }); + // Throw now if verified start sequence and there was an open exception + mex.ifExceptionThrow(); + } // Start the server and components, but not connectors! // #start(LifeCycle) is overridden so that connectors are not started super.doStart(); + if (_dryRun) + { + LOG.info(String.format("Started(dry run) %s @%dms", this, Uptime.getUptime())); + throw new StopException(); + } + // start connectors for (Connector connector : _connectors) { @@ -402,7 +420,7 @@ public class Server extends HandlerWrapper implements Attributes } mex.ifExceptionThrow(); - LOG.info(String.format("Started @%dms", Uptime.getUptime())); + LOG.info(String.format("Started %s @%dms", this, Uptime.getUptime())); } catch (Throwable th) { @@ -423,7 +441,7 @@ public class Server extends HandlerWrapper implements Attributes } finally { - if (isDumpAfterStart()) + if (isDumpAfterStart() && !(_dryRun && isDumpBeforeStop())) dumpStdErr(); } } @@ -442,6 +460,7 @@ public class Server extends HandlerWrapper implements Attributes if (isDumpBeforeStop()) dumpStdErr(); + LOG.info(String.format("Stopped %s", this)); if (LOG.isDebugEnabled()) LOG.debug("doStop {}", this); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java index cb2e2257eb7..b02064d54a3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java @@ -123,7 +123,7 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand if (_server == server) return; if (isStarted()) - throw new IllegalStateException(STARTED); + throw new IllegalStateException(getState()); _server = server; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java index e56645f5dcf..5f533d6f549 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java @@ -128,7 +128,7 @@ public abstract class AbstractHandlerContainer extends AbstractHandler implement return; if (isStarted()) - throw new IllegalStateException(STARTED); + throw new IllegalStateException(getState()); super.setServer(server); Handler[] handlers = getHandlers(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java index bbb24c536b0..8c28bcdd7b9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java @@ -271,12 +271,6 @@ public class BufferedResponseHandler extends HandlerWrapper return _next; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return false; - } - protected void commit(Queue buffers, Callback callback) { // If only 1 buffer diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 4840dbeff33..6f2b3c563aa 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -126,7 +126,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu public static final int DEFAULT_LISTENER_TYPE_INDEX = 1; public static final int EXTENDED_LISTENER_TYPE_INDEX = 0; - private static final String __unimplmented = "Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler"; + private static final String UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER = "Unimplemented {} - use org.eclipse.jetty.servlet.ServletContextHandler"; private static final Logger LOG = Log.getLogger(ContextHandler.class); @@ -889,9 +889,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu protected void stopContext() throws Exception { - // stop all the handler hierarchy - super.doStop(); - // Call the context listeners ServletContextEvent event = new ServletContextEvent(_scontext); Collections.reverse(_destroySerletContextListeners); @@ -907,6 +904,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu ex.add(x); } } + + // stop all the handler hierarchy + try + { + super.doStop(); + } + catch (Exception x) + { + ex.add(x); + } + ex.ifExceptionThrow(); } @@ -2231,7 +2239,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu @Override public void log(String message, Throwable throwable) { - _logger.warn(message, throwable); + if (throwable == null) + _logger.warn(message); + else + _logger.warn(message, throwable); } /* @@ -2491,7 +2502,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu @Override public JspConfigDescriptor getJspConfigDescriptor() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()"); return null; } @@ -2684,138 +2695,141 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu @Override public Dynamic addFilter(String filterName, Class filterClass) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Class)"); return null; } @Override public Dynamic addFilter(String filterName, Filter filter) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Filter)"); return null; } @Override public Dynamic addFilter(String filterName, String className) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, String)"); return null; } @Override public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class servletClass) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Class)"); return null; } @Override public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Servlet)"); return null; } @Override public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, String)"); return null; } + /** + * @since Servlet 4.0 + */ @Override public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addJspFile(String, String)"); return null; } @Override public T createFilter(Class c) throws ServletException { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createFilter(Class)"); return null; } @Override public T createServlet(Class c) throws ServletException { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createServlet(Class)"); return null; } @Override public Set getDefaultSessionTrackingModes() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getDefaultSessionTrackingModes()"); return null; } @Override public Set getEffectiveSessionTrackingModes() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getEffectiveSessionTrackingModes()"); return null; } @Override public FilterRegistration getFilterRegistration(String filterName) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistration(String)"); return null; } @Override public Map getFilterRegistrations() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistrations()"); return null; } @Override public ServletRegistration getServletRegistration(String servletName) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistration(String)"); return null; } @Override public Map getServletRegistrations() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistrations()"); return null; } @Override public SessionCookieConfig getSessionCookieConfig() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionCookieConfig()"); return null; } @Override public void setSessionTrackingModes(Set sessionTrackingModes) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTrackingModes(Set)"); } @Override public void addListener(String className) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(String)"); } @Override public void addListener(T t) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(T)"); } @Override public void addListener(Class listenerClass) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(Class)"); } @Override @@ -2862,14 +2876,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu @Override public JspConfigDescriptor getJspConfigDescriptor() { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()"); return null; } @Override public void declareRoles(String... roleNames) { - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "declareRoles(String...)"); } @Override @@ -2878,49 +2892,67 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu return null; } + /** + * @since Servlet 4.0 + */ @Override public int getSessionTimeout() { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionTimeout()"); return 0; } + /** + * @since Servlet 4.0 + */ @Override public void setSessionTimeout(int sessionTimeout) { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTimeout(int)"); } + /** + * @since Servlet 4.0 + */ @Override public String getRequestCharacterEncoding() { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getRequestCharacterEncoding()"); return null; } + /** + * @since Servlet 4.0 + */ @Override public void setRequestCharacterEncoding(String encoding) { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setRequestCharacterEncoding(String)"); } + /** + * @since Servlet 4.0 + */ @Override public String getResponseCharacterEncoding() { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getResponseCharacterEncoding()"); return null; } + /** + * @since Servlet 4.0 + */ @Override public void setResponseCharacterEncoding(String encoding) { // TODO new in 4.0 - LOG.warn(__unimplmented); + LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setResponseCharacterEncoding(String)"); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java index dfea9fc93b7..9bbda66b116 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java @@ -82,7 +82,7 @@ public class HandlerCollection extends AbstractHandlerContainer public void setHandlers(Handler[] handlers) { if (!_mutableWhenRunning && isStarted()) - throw new IllegalStateException(STARTED); + throw new IllegalStateException(getState()); while (true) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java index 27a53fecf98..0c0897f5c1f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java @@ -74,7 +74,7 @@ public class HandlerWrapper extends AbstractHandlerContainer public void setHandler(Handler handler) { if (isStarted()) - throw new IllegalStateException(STARTED); + throw new IllegalStateException(getState()); // check for loops if (handler == this || (handler instanceof HandlerContainer && diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java index 7666bcb0641..feab2f5644a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java @@ -48,7 +48,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.Locker; +import org.eclipse.jetty.util.thread.AutoLock; /** *

    Handler to limit the threads per IP address for DOS protection

    @@ -241,7 +241,7 @@ public class ThreadLimitHandler extends HandlerWrapper } } - protected Remote getRemote(Request baseRequest) + private Remote getRemote(Request baseRequest) { Remote remote = (Remote)baseRequest.getAttribute(REMOTE); if (remote != null) @@ -329,11 +329,11 @@ public class ThreadLimitHandler extends HandlerWrapper return (comma >= 0) ? forwardedFor.substring(comma + 1).trim() : forwardedFor; } - private final class Remote implements Closeable + private static final class Remote implements Closeable { private final String _ip; private final int _limit; - private final Locker _locker = new Locker(); + private final AutoLock _lock = new AutoLock(); private int _permits; private Deque> _queue = new ArrayDeque<>(); private final CompletableFuture _permitted = CompletableFuture.completedFuture(this); @@ -346,7 +346,7 @@ public class ThreadLimitHandler extends HandlerWrapper public CompletableFuture acquire() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { // Do we have available passes? if (_permits < _limit) @@ -358,16 +358,16 @@ public class ThreadLimitHandler extends HandlerWrapper } // No pass available, so queue a new future - CompletableFuture pass = new CompletableFuture(); + CompletableFuture pass = new CompletableFuture<>(); _queue.addLast(pass); return pass; } } @Override - public void close() throws IOException + public void close() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { // reduce the allocated passes _permits--; @@ -396,14 +396,14 @@ public class ThreadLimitHandler extends HandlerWrapper @Override public String toString() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return String.format("R[ip=%s,p=%d,l=%d,q=%d]", _ip, _permits, _limit, _queue.size()); } } } - private final class RFC7239 extends QuotedCSV + private static final class RFC7239 extends QuotedCSV { String _for; 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 5c590b2d3e5..bb1022bbb7b 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 @@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.CompressedContentFormat; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; @@ -422,7 +423,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory @Override public Deflater getDeflater(Request request, long contentLength) { - String ua = request.getHttpFields().get(HttpHeader.USER_AGENT); + HttpFields httpFields = request.getHttpFields(); + String ua = httpFields.get(HttpHeader.USER_AGENT); if (ua != null && !isAgentGzipable(ua)) { LOG.debug("{} excluded user agent {}", this, request); @@ -436,16 +438,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } // check the accept encoding header - HttpField accept = request.getHttpFields().getField(HttpHeader.ACCEPT_ENCODING); - - if (accept == null) - { - LOG.debug("{} excluded !accept {}", this, request); - return null; - } - boolean gzip = accept.contains("gzip"); - - if (!gzip) + if (!httpFields.contains(HttpHeader.ACCEPT_ENCODING, "gzip")) { LOG.debug("{} excluded not gzip accept {}", this, request); return null; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java index 4a582348277..e1178fe2788 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java @@ -93,12 +93,6 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor return _interceptor; } - @Override - public boolean isOptimizedForDirectBuffers() - { - return false; // No point as deflator is in user space. - } - @Override public void write(ByteBuffer content, boolean complete, Callback callback) { 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 11b1ec7453b..329a44bec48 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 @@ -29,7 +29,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.Locker.Lock; +import org.eclipse.jetty.util.thread.AutoLock; /** * AbstractSessionCache @@ -91,6 +91,12 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * deleted from the SessionDataStore. */ protected boolean _removeUnloadableSessions; + + /** + * If true, when a response is about to be committed back to the client, + * a dirty session will be flushed to the session store. + */ + protected boolean _flushOnResponseCommit; /** * Create a new Session object from pre-existing session data @@ -110,9 +116,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements */ public abstract Session newSession(HttpServletRequest request, SessionData data); - /** - * @see org.eclipse.jetty.server.session.SessionCache#newSession(javax.servlet.http.HttpServletRequest, java.lang.String, long, long) - */ @Override public Session newSession(HttpServletRequest request, String id, long time, long maxInactiveMs) { @@ -169,7 +172,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements /** * PlaceHolder */ - protected class PlaceHolderSession extends Session + protected static class PlaceHolderSession extends Session { /** @@ -199,9 +202,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements return _handler; } - /** - * @see org.eclipse.jetty.server.session.SessionCache#initialize(org.eclipse.jetty.server.session.SessionContext) - */ @Override public void initialize(SessionContext context) { @@ -248,9 +248,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements return _sessionDataStore; } - /** - * @see org.eclipse.jetty.server.session.SessionCache#setSessionDataStore(org.eclipse.jetty.server.session.SessionDataStore) - */ @Override public void setSessionDataStore(SessionDataStore sessionStore) { @@ -258,9 +255,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements _sessionDataStore = sessionStore; } - /** - * @see org.eclipse.jetty.server.session.SessionCache#getEvictionPolicy() - */ @ManagedAttribute(value = "session eviction policy", readonly = true) @Override public int getEvictionPolicy() @@ -272,8 +266,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * -1 means we never evict inactive sessions. * 0 means we evict a session after the last request for it exits * >0 is the number of seconds after which we evict inactive sessions from the cache - * - * @see org.eclipse.jetty.server.session.SessionCache#setEvictionPolicy(int) */ @Override public void setEvictionPolicy(int evictionTimeout) @@ -308,7 +300,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * If a session's data cannot be loaded from the store without error, remove * it from the persistent store. * - * @param removeUnloadableSessions if true unloadable sessions will be removed from session store + * @param removeUnloadableSessions if {@code true} unloadable sessions will be removed from session store */ @Override public void setRemoveUnloadableSessions(boolean removeUnloadableSessions) @@ -316,14 +308,24 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements _removeUnloadableSessions = removeUnloadableSessions; } + @Override + public void setFlushOnResponseCommit(boolean flushOnResponseCommit) + { + _flushOnResponseCommit = flushOnResponseCommit; + } + + @Override + public boolean isFlushOnResponseCommit() + { + return _flushOnResponseCommit; + } + /** * Get a session object. * * If the session object is not in this session store, try getting * the data for it from a SessionDataStore associated with the * session manager. The usage count of the session is incremented. - * - * @see org.eclipse.jetty.server.session.SessionCache#get(java.lang.String) */ @Override public Session get(String id) throws Exception @@ -339,8 +341,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * * @param id The session to retrieve * @param enter if true, the usage count of the session will be incremented - * @return - * @throws Exception + * @return the Session object + * @throws Exception if the session cannot be retrieved */ protected Session getAndEnter(String id, boolean enter) throws Exception { @@ -361,7 +363,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements //didn't get a session, try and create one and put in a placeholder for it PlaceHolderSession phs = new PlaceHolderSession(_handler, new SessionData(id, null, null, 0, 0, 0, 0)); - Lock phsLock = phs.lock(); + AutoLock phsLock = phs.lock(); Session s = doPutIfAbsent(id, phs); if (s == null) { @@ -377,7 +379,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements break; } - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { //swap it in instead of the placeholder boolean success = doReplace(id, phs, session); @@ -414,7 +416,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements { //my placeholder didn't win, check the session returned phsLock.close(); - try (Lock lock = s.lock()) + try (AutoLock lock = s.lock()) { //is it a placeholder? or is a non-resident session? In both cases, chuck it away and start again if (!s.isResident() || s instanceof PlaceHolderSession) @@ -433,7 +435,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements else { //check the session returned - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { //is it a placeholder? or is it passivated? In both cases, chuck it away and start again if (!session.isResident() || session instanceof PlaceHolderSession) @@ -494,8 +496,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * Add an entirely new session (created by the application calling Request.getSession(true)) * to the cache. The usage count of the fresh session is incremented. * - * @param id the id - * @param session + * @param id the session id + * @param session the new session to add */ @Override public void add(String id, Session session) throws Exception @@ -503,7 +505,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements if (id == null || session == null) throw new IllegalArgumentException("Add key=" + id + " session=" + (session == null ? "null" : session.getId())); - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { if (session.getSessionHandler() == null) throw new IllegalStateException("Session " + id + " is not managed"); @@ -522,8 +524,41 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements } /** - * @deprecated + * A response that has accessed this session is about to + * be returned to the client. Pass the session to the store + * to persist, so that any changes will be visible to + * subsequent requests on the same node (if using NullSessionCache), + * or on other nodes. */ + @Override + public void commit(Session session) throws Exception + { + if (session == null) + return; + + try (AutoLock lock = session.lock()) + { + //only write the session out at this point if the attributes changed. If only + //the lastAccess/expiry time changed defer the write until the last request exits + if (session.getSessionData().isDirty() && _flushOnResponseCommit) + { + if (LOG.isDebugEnabled()) + LOG.debug("Flush session {} on response commit", session); + //save the session + if (!_sessionDataStore.isPassivating()) + { + _sessionDataStore.store(session.getId(), session.getSessionData()); + } + else + { + session.willPassivate(); + _sessionDataStore.store(session.getId(), session.getSessionData()); + session.didActivate(); + } + } + } + } + @Override public void put(String id, Session session) throws Exception { @@ -542,8 +577,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * * If the evictionPolicy == SessionCache.EVICT_ON_SESSION_EXIT then after we have saved * the session, we evict it from the cache. - * - * @see org.eclipse.jetty.server.session.SessionCache#release(java.lang.String, org.eclipse.jetty.server.session.Session) */ @Override public void release(String id, Session session) throws Exception @@ -551,7 +584,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements if (id == null || session == null) throw new IllegalArgumentException("Put key=" + id + " session=" + (session == null ? "null" : session.getId())); - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { if (session.getSessionHandler() == null) throw new IllegalStateException("Session " + id + " is not managed"); @@ -630,7 +663,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements * it will check with the data store. * * @throws Exception the Exception - * @see org.eclipse.jetty.server.session.SessionCache#exists(java.lang.String) */ @Override public boolean exists(String id) throws Exception @@ -639,7 +671,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements Session s = doGet(id); if (s != null) { - try (Lock lock = s.lock()) + try (AutoLock lock = s.lock()) { //wait for the lock and check the validity of the session return s.isValid(); @@ -653,8 +685,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements /** * Check to see if this cache contains an entry for the session * corresponding to the session id. - * - * @see org.eclipse.jetty.server.session.SessionCache#contains(java.lang.String) */ @Override public boolean contains(String id) throws Exception @@ -665,8 +695,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements /** * Remove a session object from this store and from any backing store. - * - * @see org.eclipse.jetty.server.session.SessionCache#delete(java.lang.String) */ @Override public Session delete(String id) throws Exception @@ -691,9 +719,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements return doDelete(id); } - /** - * @see org.eclipse.jetty.server.session.SessionCache#checkExpiration(Set) - */ @Override public Set checkExpiration(Set candidates) { @@ -741,7 +766,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements if (LOG.isDebugEnabled()) LOG.debug("Checking for idle {}", session.getId()); - try (Lock s = session.lock()) + try (AutoLock lock = session.lock()) { if (getEvictionPolicy() > 0 && session.isIdleLongerThan(getEvictionPolicy()) && session.isValid() && session.isResident() && session.getRequests() <= 0) @@ -759,6 +784,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements if (_sessionDataStore.isPassivating()) session.willPassivate(); + //Fake being dirty to force the write + session.getSessionData().setDirty(true); _sessionDataStore.store(session.getId(), session.getSessionData()); } @@ -768,7 +795,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements catch (Exception e) { LOG.warn("Passivation of idle session {} failed", session.getId(), e); - //session.updateInactivityTimer(); } } } @@ -803,7 +829,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements if (session == null) return; - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { final String oldId = session.getId(); session.checkValidForWrite(); //can't change id on invalid session @@ -826,9 +852,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements } } - /** - * @see org.eclipse.jetty.server.session.SessionCache#setSaveOnInactiveEviction(boolean) - */ @Override public void setSaveOnInactiveEviction(boolean saveOnEvict) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java new file mode 100644 index 00000000000..b29e5585a19 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java @@ -0,0 +1,114 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.session; + +/** + * AbstractSessionCacheFactory + * + * Base class for SessionCacheFactories. + * + */ +public abstract class AbstractSessionCacheFactory implements SessionCacheFactory +{ + int _evictionPolicy; + boolean _saveOnInactiveEvict; + boolean _saveOnCreate; + boolean _removeUnloadableSessions; + boolean _flushOnResponseCommit; + + /** + * @return the flushOnResponseCommit + */ + public boolean isFlushOnResponseCommit() + { + return _flushOnResponseCommit; + } + + /** + * @param flushOnResponseCommit the flushOnResponseCommit to set + */ + public void setFlushOnResponseCommit(boolean flushOnResponseCommit) + { + _flushOnResponseCommit = flushOnResponseCommit; + } + + /** + * @return the saveOnCreate + */ + public boolean isSaveOnCreate() + { + return _saveOnCreate; + } + + /** + * @param saveOnCreate the saveOnCreate to set + */ + public void setSaveOnCreate(boolean saveOnCreate) + { + _saveOnCreate = saveOnCreate; + } + + /** + * @return the removeUnloadableSessions + */ + public boolean isRemoveUnloadableSessions() + { + return _removeUnloadableSessions; + } + + /** + * @param removeUnloadableSessions the removeUnloadableSessions to set + */ + public void setRemoveUnloadableSessions(boolean removeUnloadableSessions) + { + _removeUnloadableSessions = removeUnloadableSessions; + } + + /** + * @return the evictionPolicy + */ + public int getEvictionPolicy() + { + return _evictionPolicy; + } + + /** + * @param evictionPolicy the evictionPolicy to set + */ + public void setEvictionPolicy(int evictionPolicy) + { + _evictionPolicy = evictionPolicy; + } + + /** + * @return the saveOnInactiveEvict + */ + public boolean isSaveOnInactiveEvict() + { + return _saveOnInactiveEvict; + } + + /** + * @param saveOnInactiveEvict the saveOnInactiveEvict to set + */ + public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict) + { + _saveOnInactiveEvict = saveOnInactiveEvict; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java index 17ce67038ce..813a661f2d5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java @@ -81,24 +81,20 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem public SessionData load(String id) throws Exception { if (!isStarted()) - throw new IllegalStateException ("Not started"); + throw new IllegalStateException("Not started"); final AtomicReference reference = new AtomicReference(); final AtomicReference exception = new AtomicReference(); - Runnable r = new Runnable() + Runnable r = () -> { - @Override - public void run() + try { - try - { - reference.set(doLoad(id)); - } - catch (Exception e) - { - exception.set(e); - } + reference.set(doLoad(id)); + } + catch (Exception e) + { + exception.set(e); } }; @@ -129,10 +125,14 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem long savePeriodMs = (_savePeriodSec <= 0 ? 0 : TimeUnit.SECONDS.toMillis(_savePeriodSec)); if (LOG.isDebugEnabled()) - LOG.debug("Store: id={}, dirty={}, lsave={}, period={}, elapsed={}", id, data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis() - lastSave)); + { + LOG.debug("Store: id={}, mdirty={}, dirty={}, lsave={}, period={}, elapsed={}", id, data.isMetaDataDirty(), + data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis() - lastSave)); + } - //save session if attribute changed or never been saved or time between saves exceeds threshold - if (data.isDirty() || (lastSave <= 0) || ((System.currentTimeMillis() - lastSave) >= savePeriodMs)) + //save session if attribute changed, never been saved or metadata changed (eg expiry time) and save interval exceeded + if (data.isDirty() || (lastSave <= 0) || + (data.isMetaDataDirty() && ((System.currentTimeMillis() - lastSave) >= savePeriodMs))) { //set the last saved time to now data.setLastSaved(System.currentTimeMillis()); @@ -140,7 +140,7 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem { //call the specific store method, passing in previous save time doStore(id, data, lastSave); - data.setDirty(false); //only undo the dirty setting if we saved it + data.clean(); //unset all dirty flags } catch (Exception e) { @@ -163,7 +163,7 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem public Set getExpired(Set candidates) { if (!isStarted()) - throw new IllegalStateException ("Not started"); + throw new IllegalStateException("Not started"); try { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java index b1261647414..87b945e5a0b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java @@ -23,80 +23,8 @@ package org.eclipse.jetty.server.session; * * Factory for creating new DefaultSessionCaches. */ -public class DefaultSessionCacheFactory implements SessionCacheFactory +public class DefaultSessionCacheFactory extends AbstractSessionCacheFactory { - int _evictionPolicy; - boolean _saveOnInactiveEvict; - boolean _saveOnCreate; - boolean _removeUnloadableSessions; - - /** - * @return the saveOnCreate - */ - public boolean isSaveOnCreate() - { - return _saveOnCreate; - } - - /** - * @param saveOnCreate the saveOnCreate to set - */ - public void setSaveOnCreate(boolean saveOnCreate) - { - _saveOnCreate = saveOnCreate; - } - - /** - * @return the removeUnloadableSessions - */ - public boolean isRemoveUnloadableSessions() - { - return _removeUnloadableSessions; - } - - /** - * @param removeUnloadableSessions the removeUnloadableSessions to set - */ - public void setRemoveUnloadableSessions(boolean removeUnloadableSessions) - { - _removeUnloadableSessions = removeUnloadableSessions; - } - - /** - * @return the evictionPolicy - */ - public int getEvictionPolicy() - { - return _evictionPolicy; - } - - /** - * @param evictionPolicy the evictionPolicy to set - */ - public void setEvictionPolicy(int evictionPolicy) - { - _evictionPolicy = evictionPolicy; - } - - /** - * @return the saveOnInactiveEvict - */ - public boolean isSaveOnInactiveEvict() - { - return _saveOnInactiveEvict; - } - - /** - * @param saveOnInactiveEvict the saveOnInactiveEvict to set - */ - public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict) - { - _saveOnInactiveEvict = saveOnInactiveEvict; - } - - /** - * @see org.eclipse.jetty.server.session.SessionCacheFactory#getSessionCache(org.eclipse.jetty.server.session.SessionHandler) - */ @Override public SessionCache getSessionCache(SessionHandler handler) { @@ -105,6 +33,7 @@ public class DefaultSessionCacheFactory implements SessionCacheFactory cache.setSaveOnInactiveEviction(isSaveOnInactiveEvict()); cache.setSaveOnCreate(isSaveOnCreate()); cache.setRemoveUnloadableSessions(isRemoveUnloadableSessions()); + cache.setFlushOnResponseCommit(isFlushOnResponseCommit()); return cache; } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java index e22919eed7b..c589aab4bdb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java @@ -18,12 +18,7 @@ package org.eclipse.jetty.server.session; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSessionAttributeListener; -import javax.servlet.http.HttpSessionBindingEvent; /** * NullSessionCache @@ -35,151 +30,6 @@ import javax.servlet.http.HttpSessionBindingEvent; */ public class NullSessionCache extends AbstractSessionCache { - /** - * If the writethrough mode is ALWAYS or NEW, then use an - * attribute listener to ascertain when the attribute has changed. - * - */ - public class WriteThroughAttributeListener implements HttpSessionAttributeListener - { - Set _sessionsBeingWritten = ConcurrentHashMap.newKeySet(); - - @Override - public void attributeAdded(HttpSessionBindingEvent event) - { - doAttributeChanged(event); - } - - @Override - public void attributeRemoved(HttpSessionBindingEvent event) - { - doAttributeChanged(event); - } - - @Override - public void attributeReplaced(HttpSessionBindingEvent event) - { - doAttributeChanged(event); - } - - private void doAttributeChanged(HttpSessionBindingEvent event) - { - if (_writeThroughMode == WriteThroughMode.ON_EXIT) - return; - - Session session = (Session)event.getSession(); - - SessionDataStore store = getSessionDataStore(); - - if (store == null) - return; - - if (_writeThroughMode == WriteThroughMode.ALWAYS || (_writeThroughMode == WriteThroughMode.NEW && session.isNew())) - { - //ensure that a call to willPassivate doesn't result in a passivation - //listener removing an attribute, which would cause this listener to - //be called again - if (_sessionsBeingWritten.add(session)) - { - try - { - //should hold the lock on the session, but as sessions are never shared - //with the NullSessionCache, there can be no other thread modifying the - //same session at the same time (although of course there can be another - //request modifying its copy of the session data, so it is impossible - //to guarantee the order of writes). - if (store.isPassivating()) - session.willPassivate(); - store.store(session.getId(), session.getSessionData()); - if (store.isPassivating()) - session.didActivate(); - } - catch (Exception e) - { - LOG.warn("Write through of {} failed", e); - } - finally - { - _sessionsBeingWritten.remove(session); - } - } - } - } - } - - /** - * Defines the circumstances a session will be written to the backing store. - */ - public enum WriteThroughMode - { - /** - * ALWAYS means write through every attribute change. - */ - ALWAYS, - /** - * NEW means to write through every attribute change only - * while the session is freshly created, ie its id has not yet been returned to the client - */ - NEW, - /** - * ON_EXIT means write the session only when the request exits - * (which is the default behaviour of AbstractSessionCache) - */ - ON_EXIT - } - - private WriteThroughMode _writeThroughMode = WriteThroughMode.ON_EXIT; - protected WriteThroughAttributeListener _listener = null; - - /** - * @return the writeThroughMode - */ - public WriteThroughMode getWriteThroughMode() - { - return _writeThroughMode; - } - - /** - * @param writeThroughMode the writeThroughMode to set - */ - public void setWriteThroughMode(WriteThroughMode writeThroughMode) - { - if (getSessionHandler() == null) - throw new IllegalStateException("No SessionHandler"); - - //assume setting null is the same as ON_EXIT - if (writeThroughMode == null) - { - if (_listener != null) - getSessionHandler().removeEventListener(_listener); - _listener = null; - _writeThroughMode = WriteThroughMode.ON_EXIT; - return; - } - - switch (writeThroughMode) - { - case ON_EXIT: - { - if (_listener != null) - getSessionHandler().removeEventListener(_listener); - _listener = null; - break; - } - case NEW: - case ALWAYS: - { - if (_listener == null) - { - _listener = new WriteThroughAttributeListener(); - getSessionHandler().addEventListener(_listener); - } - break; - } - } - _writeThroughMode = writeThroughMode; - } - /** * @param handler The SessionHandler related to this SessionCache */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java index dd4a4cd0986..40bf5f45f9d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java @@ -18,75 +18,51 @@ package org.eclipse.jetty.server.session; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + /** * NullSessionCacheFactory * * Factory for NullSessionCaches. */ -public class NullSessionCacheFactory implements SessionCacheFactory +public class NullSessionCacheFactory extends AbstractSessionCacheFactory { - boolean _saveOnCreate; - boolean _removeUnloadableSessions; - NullSessionCache.WriteThroughMode _writeThroughMode; - - /** - * @return the writeThroughMode - */ - public NullSessionCache.WriteThroughMode getWriteThroughMode() + private static final Logger LOG = Log.getLogger("org.eclipse.jetty.server.session"); + + @Override + public int getEvictionPolicy() { - return _writeThroughMode; + return SessionCache.EVICT_ON_SESSION_EXIT; //never actually stored } - /** - * @param writeThroughMode the writeThroughMode to set - */ - public void setWriteThroughMode(NullSessionCache.WriteThroughMode writeThroughMode) + @Override + public void setEvictionPolicy(int evictionPolicy) { - _writeThroughMode = writeThroughMode; + if (LOG.isDebugEnabled()) + LOG.debug("Ignoring eviction policy setting for NullSessionCaches"); } - /** - * @return the saveOnCreate - */ - public boolean isSaveOnCreate() + @Override + public boolean isSaveOnInactiveEvict() { - return _saveOnCreate; + return false; //never kept in cache } - /** - * @param saveOnCreate the saveOnCreate to set - */ - public void setSaveOnCreate(boolean saveOnCreate) + @Override + public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict) { - _saveOnCreate = saveOnCreate; + if (LOG.isDebugEnabled()) + LOG.debug("Ignoring eviction policy setting for NullSessionCaches"); } - /** - * @return the removeUnloadableSessions - */ - public boolean isRemoveUnloadableSessions() - { - return _removeUnloadableSessions; - } - - /** - * @param removeUnloadableSessions the removeUnloadableSessions to set - */ - public void setRemoveUnloadableSessions(boolean removeUnloadableSessions) - { - _removeUnloadableSessions = removeUnloadableSessions; - } - - /** - * @see org.eclipse.jetty.server.session.SessionCacheFactory#getSessionCache(org.eclipse.jetty.server.session.SessionHandler) - */ @Override public SessionCache getSessionCache(SessionHandler handler) { NullSessionCache cache = new NullSessionCache(handler); cache.setSaveOnCreate(isSaveOnCreate()); cache.setRemoveUnloadableSessions(isRemoveUnloadableSessions()); - cache.setWriteThroughMode(_writeThroughMode); + cache.setFlushOnResponseCommit(isFlushOnResponseCommit()); return cache; } } 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 f33b4d83f1f..50656e17f40 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 @@ -36,8 +36,7 @@ import javax.servlet.http.HttpSessionEvent; import org.eclipse.jetty.io.CyclicTimeout; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.Locker; -import org.eclipse.jetty.util.thread.Locker.Lock; +import org.eclipse.jetty.util.thread.AutoLock; /** * Session @@ -73,15 +72,11 @@ public class Session implements SessionHandler.SessionIf VALID, INVALID, INVALIDATING, CHANGING } - ; - public enum IdState { SET, CHANGING } - ; - protected final SessionData _sessionData; // the actual data associated with // a session @@ -98,7 +93,7 @@ public class Session implements SessionHandler.SessionIf protected State _state = State.VALID; // state of the session:valid,invalid // or being invalidated - protected Locker _lock = new Locker(); // sync lock + protected AutoLock _lock = new AutoLock(); protected Condition _stateChangeCompleted = _lock.newCondition(); protected boolean _resident = false; protected final SessionInactivityTimer _sessionInactivityTimer; @@ -128,7 +123,7 @@ public class Session implements SessionHandler.SessionIf long now = System.currentTimeMillis(); //handle what to do with the session after the timer expired getSessionHandler().sessionInactivityTimerExpired(Session.this, now); - try (Lock lock = Session.this.lock()) + try (AutoLock lock = Session.this.lock()) { //grab the lock and check what happened to the session: if it didn't get evicted and //it hasn't expired, we need to reset the timer @@ -213,7 +208,7 @@ public class Session implements SessionHandler.SessionIf */ public long getRequests() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _requests; } @@ -226,7 +221,7 @@ public class Session implements SessionHandler.SessionIf protected void cookieSet() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { _sessionData.setCookieSet(_sessionData.getAccessed()); } @@ -234,7 +229,7 @@ public class Session implements SessionHandler.SessionIf protected void use() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { _requests++; @@ -247,7 +242,7 @@ public class Session implements SessionHandler.SessionIf protected boolean access(long time) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { if (!isValid() || !isResident()) return false; @@ -267,7 +262,7 @@ public class Session implements SessionHandler.SessionIf protected void complete() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { _requests--; @@ -294,7 +289,7 @@ public class Session implements SessionHandler.SessionIf */ protected boolean isExpiredAt(long time) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _sessionData.isExpiredAt(time); } @@ -309,7 +304,7 @@ public class Session implements SessionHandler.SessionIf protected boolean isIdleLongerThan(int sec) { long now = System.currentTimeMillis(); - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return ((_sessionData.getAccessed() + (sec * 1000)) <= now); } @@ -350,7 +345,7 @@ public class Session implements SessionHandler.SessionIf */ public void unbindValue(java.lang.String name, Object value) { - if (value != null && value instanceof HttpSessionBindingListener) + if (value instanceof HttpSessionBindingListener) ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this, name)); } @@ -363,7 +358,7 @@ public class Session implements SessionHandler.SessionIf */ public void bindValue(java.lang.String name, Object value) { - if (value != null && value instanceof HttpSessionBindingListener) + if (value instanceof HttpSessionBindingListener) ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this, name)); } @@ -372,16 +367,31 @@ public class Session implements SessionHandler.SessionIf */ public void didActivate() { - HttpSessionEvent event = new HttpSessionEvent(this); - for (Iterator iter = _sessionData.getKeys().iterator(); iter.hasNext();) + //A passivate listener might remove a non-serializable attribute that + //the activate listener might put back in again, which would spuriously + //set the dirty bit to true, causing another round of passivate/activate + //when the request exits. The store clears the dirty bit if it does a + //save, so ensure dirty flag is set to the value determined by the store, + //not a passivation listener. + boolean dirty = getSessionData().isDirty(); + + try { - Object value = _sessionData.getAttribute(iter.next()); - if (value instanceof HttpSessionActivationListener) + HttpSessionEvent event = new HttpSessionEvent(this); + for (String name : _sessionData.getKeys()) { - HttpSessionActivationListener listener = (HttpSessionActivationListener)value; - listener.sessionDidActivate(event); + Object value = _sessionData.getAttribute(name); + if (value instanceof HttpSessionActivationListener) + { + HttpSessionActivationListener listener = (HttpSessionActivationListener)value; + listener.sessionDidActivate(event); + } } } + finally + { + getSessionData().setDirty(dirty); + } } /** @@ -390,9 +400,9 @@ public class Session implements SessionHandler.SessionIf public void willPassivate() { HttpSessionEvent event = new HttpSessionEvent(this); - for (Iterator iter = _sessionData.getKeys().iterator(); iter.hasNext();) + for (String name : _sessionData.getKeys()) { - Object value = _sessionData.getAttribute(iter.next()); + Object value = _sessionData.getAttribute(name); if (value instanceof HttpSessionActivationListener) { HttpSessionActivationListener listener = (HttpSessionActivationListener)value; @@ -403,7 +413,7 @@ public class Session implements SessionHandler.SessionIf public boolean isValid() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.VALID; } @@ -411,21 +421,15 @@ public class Session implements SessionHandler.SessionIf public boolean isInvalid() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.INVALID || _state == State.INVALIDATING; } } - public boolean isChanging() - { - checkLocked(); - return _state == State.CHANGING; - } - public long getCookieSetTime() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _sessionData.getCookieSet(); } @@ -434,7 +438,7 @@ public class Session implements SessionHandler.SessionIf @Override public long getCreationTime() throws IllegalStateException { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); return _sessionData.getCreated(); @@ -447,7 +451,7 @@ public class Session implements SessionHandler.SessionIf @Override public String getId() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _sessionData.getId(); } @@ -474,7 +478,7 @@ public class Session implements SessionHandler.SessionIf @Override public long getLastAccessedTime() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { if (isInvalid()) { @@ -501,10 +505,14 @@ public class Session implements SessionHandler.SessionIf @Override public void setMaxInactiveInterval(int secs) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { _sessionData.setMaxInactiveMs((long)secs * 1000L); _sessionData.calcAndSetExpiry(); + //dirty metadata writes can be skipped, but changing the + //maxinactiveinterval should write the session out because + //it may affect the session on other nodes, or on the same + //node in the case of the nullsessioncache _sessionData.setDirty(true); if (LOG.isDebugEnabled()) @@ -530,7 +538,7 @@ public class Session implements SessionHandler.SessionIf { long time = 0; - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { long remaining = _sessionData.getExpiry() - now; long maxInactive = _sessionData.getMaxInactiveMs(); @@ -594,7 +602,7 @@ public class Session implements SessionHandler.SessionIf @Override public int getMaxInactiveInterval() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { long maxInactiveMs = _sessionData.getMaxInactiveMs(); return (int)(maxInactiveMs < 0 ? -1 : maxInactiveMs / 1000); @@ -624,8 +632,6 @@ public class Session implements SessionHandler.SessionIf */ protected void checkValidForWrite() throws IllegalStateException { - checkLocked(); - if (_state == State.INVALID) throw new IllegalStateException("Not valid for write: id=" + _sessionData.getId() + " created=" + _sessionData.getCreated() + @@ -649,8 +655,6 @@ public class Session implements SessionHandler.SessionIf */ protected void checkValidForRead() throws IllegalStateException { - checkLocked(); - if (_state == State.INVALID) throw new IllegalStateException("Invalid for read: id=" + _sessionData.getId() + " created=" + _sessionData.getCreated() + @@ -666,19 +670,13 @@ public class Session implements SessionHandler.SessionIf throw new IllegalStateException("Invalid for read: id=" + _sessionData.getId() + " not resident"); } - protected void checkLocked() throws IllegalStateException - { - if (!_lock.isLocked()) - throw new IllegalStateException("Session not locked"); - } - /** * @see javax.servlet.http.HttpSession#getAttribute(java.lang.String) */ @Override public Object getAttribute(String name) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); return _sessionData.getAttribute(name); @@ -692,7 +690,7 @@ public class Session implements SessionHandler.SessionIf @Deprecated(since = "Servlet API 2.2") public Object getValue(String name) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); return _sessionData.getAttribute(name); @@ -705,11 +703,11 @@ public class Session implements SessionHandler.SessionIf @Override public Enumeration getAttributeNames() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); final Iterator itor = _sessionData.getKeys().iterator(); - return new Enumeration() + return new Enumeration<>() { @Override @@ -745,7 +743,7 @@ public class Session implements SessionHandler.SessionIf @Deprecated(since = "Servlet API 2.2") public String[] getValueNames() throws IllegalStateException { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); Iterator itor = _sessionData.getKeys().iterator(); @@ -768,7 +766,7 @@ public class Session implements SessionHandler.SessionIf public void setAttribute(String name, Object value) { Object old = null; - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { // if session is not valid, don't accept the set checkValidForWrite(); @@ -822,7 +820,7 @@ public class Session implements SessionHandler.SessionIf String id = null; String extendedId = null; - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { while (true) { @@ -858,7 +856,7 @@ public class Session implements SessionHandler.SessionIf String newId = _handler._sessionIdManager.renewSessionId(id, extendedId, request); - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -936,7 +934,7 @@ public class Session implements SessionHandler.SessionIf * * @return the lock */ - public Lock lock() + public AutoLock lock() { return _lock.lock(); } @@ -948,7 +946,7 @@ public class Session implements SessionHandler.SessionIf { boolean result = false; - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { while (true) @@ -1007,7 +1005,7 @@ public class Session implements SessionHandler.SessionIf */ protected void finishInvalidate() throws IllegalStateException { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { try { @@ -1045,7 +1043,7 @@ public class Session implements SessionHandler.SessionIf @Override public boolean isNew() throws IllegalStateException { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { checkValidForRead(); return _newSession; @@ -1054,7 +1052,7 @@ public class Session implements SessionHandler.SessionIf public void setIdChanged(boolean changed) { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { _idChanged = changed; } @@ -1062,7 +1060,7 @@ public class Session implements SessionHandler.SessionIf public boolean isIdChanged() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return _idChanged; } @@ -1080,9 +1078,6 @@ public class Session implements SessionHandler.SessionIf return _sessionData; } - /** - * - */ public void setResident(boolean resident) { _resident = resident; @@ -1099,7 +1094,7 @@ public class Session implements SessionHandler.SessionIf @Override public String toString() { - try (Lock lock = _lock.lock()) + try (AutoLock lock = _lock.lock()) { return String.format("%s@%x{id=%s,x=%s,req=%d,res=%b}", getClass().getSimpleName(), diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java index 69bcceeafa1..b47810a9225 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java @@ -126,11 +126,11 @@ public interface SessionCache extends LifeCycle * @param id the session id * @param session the current session object * @throws Exception if any error occurred - * @deprecated @see release + * @deprecated use {@link #release(String, Session)} instead */ + @Deprecated void put(String id, Session session) throws Exception; - - + /** * Finish using a Session. This is called by the SessionHandler * once a request is finished with a Session. SessionCache @@ -142,7 +142,17 @@ public interface SessionCache extends LifeCycle * @throws Exception if any error occurred */ void release(String id, Session session) throws Exception; - + + /** + * Called when a response is about to be committed. The + * cache can write the session to ensure that the + * SessionDataStore contains changes to the session + * that occurred during the lifetime of the request. This + * can help ensure that if a subsequent request goes to a + * different server, it will be able to see the session + * changes via the shared store. + */ + void commit(Session session) throws Exception; /** * Check to see if a Session is in the cache. Does NOT consult @@ -266,4 +276,18 @@ public interface SessionCache extends LifeCycle * @return if true unloadable session will be deleted */ boolean isRemoveUnloadableSessions(); + + /** + * If true, a dirty session will be written to the SessionDataStore + * just before a response is returned to the client. This ensures + * that subsequent requests to either the same node or a different + * node see the changed session data. + */ + void setFlushOnResponseCommit(boolean flushOnResponse); + + /** + * @return true if dirty sessions should be written + * before the response is committed. + */ + boolean isFlushOnResponseCommit(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java index 57a041c9ccc..3b3a9fd0493 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java @@ -58,6 +58,7 @@ public class SessionData implements Serializable protected Map _attributes; protected boolean _dirty; protected long _lastSaved; //time in msec since last save + protected boolean _metaDataDirty; //non-attribute data has changed /** * Serialize the attribute map of the session. @@ -161,11 +162,6 @@ public class SessionData implements Serializable } public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs) - { - this(id, cpath, vhost, created, accessed, lastAccessed, maxInactiveMs, new ConcurrentHashMap()); - } - - public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs, Map attributes) { _id = id; setContextPath(cpath); @@ -175,7 +171,13 @@ public class SessionData implements Serializable _lastAccessed = lastAccessed; _maxInactiveMs = maxInactiveMs; calcAndSetExpiry(); - _attributes = attributes; + _attributes = new ConcurrentHashMap<>(); + } + + public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs, Map attributes) + { + this(id, cpath, vhost, created, accessed, lastAccessed, maxInactiveMs); + putAllAttributes(attributes); } /** @@ -239,6 +241,22 @@ public class SessionData implements Serializable setDirty(true); } + /** + * @return the metaDataDirty + */ + public boolean isMetaDataDirty() + { + return _metaDataDirty; + } + + /** + * @param metaDataDirty true means non-attribute data has changed + */ + public void setMetaDataDirty(boolean metaDataDirty) + { + _metaDataDirty = metaDataDirty; + } + /** * @param name the name of the attribute * @return the value of the attribute named @@ -266,6 +284,15 @@ public class SessionData implements Serializable return old; } + /** + * Clear all dirty flags. + */ + public void clean() + { + setDirty(false); + setMetaDataDirty(false); + } + public void putAllAttributes(Map attributes) { _attributes.putAll(attributes); @@ -365,11 +392,13 @@ public class SessionData implements Serializable public void calcAndSetExpiry(long time) { setExpiry(calcExpiry(time)); + setMetaDataDirty(true); } public void calcAndSetExpiry() { setExpiry(calcExpiry()); + setMetaDataDirty(true); } public long getCreated() 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 4f4dc81d758..0505da7722c 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 @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; - import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; @@ -59,7 +58,7 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; -import org.eclipse.jetty.util.thread.Locker.Lock; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; @@ -355,10 +354,9 @@ public class SessionHandler extends ScopedHandler } /** - * Called by the {@link Request} when it finally finishes. + * Called when a request is finally leaving a session. * * @param session the session object - * @see #access(HttpSession, boolean) */ public void complete(HttpSession session) { @@ -378,6 +376,28 @@ public class SessionHandler extends ScopedHandler LOG.warn(e); } } + + /** + * Called when a response is about to be committed. + * We might take this opportunity to persist the session + * so that any subsequent requests to other servers + * will see the modifications. + */ + public void commit(HttpSession session) + { + if (session == null) + return; + + Session s = ((SessionIf)session).getSession(); + try + { + _sessionCache.commit(s); + } + catch (Exception e) + { + LOG.warn(e); + } + } /* * @see org.eclipse.thread.AbstractLifeCycle#doStart() @@ -1264,7 +1284,7 @@ public class SessionHandler extends ScopedHandler //1. valid //2. expired //3. idle - try (Lock lock = session.lock()) + try (AutoLock lock = session.lock()) { if (session.getRequests() > 0) return; //session can't expire or be idle if there is a request in it diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java index 6a0e4369f5a..f4a55a05179 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java @@ -56,11 +56,13 @@ import org.junit.jupiter.api.condition.DisabledOnOs; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -166,6 +168,66 @@ public class GracefulStopTest client.close(); } + + /** + * Test completed writes during shutdown do not close output + * @throws Exception on test failure + */ + @Test + public void testWriteDuringShutdown() throws Exception + { + Server server = new Server(); + server.setStopTimeout(1000); + + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ABHandler handler = new ABHandler(); + StatisticsHandler stats = new StatisticsHandler(); + server.setHandler(stats); + stats.setHandler(handler); + + server.start(); + + Thread stopper = new Thread(() -> + { + try + { + handler.latchA.await(); + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(); + } + }); + stopper.start(); + + final int port = connector.getLocalPort(); + try(Socket client = new Socket("127.0.0.1", port)) + { + client.getOutputStream().write(( + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + port + "\r\n" + + "\r\n" + ).getBytes()); + client.getOutputStream().flush(); + + while (!connector.isShutdown()) + Thread.sleep(10); + + handler.latchB.countDown(); + + String response = IO.toString(client.getInputStream()); + assertThat(response, startsWith("HTTP/1.1 200 ")); + assertThat(response, containsString("Content-Length: 2")); + assertThat(response, containsString("Connection: close")); + assertThat(response, endsWith("ab")); + } + stopper.join(); + } + /** * Test of standard graceful timeout mechanism when a block request does * complete. Note that even though the request completes after 100ms, the @@ -736,6 +798,30 @@ public class GracefulStopTest } } + static class ABHandler extends AbstractHandler + { + final CountDownLatch latchA = new CountDownLatch(1); + final CountDownLatch latchB = new CountDownLatch(1); + + @Override + public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setContentLength(2); + response.getOutputStream().write("a".getBytes()); + try + { + latchA.countDown(); + latchB.await(); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + response.flushBuffer(); + response.getOutputStream().write("b".getBytes()); + } + } + static class TestHandler extends AbstractHandler { final CountDownLatch latch = new CountDownLatch(1); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java index b0f753b279e..a3ecb471d4d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java @@ -651,12 +651,6 @@ public class HttpOutputTest _next.write(BufferUtil.toBuffer(s), complete, callback); } - @Override - public boolean isOptimizedForDirectBuffers() - { - return _next.isOptimizedForDirectBuffers(); - } - @Override public Interceptor getNextInterceptor() { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index afb088c10c1..4322302b8f0 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -174,12 +174,6 @@ public class ResponseTest { _channelError = failure; } - - @Override - public boolean isOptimizedForDirectBuffers() - { - return false; - } }); } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java index ddcaa278423..5c688222192 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java @@ -43,11 +43,11 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl { private static final Logger LOG = Log.getLogger(BaseHolder.class); - protected final Source _source; - protected transient Class _class; - protected String _className; - protected boolean _extInstance; - protected ServletHandler _servletHandler; + private final Source _source; + private Class _class; + private String _className; + private T _instance; + private ServletHandler _servletHandler; protected BaseHolder(Source source) { @@ -101,7 +101,7 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl public void doStop() throws Exception { - if (!_extInstance) + if (_instance == null) _class = null; } @@ -163,12 +163,26 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl } } + protected synchronized void setInstance(T instance) + { + _instance = instance; + if (instance == null) + setHeldClass(null); + else + setHeldClass((Class)instance.getClass()); + } + + protected synchronized T getInstance() + { + return _instance; + } + /** * @return True if this holder was created for a specific instance. */ - public boolean isInstance() + public synchronized boolean isInstance() { - return _extInstance; + return _instance != null; } @Override diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java index 3881bb05ff3..9b0f0b6261a 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -91,11 +91,10 @@ public class FilterHolder extends Holder { super.doStart(); - if (!javax.servlet.Filter.class - .isAssignableFrom(_class)) + if (!javax.servlet.Filter.class.isAssignableFrom(getHeldClass())) { - String msg = _class + " is not a javax.servlet.Filter"; - super.stop(); + String msg = getHeldClass() + " is not a javax.servlet.Filter"; + doStop(); throw new IllegalStateException(msg); } } @@ -103,15 +102,18 @@ public class FilterHolder extends Holder @Override public void initialize() throws Exception { - if (!_initialized) + synchronized (this) { - super.initialize(); + if (_filter != null) + return; + super.initialize(); + _filter = getInstance(); if (_filter == null) { try { - ServletContext context = _servletHandler.getServletContext(); + ServletContext context = getServletHandler().getServletContext(); _filter = (context instanceof ServletContextHandler.Context) ? context.createFilter(getHeldClass()) : getHeldClass().getDeclaredConstructor().newInstance(); @@ -126,37 +128,30 @@ public class FilterHolder extends Holder throw ex; } } - _config = new Config(); if (LOG.isDebugEnabled()) LOG.debug("Filter.init {}", _filter); _filter.init(_config); } - - _initialized = true; } @Override public void doStop() throws Exception { + super.doStop(); + _config = null; if (_filter != null) { try { destroyInstance(_filter); } - catch (Exception e) + finally { - LOG.warn(e); + _filter = null; } } - if (!_extInstance) - _filter = null; - - _config = null; - _initialized = false; - super.doStop(); } @Override @@ -172,11 +167,7 @@ public class FilterHolder extends Holder public synchronized void setFilter(Filter filter) { - _filter = filter; - _extInstance = true; - setHeldClass(filter.getClass()); - if (getName() == null) - setName(filter.getClass().getName()); + setInstance(filter); } public Filter getFilter() @@ -187,19 +178,19 @@ public class FilterHolder extends Holder @Override public void dump(Appendable out, String indent) throws IOException { - if (_initParams.isEmpty()) + if (getInitParameters().isEmpty()) Dumpable.dumpObjects(out, indent, this, _filter == null ? getHeldClass() : _filter); else Dumpable.dumpObjects(out, indent, this, _filter == null ? getHeldClass() : _filter, - new DumpableCollection("initParams", _initParams.entrySet())); + new DumpableCollection("initParams", getInitParameters().entrySet())); } @Override public String toString() { - return String.format("%s@%x==%s,inst=%b,async=%b", _name, hashCode(), _className, _filter != null, isAsyncSupported()); + return String.format("%s@%x==%s,inst=%b,async=%b", getName(), hashCode(), getClassName(), _filter != null, isAsyncSupported()); } public FilterRegistration.Dynamic getRegistration() @@ -220,9 +211,9 @@ public class FilterHolder extends Holder mapping.setServletNames(servletNames); mapping.setDispatcherTypes(dispatcherTypes); if (isMatchAfter) - _servletHandler.addFilterMapping(mapping); + getServletHandler().addFilterMapping(mapping); else - _servletHandler.prependFilterMapping(mapping); + getServletHandler().prependFilterMapping(mapping); } @Override @@ -234,15 +225,15 @@ public class FilterHolder extends Holder mapping.setPathSpecs(urlPatterns); mapping.setDispatcherTypes(dispatcherTypes); if (isMatchAfter) - _servletHandler.addFilterMapping(mapping); + getServletHandler().addFilterMapping(mapping); else - _servletHandler.prependFilterMapping(mapping); + getServletHandler().prependFilterMapping(mapping); } @Override public Collection getServletNameMappings() { - FilterMapping[] mappings = _servletHandler.getFilterMappings(); + FilterMapping[] mappings = getServletHandler().getFilterMappings(); List names = new ArrayList(); for (FilterMapping mapping : mappings) { @@ -258,7 +249,7 @@ public class FilterHolder extends Holder @Override public Collection getUrlPatternMappings() { - FilterMapping[] mappings = _servletHandler.getFilterMappings(); + FilterMapping[] mappings = getServletHandler().getFilterMappings(); List patterns = new ArrayList(); for (FilterMapping mapping : mappings) { @@ -277,7 +268,7 @@ public class FilterHolder extends Holder @Override public String getFilterName() { - return _name; + return getName(); } } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java index 50e9e0c3dfe..adcb14bbdae 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java @@ -45,16 +45,15 @@ public abstract class Holder extends BaseHolder { private static final Logger LOG = Log.getLogger(Holder.class); - protected final Map _initParams = new HashMap(3); - protected String _displayName; - protected boolean _asyncSupported; - protected String _name; - protected boolean _initialized = false; + private final Map _initParams = new HashMap(3); + private String _displayName; + private boolean _asyncSupported; + private String _name; protected Holder(Source source) { super(source); - switch (_source.getOrigin()) + switch (getSource().getOrigin()) { case JAVAX_API: case DESCRIPTOR: @@ -98,6 +97,14 @@ public abstract class Holder extends BaseHolder return _name; } + @Override + protected synchronized void setInstance(T instance) + { + super.setInstance(instance); + if (getName() == null) + setName(String.format("%s@%x", instance.getClass().getName(), instance.hashCode())); + } + public void destroyInstance(Object instance) throws Exception { @@ -175,7 +182,7 @@ public abstract class Holder extends BaseHolder @Override public String toString() { - return String.format("%s@%x==%s", _name, hashCode(), _className); + return String.format("%s@%x==%s", _name, hashCode(), getClassName()); } protected class HolderConfig @@ -183,7 +190,7 @@ public abstract class Holder extends BaseHolder public ServletContext getServletContext() { - return _servletHandler.getServletContext(); + return getServletHandler().getServletContext(); } public String getInitParameter(String param) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java index 245101dd63b..9313237ea4e 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java @@ -64,52 +64,66 @@ public class ListenerHolder extends BaseHolder */ public void setListener(EventListener listener) { - _listener = listener; - _extInstance = true; - setHeldClass(_listener.getClass()); + setInstance(listener); } @Override public void doStart() throws Exception { super.doStart(); - if (!java.util.EventListener.class.isAssignableFrom(_class)) + if (!java.util.EventListener.class.isAssignableFrom(getHeldClass())) { - String msg = _class + " is not a java.util.EventListener"; + String msg = getHeldClass() + " is not a java.util.EventListener"; super.stop(); throw new IllegalStateException(msg); } - + ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler(); - if (_listener == null) + if (contextHandler != null) { - //create an instance of the listener and decorate it - try + _listener = getInstance(); + if (_listener == null) { - ServletContext scontext = contextHandler.getServletContext(); - _listener = (scontext instanceof ServletContextHandler.Context) - ? scontext.createListener(getHeldClass()) - : getHeldClass().getDeclaredConstructor().newInstance(); - } - catch (ServletException ex) - { - Throwable cause = ex.getRootCause(); - if (cause instanceof InstantiationException) - throw (InstantiationException)cause; - if (cause instanceof IllegalAccessException) - throw (IllegalAccessException)cause; - throw ex; + //create an instance of the listener and decorate it + try + { + ServletContext scontext = contextHandler.getServletContext(); + _listener = (scontext instanceof ServletContextHandler.Context) + ? scontext.createListener(getHeldClass()) + : getHeldClass().getDeclaredConstructor().newInstance(); + } + catch (ServletException ex) + { + Throwable cause = ex.getRootCause(); + if (cause instanceof InstantiationException) + throw (InstantiationException)cause; + if (cause instanceof IllegalAccessException) + throw (IllegalAccessException)cause; + throw ex; + } } + contextHandler.addEventListener(_listener); } - contextHandler.addEventListener(_listener); } @Override public void doStop() throws Exception { super.doStop(); - if (!_extInstance) - _listener = null; + if (_listener != null) + { + try + { + ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler(); + if (contextHandler != null) + contextHandler.removeEventListener(_listener); + getServletHandler().destroyListener(_listener); + } + finally + { + _listener = null; + } + } } @Override diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 3ec2bd383f9..9f69fce9b3d 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -121,8 +121,6 @@ public class ServletContextHandler extends ContextHandler public interface ServletContainerInitializerCaller extends LifeCycle {} - ; - protected final DecoratedObjectFactory _objFactory; protected Class _defaultSecurityHandlerClass = org.eclipse.jetty.security.ConstraintSecurityHandler.class; protected SessionHandler _sessionHandler; @@ -731,6 +729,11 @@ public class ServletContextHandler extends ContextHandler _objFactory.destroy(filter); } + void destroyListener(EventListener listener) + { + _objFactory.destroy(listener); + } + public static class JspPropertyGroup implements JspPropertyGroupDescriptor { private List _urlPatterns = new ArrayList(); @@ -1274,6 +1277,11 @@ public class ServletContextHandler extends ContextHandler } } + public void destroyFilter(T f) + { + _objFactory.destroy(f); + } + @Override public T createServlet(Class c) throws ServletException { @@ -1289,6 +1297,11 @@ public class ServletContextHandler extends ContextHandler } } + public void destroyServlet(T s) + { + _objFactory.destroy(s); + } + @Override public Set getDefaultSessionTrackingModes() { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 31e6f940d96..8bae9ebae07 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.EventListener; import java.util.HashMap; import java.util.List; import java.util.ListIterator; @@ -1706,6 +1707,12 @@ public class ServletHandler extends ScopedHandler _contextHandler.destroyFilter(filter); } + void destroyListener(EventListener listener) + { + if (_contextHandler != null) + _contextHandler.destroyListener(listener); + } + @SuppressWarnings("serial") public static class Default404Servlet extends HttpServlet { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index e3435fdc511..1466a0d9f48 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -32,6 +32,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; +import java.util.concurrent.TimeUnit; +import javax.servlet.GenericServlet; import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import javax.servlet.ServletConfig; @@ -43,6 +45,7 @@ import javax.servlet.ServletResponse; import javax.servlet.ServletSecurityElement; import javax.servlet.SingleThreadModel; import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.RunAsToken; @@ -70,7 +73,6 @@ import org.eclipse.jetty.util.log.Logger; @ManagedObject("Servlet Holder") public class ServletHolder extends Holder implements UserIdentity.Scope, Comparable { - private static final Logger LOG = Log.getLogger(ServletHolder.class); private int _initOrder = -1; private boolean _initOnStartup = false; @@ -82,11 +84,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope private ServletRegistration.Dynamic _registration; private JspContainer _jspContainer; - private Servlet _servlet; - private long _unavailable; + private volatile Servlet _servlet; private Config _config; private boolean _enabled = true; - private UnavailableException _unavailableEx; public static final String APACHE_SENTINEL_CLASS = "org.apache.tomcat.InstanceManager"; public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix"; @@ -167,7 +167,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope */ public UnavailableException getUnavailableException() { - return _unavailableEx; + Servlet servlet = _servlet; + if (servlet instanceof UnavailableServlet) + return ((UnavailableServlet)servlet).getUnavailableException(); + return null; } public synchronized void setServlet(Servlet servlet) @@ -175,11 +178,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope if (servlet == null || servlet instanceof SingleThreadModel) throw new IllegalArgumentException(SingleThreadModel.class.getName() + " has been deprecated since Servlet API 2.4"); - _extInstance = true; - _servlet = servlet; - setHeldClass(servlet.getClass()); - if (getName() == null) - setName(servlet.getClass().getName() + "-" + super.hashCode()); + setInstance(servlet); } @ManagedAttribute(value = "initialization order", readonly = true) @@ -218,20 +217,20 @@ public class ServletHolder extends Holder implements UserIdentity.Scope if (sh._initOrder > _initOrder) return -1; - // consider _className, need to position properly when one is configured but not the other + // consider getClassName(), need to position properly when one is configured but not the other int c; - if (_className == null && sh._className == null) + if (getClassName() == null && sh.getClassName() == null) c = 0; - else if (_className == null) + else if (getClassName() == null) c = -1; - else if (sh._className == null) + else if (sh.getClassName() == null) c = 1; else - c = _className.compareTo(sh._className); + c = getClassName().compareTo(sh.getClassName()); - // if _initOrder and _className are the same, consider the _name + // if _initOrder and getClassName() are the same, consider the getName() if (c == 0) - c = _name.compareTo(sh._name); + c = getName().compareTo(sh.getName()); return c; } @@ -245,7 +244,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope @Override public int hashCode() { - return _name == null ? System.identityHashCode(this) : _name.hashCode(); + return getName() == null ? System.identityHashCode(this) : getName().hashCode(); } /** @@ -309,7 +308,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope public void doStart() throws Exception { - _unavailable = 0; if (!_enabled) return; @@ -342,7 +340,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope //copy jsp init params that don't exist for this servlet for (Map.Entry entry : jsp.getInitParameters().entrySet()) { - if (!_initParams.containsKey(entry.getKey())) + if (!getInitParameters().containsKey(entry.getKey())) setInitParameter(entry.getKey(), entry.getValue()); } //jsp specific: set up the jsp-file on the JspServlet. If load-on-startup is >=0 and the jsp container supports @@ -365,7 +363,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope catch (UnavailableException ex) { makeUnavailable(ex); - if (_servletHandler.isStartWithUnavailable()) + if (getServletHandler().isStartWithUnavailable()) { LOG.ignore(ex); return; @@ -382,7 +380,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope catch (UnavailableException ex) { makeUnavailable(ex); - if (_servletHandler.isStartWithUnavailable()) + if (getServletHandler().isStartWithUnavailable()) { LOG.ignore(ex); return; @@ -394,16 +392,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope //check if we need to forcibly set load-on-startup checkInitOnStartup(); - _identityService = _servletHandler.getIdentityService(); - if (_identityService != null && _runAsRole != null) - _runAsToken = _identityService.newRunAsToken(_runAsRole); + if (_runAsRole == null) + { + _identityService = null; + _runAsToken = null; + } + else + { + _identityService = getServletHandler().getIdentityService(); + if (_identityService != null) + _runAsToken = _identityService.newRunAsToken(_runAsRole); + } _config = new Config(); synchronized (this) { - // TODO: remove support for deprecated SingleThreadModel?? - if (_class != null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class)) + if (getHeldClass() != null && javax.servlet.SingleThreadModel.class.isAssignableFrom(getHeldClass())) _servlet = new SingleThreadedWrapper(); } } @@ -412,57 +417,37 @@ public class ServletHolder extends Holder implements UserIdentity.Scope public void initialize() throws Exception { - if (!_initialized) + synchronized (this) { - super.initialize(); - if (_extInstance || _initOnStartup) + if (_servlet == null && (_initOnStartup || isInstance())) { - try - { - initServlet(); - } - catch (Exception e) - { - if (_servletHandler.isStartWithUnavailable()) - LOG.ignore(e); - else - throw e; - } + super.initialize(); + initServlet(); } } - _initialized = true; } @Override public void doStop() throws Exception { - Object oldRunAs = null; - if (_servlet != null) + synchronized (this) { - try + Servlet servlet = _servlet; + if (servlet != null) { - if (_identityService != null) - oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); - - destroyInstance(_servlet); - } - catch (Exception e) - { - LOG.warn(e); - } - finally - { - if (_identityService != null) - _identityService.unsetRunAs(oldRunAs); + _servlet = null; + try + { + destroyInstance(servlet); + } + catch (Exception e) + { + LOG.warn(e); + } } + _config = null; } - - if (!_extInstance) - _servlet = null; - - _config = null; - _initialized = false; } @Override @@ -482,41 +467,24 @@ public class ServletHolder extends Holder implements UserIdentity.Scope * @return The servlet * @throws ServletException if unable to init the servlet on first use */ - public synchronized Servlet getServlet() + public Servlet getServlet() throws ServletException { Servlet servlet = _servlet; - if (servlet != null && _unavailable == 0) - return servlet; - - synchronized (this) + if (servlet == null) { - // Handle previous unavailability - if (_unavailable != 0) + synchronized (this) { - if (_unavailable < 0 || _unavailable > 0 && System.currentTimeMillis() < _unavailable) - throw _unavailableEx; - _unavailable = 0; - _unavailableEx = null; - } - - servlet = _servlet; - if (servlet != null) - return servlet; - - if (isRunning()) - { - if (_class == null) - throw new UnavailableException("Servlet Not Initialized"); - if (_unavailable != 0 || !_initOnStartup) - initServlet(); servlet = _servlet; - if (servlet == null) - throw new UnavailableException("Could not instantiate " + _class); + if (servlet == null && isRunning()) + { + if (getHeldClass() != null) + initServlet(); + servlet = _servlet; + } } - - return servlet; } + return servlet; } /** @@ -526,13 +494,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope */ public Servlet getServletInstance() { - Servlet servlet = _servlet; - if (servlet != null) - return servlet; - synchronized (this) - { - return _servlet; - } + return _servlet; } /** @@ -543,9 +505,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope public void checkServletType() throws UnavailableException { - if (_class == null || !javax.servlet.Servlet.class.isAssignableFrom(_class)) + if (getHeldClass() == null || !javax.servlet.Servlet.class.isAssignableFrom(getHeldClass())) { - throw new UnavailableException("Servlet " + _class + " is not a javax.servlet.Servlet"); + throw new UnavailableException("Servlet " + getHeldClass() + " is not a javax.servlet.Servlet"); } } @@ -554,18 +516,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope */ public boolean isAvailable() { - if (isStarted() && _unavailable == 0) - return true; - try - { - getServlet(); - } - catch (Exception e) - { - LOG.ignore(e); - } - - return isStarted() && _unavailable == 0; + return (isStarted() && !(_servlet instanceof UnavailableServlet)); } /** @@ -576,30 +527,19 @@ public class ServletHolder extends Holder implements UserIdentity.Scope */ private void checkInitOnStartup() { - if (_class == null) + if (getHeldClass() == null) return; - if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup) + if ((getHeldClass().getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup) setInitOrder(Integer.MAX_VALUE); } - private void makeUnavailable(UnavailableException e) + private Servlet makeUnavailable(UnavailableException e) { - if (_unavailableEx == e && _unavailable != 0) - return; - - _servletHandler.getServletContext().log("unavailable", e); - - _unavailableEx = e; - _unavailable = -1; - if (e.isPermanent()) - _unavailable = -1; - else + synchronized (this) { - if (_unavailableEx.getUnavailableSeconds() > 0) - _unavailable = System.currentTimeMillis() + 1000 * _unavailableEx.getUnavailableSeconds(); - else - _unavailable = System.currentTimeMillis() + 5000; // TODO configure + _servlet = new UnavailableServlet(e, _servlet); + return _servlet; } } @@ -609,37 +549,39 @@ public class ServletHolder extends Holder implements UserIdentity.Scope makeUnavailable((UnavailableException)e); else { - ServletContext ctx = _servletHandler.getServletContext(); + ServletContext ctx = getServletHandler().getServletContext(); if (ctx == null) LOG.info("unavailable", e); else ctx.log("unavailable", e); - _unavailableEx = new UnavailableException(String.valueOf(e), -1) + UnavailableException unavailable = new UnavailableException(String.valueOf(e), -1) { { initCause(e); } }; - _unavailable = -1; + makeUnavailable(unavailable); } } private synchronized void initServlet() throws ServletException { - Object oldRunAs = null; try { + if (_servlet == null) + _servlet = getInstance(); if (_servlet == null) _servlet = newInstance(); if (_config == null) _config = new Config(); // Handle run as - if (_identityService != null) - { - oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); - } + if (_identityService != null && _runAsToken != null) + _servlet = new RunAsServlet(_servlet, _identityService, _runAsToken); + + if (!isAsyncSupported()) + _servlet = new NotAsyncServlet(_servlet); // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet if (isJspServlet()) @@ -659,30 +601,21 @@ public class ServletHolder extends Holder implements UserIdentity.Scope catch (UnavailableException e) { makeUnavailable(e); - _servlet = null; - _config = null; - throw e; + if (getServletHandler().isStartWithUnavailable()) + LOG.warn(e); + else + throw e; } catch (ServletException e) { makeUnavailable(e.getCause() == null ? e : e.getCause()); - _servlet = null; - _config = null; throw e; } catch (Exception e) { makeUnavailable(e); - _servlet = null; - _config = null; throw new ServletException(this.toString(), e); } - finally - { - // pop run-as role - if (_identityService != null) - _identityService.unsetRunAs(oldRunAs); - } } /** @@ -693,7 +626,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext()); /* Set the webapp's classpath for Jasper */ - ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath()); + ch.setAttribute("org.apache.catalina.jspgetHeldClass()path", ch.getClassPath()); /* Set up other classpath attribute */ if ("?".equals(getInitParameter("classpath"))) @@ -812,56 +745,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope UnavailableException, IOException { - if (_class == null) - throw new UnavailableException("Servlet Not Initialized"); - - Servlet servlet = getServlet(); - - // Service the request - Object oldRunAs = null; - boolean suspendable = baseRequest.isAsyncSupported(); try { - // Handle aliased path - if (_forcedPath != null) - adaptForcedPathToJspContainer(request); - - // Handle run as - if (_identityService != null) - oldRunAs = _identityService.setRunAs(baseRequest.getResolvedUserIdentity(), _runAsToken); - - if (baseRequest.isAsyncSupported() && !isAsyncSupported()) - { - try - { - baseRequest.setAsyncSupported(false, this.toString()); - servlet.service(request, response); - } - finally - { - baseRequest.setAsyncSupported(true, null); - } - } - else - servlet.service(request, response); + Servlet servlet = getServlet(); + if (servlet == null) + throw new UnavailableException("Servlet Not Initialized"); + servlet.service(request, response); } catch (UnavailableException e) { - makeUnavailable(e); - throw _unavailableEx; - } - finally - { - // Pop run-as role. - if (_identityService != null) - _identityService.unsetRunAs(oldRunAs); + makeUnavailable(e).service(request, response); } } protected boolean isJspServlet() { Servlet servlet = getServletInstance(); - Class c = servlet == null ? _class : servlet.getClass(); + Class c = servlet == null ? getHeldClass() : servlet.getClass(); while (c != null) { @@ -879,11 +779,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope return ("org.apache.jasper.servlet.JspServlet".equals(classname)); } - private void adaptForcedPathToJspContainer(ServletRequest request) - { - //no-op for apache jsp - } - private void detectJspContainer() { if (_jspContainer == null) @@ -1051,7 +946,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope Set clash = null; for (String pattern : urlPatterns) { - ServletMapping mapping = _servletHandler.getServletMapping(pattern); + ServletMapping mapping = getServletHandler().getServletMapping(pattern); if (mapping != null) { //if the servlet mapping was from a default descriptor, then allow it to be overridden @@ -1072,7 +967,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope ServletMapping mapping = new ServletMapping(Source.JAVAX_API); mapping.setServletName(ServletHolder.this.getName()); mapping.setPathSpecs(urlPatterns); - _servletHandler.addServletMapping(mapping); + getServletHandler().addServletMapping(mapping); return Collections.emptySet(); } @@ -1080,7 +975,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope @Override public Collection getMappings() { - ServletMapping[] mappings = _servletHandler.getServletMappings(); + ServletMapping[] mappings = getServletHandler().getServletMappings(); List patterns = new ArrayList(); if (mappings != null) { @@ -1134,7 +1029,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope @Override public Set setServletSecurity(ServletSecurityElement securityElement) { - return _servletHandler.setServletSecurity(this, securityElement); + return getServletHandler().setServletSecurity(this, securityElement); } } @@ -1259,9 +1154,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope try { ServletContext ctx = getServletHandler().getServletContext(); - if (ctx == null) - return getHeldClass().getDeclaredConstructor().newInstance(); - return ctx.createServlet(getHeldClass()); + if (ctx instanceof ServletContextHandler.Context) + return ctx.createServlet(getHeldClass()); + return getHeldClass().getDeclaredConstructor().newInstance(); + } catch (ServletException ex) { @@ -1281,18 +1177,221 @@ public class ServletHolder extends Holder implements UserIdentity.Scope @Override public void dump(Appendable out, String indent) throws IOException { - if (_initParams.isEmpty()) + if (getInitParameters().isEmpty()) Dumpable.dumpObjects(out, indent, this, _servlet == null ? getHeldClass() : _servlet); else Dumpable.dumpObjects(out, indent, this, _servlet == null ? getHeldClass() : _servlet, - new DumpableCollection("initParams", _initParams.entrySet())); + new DumpableCollection("initParams", getInitParameters().entrySet())); } @Override public String toString() { - return String.format("%s@%x==%s,jsp=%s,order=%d,inst=%b,async=%b", _name, hashCode(), _className, _forcedPath, _initOrder, _servlet != null, isAsyncSupported()); + return String.format("%s@%x==%s,jsp=%s,order=%d,inst=%b,async=%b", getName(), hashCode(), getClassName(), _forcedPath, _initOrder, _servlet != null, isAsyncSupported()); + } + + private class UnavailableServlet extends GenericServlet + { + final UnavailableException _unavailableException; + final Servlet _servlet; + final long _available; + + public UnavailableServlet(UnavailableException unavailableException, Servlet servlet) + { + _unavailableException = unavailableException; + + if (unavailableException.isPermanent()) + { + _servlet = null; + _available = -1; + if (servlet != null) + { + try + { + destroyInstance(servlet); + } + catch (Throwable th) + { + if (th != unavailableException) + unavailableException.addSuppressed(th); + } + } + } + else + { + _servlet = servlet; + _available = System.nanoTime() + TimeUnit.SECONDS.toNanos(unavailableException.getUnavailableSeconds()); + } + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + if (_available == -1) + ((HttpServletResponse)res).sendError(HttpServletResponse.SC_NOT_FOUND); + else if (System.nanoTime() < _available) + ((HttpServletResponse)res).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + else + { + synchronized (ServletHolder.this) + { + ServletHolder.this._servlet = this._servlet; + _servlet.service(req, res); + } + } + } + + @Override + public void destroy() + { + if (_servlet != null) + { + try + { + destroyInstance(_servlet); + } + catch (Throwable th) + { + LOG.warn(th); + } + } + } + + public UnavailableException getUnavailableException() + { + return _unavailableException; + } + } + + private static class WrapperServlet implements Servlet + { + final Servlet _servlet; + + public WrapperServlet(Servlet servlet) + { + _servlet = servlet; + } + + @Override + public void init(ServletConfig config) throws ServletException + { + _servlet.init(config); + } + + @Override + public ServletConfig getServletConfig() + { + return _servlet.getServletConfig(); + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + _servlet.service(req, res); + } + + @Override + public String getServletInfo() + { + return _servlet.getServletInfo(); + } + + @Override + public void destroy() + { + _servlet.destroy(); + } + + @Override + public String toString() + { + return String.format("%s:%s", this.getClass().getSimpleName(), _servlet.toString()); + } + } + + private static class RunAsServlet extends WrapperServlet + { + final IdentityService _identityService; + final RunAsToken _runAsToken; + + public RunAsServlet(Servlet servlet, IdentityService identityService, RunAsToken runAsToken) + { + super(servlet); + _identityService = identityService; + _runAsToken = runAsToken; + } + + @Override + public void init(ServletConfig config) throws ServletException + { + Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); + try + { + _servlet.init(config); + } + finally + { + _identityService.unsetRunAs(oldRunAs); + } + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); + try + { + _servlet.service(req, res); + } + finally + { + _identityService.unsetRunAs(oldRunAs); + } + } + + @Override + public void destroy() + { + Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); + try + { + _servlet.destroy(); + } + finally + { + _identityService.unsetRunAs(oldRunAs); + } + } + } + + private static class NotAsyncServlet extends WrapperServlet + { + public NotAsyncServlet(Servlet servlet) + { + super(servlet); + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + if (req.isAsyncSupported()) + { + try + { + ((Request)req).setAsyncSupported(false, this.toString()); + _servlet.service(req, res); + } + finally + { + ((Request)req).setAsyncSupported(true, null); + } + } + else + { + _servlet.service(req, res); + } + } } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java index a82e7a63452..4f7ea9af797 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; @@ -36,6 +37,7 @@ import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -58,6 +60,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class ErrorPageTest @@ -67,6 +70,8 @@ public class ErrorPageTest private StacklessLogging _stackless; private static CountDownLatch __asyncSendErrorCompleted; private ErrorPageErrorHandler _errorPageErrorHandler; + private static AtomicBoolean __destroyed; + private ServletContextHandler _context; @BeforeEach public void init() throws Exception @@ -75,25 +80,24 @@ public class ErrorPageTest _connector = new LocalConnector(_server); _server.addConnector(_connector); - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS); + _context = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS); - _server.setHandler(context); + _server.setHandler(_context); - context.setContextPath("/"); - - context.addFilter(SingleDispatchFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); - - context.addServlet(DefaultServlet.class, "/"); - context.addServlet(FailServlet.class, "/fail/*"); - context.addServlet(FailClosedServlet.class, "/fail-closed/*"); - context.addServlet(ErrorServlet.class, "/error/*"); - context.addServlet(AppServlet.class, "/app/*"); - context.addServlet(LongerAppServlet.class, "/longer.app/*"); - context.addServlet(SyncSendErrorServlet.class, "/sync/*"); - context.addServlet(AsyncSendErrorServlet.class, "/async/*"); - context.addServlet(NotEnoughServlet.class, "/notenough/*"); - context.addServlet(DeleteServlet.class, "/delete/*"); - context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*"); + _context.setContextPath("/"); + _context.addFilter(SingleDispatchFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); + _context.addServlet(DefaultServlet.class, "/"); + _context.addServlet(FailServlet.class, "/fail/*"); + _context.addServlet(FailClosedServlet.class, "/fail-closed/*"); + _context.addServlet(ErrorServlet.class, "/error/*"); + _context.addServlet(AppServlet.class, "/app/*"); + _context.addServlet(LongerAppServlet.class, "/longer.app/*"); + _context.addServlet(SyncSendErrorServlet.class, "/sync/*"); + _context.addServlet(AsyncSendErrorServlet.class, "/async/*"); + _context.addServlet(NotEnoughServlet.class, "/notenough/*"); + _context.addServlet(UnavailableServlet.class, "/unavailable/*"); + _context.addServlet(DeleteServlet.class, "/delete/*"); + _context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*"); HandlerWrapper noopHandler = new HandlerWrapper() { @@ -106,10 +110,10 @@ public class ErrorPageTest super.handle(target, baseRequest, request, response); } }; - context.insertHandler(noopHandler); + _context.insertHandler(noopHandler); _errorPageErrorHandler = new ErrorPageErrorHandler(); - context.setErrorHandler(_errorPageErrorHandler); + _context.setErrorHandler(_errorPageErrorHandler); _errorPageErrorHandler.addErrorPage(595, "/error/595"); _errorPageErrorHandler.addErrorPage(597, "/sync"); _errorPageErrorHandler.addErrorPage(599, "/error/599"); @@ -408,6 +412,46 @@ public class ErrorPageTest assertThat(response, Matchers.endsWith("SomeBytes")); } + @Test + public void testPermanentlyUnavailable() throws Exception + { + try (StacklessLogging ignore = new StacklessLogging(_context.getLogger())) + { + try (StacklessLogging ignore2 = new StacklessLogging(HttpChannel.class)) + { + __destroyed = new AtomicBoolean(false); + String response = _connector.getResponse("GET /unavailable/info HTTP/1.0\r\n\r\n"); + assertThat(response, Matchers.containsString("HTTP/1.1 404 ")); + assertTrue(__destroyed.get()); + } + } + } + + @Test + public void testUnavailable() throws Exception + { + try (StacklessLogging ignore = new StacklessLogging(_context.getLogger())) + { + try (StacklessLogging ignore2 = new StacklessLogging(HttpChannel.class)) + { + __destroyed = new AtomicBoolean(false); + String response = _connector.getResponse("GET /unavailable/info?for=1 HTTP/1.0\r\n\r\n"); + assertThat(response, Matchers.containsString("HTTP/1.1 503 ")); + assertFalse(__destroyed.get()); + + response = _connector.getResponse("GET /unavailable/info?ok=true HTTP/1.0\r\n\r\n"); + assertThat(response, Matchers.containsString("HTTP/1.1 503 ")); + assertFalse(__destroyed.get()); + + Thread.sleep(1500); + + response = _connector.getResponse("GET /unavailable/info?ok=true HTTP/1.0\r\n\r\n"); + assertThat(response, Matchers.containsString("HTTP/1.1 200 ")); + assertFalse(__destroyed.get()); + } + } + } + public static class AppServlet extends HttpServlet implements Servlet { @Override @@ -618,6 +662,34 @@ public class ErrorPageTest } } + public static class UnavailableServlet extends HttpServlet implements Servlet + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String ok = request.getParameter("ok"); + if (Boolean.parseBoolean(ok)) + { + response.setStatus(200); + response.flushBuffer(); + return; + } + + String f = request.getParameter("for"); + if (f == null) + throw new UnavailableException("testing permanent"); + + throw new UnavailableException("testing periodic", Integer.parseInt(f)); + } + + @Override + public void destroy() + { + if (__destroyed != null) + __destroyed.set(true); + } + } + public static class SingleDispatchFilter implements Filter { ConcurrentMap dispatches = new ConcurrentHashMap<>(); @@ -665,7 +737,6 @@ public class ErrorPageTest @Override public void destroy() { - } } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java index 6441cffb7b1..d1f6bc6e05b 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java @@ -285,6 +285,34 @@ public class GzipHandlerTest assertEquals(__content, testOut.toString("UTF8")); } + @Test + public void testGzipHandlerWithMultipleAcceptEncodingHeaders() throws Exception + { + // generated and parsed test + HttpTester.Request request = HttpTester.newRequest(); + HttpTester.Response response; + + request.setMethod("GET"); + request.setURI("/ctx/content?vary=Accept-Encoding,Other"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host", "tester"); + request.setHeader("accept-encoding", "deflate"); + request.setHeader("accept-encoding", "gzip"); + + response = HttpTester.parseResponse(_connector.getResponse(request.generate())); + + assertThat(response.getStatus(), is(200)); + assertThat(response.get("Content-Encoding"), Matchers.equalToIgnoringCase("gzip")); + assertThat(response.get("ETag"), is(__contentETagGzip)); + assertThat(response.getCSV("Vary", false), Matchers.contains("Accept-Encoding", "Other")); + + InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes())); + ByteArrayOutputStream testOut = new ByteArrayOutputStream(); + IO.copy(testIn, testOut); + + assertEquals(__content, testOut.toString("UTF8")); + } + @Test public void testGzipNotMicro() throws Exception { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java deleted file mode 100644 index d29eb5baa67..00000000000 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2019 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. -// ======================================================================== -// - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.eclipse.jetty.servlet; - -import java.util.Collections; -import java.util.Set; -import javax.servlet.ServletRegistration; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * - */ -public class HolderTest -{ - - @Test - public void testInitParams() throws Exception - { - ServletHolder holder = new ServletHolder(Source.JAVAX_API); - ServletRegistration reg = holder.getRegistration(); - - assertThrows(IllegalArgumentException.class,() -> reg.setInitParameter(null, "foo")); - - assertThrows(IllegalArgumentException.class,() -> reg.setInitParameter("foo", null)); - - reg.setInitParameter("foo", "bar"); - assertFalse(reg.setInitParameter("foo", "foo")); - - Set clash = reg.setInitParameters(Collections.singletonMap("foo", "bax")); - assertTrue(clash != null && clash.size() == 1, "should be one clash"); - - assertThrows(IllegalArgumentException.class,() -> reg.setInitParameters(Collections.singletonMap((String)null, "bax"))); - assertThrows(IllegalArgumentException.class,() -> reg.setInitParameters(Collections.singletonMap("foo", (String)null))); - - Set clash2 = reg.setInitParameters(Collections.singletonMap("FOO", "bax")); - assertTrue(clash2.isEmpty(), "should be no clash"); - assertEquals(2, reg.getInitParameters().size(), "setInitParameters should not replace existing non-clashing init parameters"); - } -} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java index 9a938ad9b30..080e045eca2 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java @@ -667,7 +667,7 @@ public class ServletContextHandlerTest } @Test - public void testAddServletFromFilter() throws Exception + public void testAddServletByClassFromFilter() throws Exception { //A servlet cannot be added from a Filter Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT"); @@ -718,6 +718,110 @@ public class ServletContextHandlerTest } } + @Test + public void testAddServletByInstanceFromFilter() throws Exception + { + //A servlet cannot be added from a Filter + Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT"); + + try (StacklessLogging stackless = new StacklessLogging(logger)) + { + ServletContextHandler context = new ServletContextHandler(); + context.setLogger(logger); + FilterHolder holder = new FilterHolder(new Filter() + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", new HelloServlet()); + rego.addMapping("/hello/*"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + } + + @Override + public void destroy() + { + } + + }); + context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.getServletHandler().setStartWithUnavailable(false); + context.setContextPath("/"); + _server.setHandler(context); + _server.start(); + fail("Servlet can only be added from SCI or SCL"); + } + catch (Exception e) + { + if (!(e instanceof IllegalStateException)) + { + if (e instanceof ServletException) + { + assertTrue(e.getCause() instanceof IllegalStateException); + } + else + fail(e); + } + } + } + + @Test + public void testAddServletByClassNameFromFilter() throws Exception + { + //A servlet cannot be added from a Filter + Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT"); + + try (StacklessLogging stackless = new StacklessLogging(logger)) + { + ServletContextHandler context = new ServletContextHandler(); + context.setLogger(logger); + FilterHolder holder = new FilterHolder(new Filter() + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", HelloServlet.class.getName()); + rego.addMapping("/hello/*"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + } + + @Override + public void destroy() + { + } + + }); + context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.getServletHandler().setStartWithUnavailable(false); + context.setContextPath("/"); + _server.setHandler(context); + _server.start(); + fail("Servlet can only be added from SCI or SCL"); + } + catch (Exception e) + { + if (!(e instanceof IllegalStateException)) + { + if (e instanceof ServletException) + { + assertTrue(e.getCause() instanceof IllegalStateException); + } + else + fail(e); + } + } + } + @Test public void testAddServletFromSCL() throws Exception { @@ -770,6 +874,7 @@ public class ServletContextHandlerTest rego.addMapping("/hello/*"); } } + root.addBean(new MySCIStarter(root.getServletContext(), new ServletAddingSCI()), true); _server.start(); @@ -797,7 +902,6 @@ public class ServletContextHandlerTest request.append("\n"); String response = _connector.getResponse(request.toString()); - int result; assertThat("Response", response, containsString("Test")); context.addServlet(HelloServlet.class, "/hello"); @@ -950,7 +1054,6 @@ public class ServletContextHandlerTest request.append("\n"); String response = _connector.getResponse(request.toString()); - int result; assertThat("Response", response, containsString("Test")); assertEquals(extra, context.getSessionHandler().getHandler()); @@ -995,7 +1098,6 @@ public class ServletContextHandlerTest request.append("\n"); String response = _connector.getResponse(request.toString()); - int result; assertThat("Response", response, containsString("Test")); context.stop(); @@ -1016,7 +1118,7 @@ public class ServletContextHandlerTest @Test public void testSetSecurityHandler() throws Exception { - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS|ServletContextHandler.SECURITY|ServletContextHandler.GZIP); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY | ServletContextHandler.GZIP); assertNotNull(context.getSessionHandler()); SessionHandler sessionHandler = context.getSessionHandler(); assertNotNull(context.getSecurityHandler()); @@ -1094,7 +1196,6 @@ public class ServletContextHandlerTest request.append("\n"); String response = _connector.getResponse(request.toString()); - int result; assertThat("Response", response, containsString("Test")); context.stop(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java index f9553980962..59474c2d978 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java @@ -18,11 +18,11 @@ package org.eclipse.jetty.servlet; -import javax.servlet.ServletException; +import java.util.Collections; +import java.util.Set; +import javax.servlet.ServletRegistration; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.MultiException; @@ -32,18 +32,41 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import java.io.IOException; - public class ServletHolderTest { - public static class FakeServlet extends HttpServlet { } - + + @Test + public void testInitParams() throws Exception + { + ServletHolder holder = new ServletHolder(Source.JAVAX_API); + ServletRegistration reg = holder.getRegistration(); + + assertThrows(IllegalArgumentException.class, () -> reg.setInitParameter(null, "foo")); + + assertThrows(IllegalArgumentException.class, () -> reg.setInitParameter("foo", null)); + + reg.setInitParameter("foo", "bar"); + assertFalse(reg.setInitParameter("foo", "foo")); + + Set clash = reg.setInitParameters(Collections.singletonMap("foo", "bax")); + assertTrue(clash != null && clash.size() == 1, "should be one clash"); + + assertThrows(IllegalArgumentException.class, () -> reg.setInitParameters(Collections.singletonMap((String)null, "bax"))); + assertThrows(IllegalArgumentException.class, () -> reg.setInitParameters(Collections.singletonMap("foo", (String)null))); + + Set clash2 = reg.setInitParameters(Collections.singletonMap("FOO", "bax")); + assertTrue(clash2.isEmpty(), "should be no clash"); + assertEquals(2, reg.getInitParameters().size(), "setInitParameters should not replace existing non-clashing init parameters"); + } @Test public void testTransitiveCompareTo() throws Exception @@ -78,26 +101,16 @@ public class ServletHolderTest ServletHolder h = new ServletHolder(); h.setName("test"); - assertEquals(null, h.getClassNameForJsp(null)); - - assertEquals(null, h.getClassNameForJsp("")); - - assertEquals(null, h.getClassNameForJsp("/blah/")); - - assertEquals(null, h.getClassNameForJsp("//blah///")); - - assertEquals(null, h.getClassNameForJsp("/a/b/c/blah/")); - + assertNull(h.getClassNameForJsp(null)); + assertNull(h.getClassNameForJsp("")); + assertNull(h.getClassNameForJsp("/blah/")); + assertNull(h.getClassNameForJsp("//blah///")); + assertNull(h.getClassNameForJsp("/a/b/c/blah/")); assertEquals("org.apache.jsp.a.b.c.blah", h.getClassNameForJsp("/a/b/c/blah")); - assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("/blah.jsp")); - assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("//blah.jsp")); - assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("blah.jsp")); - assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("/a/b/c/blah.jsp")); - assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("a/b/c/blah.jsp")); } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java new file mode 100644 index 00000000000..01100eb8b03 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java @@ -0,0 +1,229 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.servlet; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.EventListener; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Decorator; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ServletLifeCycleTest +{ + static final Queue events = new ConcurrentLinkedQueue<>(); + + @Test + public void testLifeCycle() throws Exception + { + Server server = new Server(0); + LocalConnector connector = new LocalConnector(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(server, "/"); + + context.getObjectFactory().addDecorator(new TestDecorator()); + + ServletHandler sh = context.getServletHandler(); + sh.addListener(new ListenerHolder(TestListener.class)); + context.addEventListener(context.getServletContext().createListener(TestListener2.class)); + + sh.addFilterWithMapping(TestFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + sh.addFilterWithMapping(new FilterHolder(context.getServletContext().createFilter(TestFilter2.class)), "/*", EnumSet.of(DispatcherType.REQUEST)); + + sh.addServletWithMapping(TestServlet.class, "/1/*").setInitOrder(1); + sh.addServletWithMapping(TestServlet2.class, "/2/*").setInitOrder(-1); + sh.addServletWithMapping(new ServletHolder(context.getServletContext().createServlet(TestServlet3.class)) + {{ + setInitOrder(1); + }}, "/3/*"); + + assertThat(events, Matchers.contains( + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2", + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3")); + + events.clear(); + server.start(); + assertThat(events, Matchers.contains( + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener", + "ContextInitialized class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2", + "ContextInitialized class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener", + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", + "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", + "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3")); + + events.clear(); + connector.getResponse("GET /2/info HTTP/1.0\r\n\r\n"); + + assertThat(events, Matchers.contains( + "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", + "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", + "doFilter class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "doFilter class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", + "service class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2")); + + events.clear(); + server.stop(); + + assertThat(events, Matchers.contains( + "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener", + "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", + "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener")); + + // Listener added before start is not destroyed + EventListener[] listeners = context.getEventListeners(); + assertThat(listeners.length, is(1)); + assertThat(listeners[0].getClass(), is(TestListener2.class)); + } + + public static class TestDecorator implements Decorator + { + @Override + public T decorate(T o) + { + events.add("Decorate " + o.getClass()); + return o; + } + + @Override + public void destroy(Object o) + { + events.add("Destroy " + o.getClass()); + } + } + + public static class TestListener implements ServletContextListener + { + @Override + public void contextInitialized(ServletContextEvent sce) + { + events.add("ContextInitialized " + this.getClass()); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + events.add("contextDestroyed " + this.getClass()); + } + } + + public static class TestListener2 extends TestListener + { + } + + public static class TestFilter implements Filter + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + events.add("init " + this.getClass()); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + events.add("doFilter " + this.getClass()); + chain.doFilter(request, response); + } + + @Override + public void destroy() + { + events.add("destroy " + this.getClass()); + } + } + + public static class TestFilter2 extends TestFilter + { + } + + public static class TestServlet implements Servlet + { + @Override + public void init(ServletConfig config) throws ServletException + { + events.add("init " + this.getClass()); + } + + @Override + public ServletConfig getServletConfig() + { + return null; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + events.add("service " + this.getClass()); + } + + @Override + public String getServletInfo() + { + return null; + } + + @Override + public void destroy() + { + events.add("destroy " + this.getClass()); + } + } + + public static class TestServlet2 extends TestServlet + { + } + + public static class TestServlet3 extends TestServlet + { + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java index 9e82816f2be..1fc1d9523a4 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.util; import java.io.IOException; -import org.eclipse.jetty.util.thread.Locker; +import org.eclipse.jetty.util.thread.AutoLock; /** * This specialized callback implements a pattern that allows @@ -125,7 +125,7 @@ public abstract class IteratingCallback implements Callback SUCCEEDED } - private Locker _locker = new Locker(); + private final AutoLock _lock = new AutoLock(); private State _state; private boolean _iterate; @@ -188,35 +188,31 @@ public abstract class IteratingCallback implements Callback { boolean process = false; - loop: - while (true) + try (AutoLock lock = _lock.lock()) { - try (Locker.Lock lock = _locker.lock()) + switch (_state) { - switch (_state) - { - case PENDING: - case CALLED: - // process will be called when callback is handled - break loop; + case PENDING: + case CALLED: + // process will be called when callback is handled + break; - case IDLE: - _state = State.PROCESSING; - process = true; - break loop; + case IDLE: + _state = State.PROCESSING; + process = true; + break; - case PROCESSING: - _iterate = true; - break loop; + case PROCESSING: + _iterate = true; + break; - case FAILED: - case SUCCEEDED: - break loop; + case FAILED: + case SUCCEEDED: + break; - case CLOSED: - default: - throw new IllegalStateException(toString()); - } + case CLOSED: + default: + throw new IllegalStateException(toString()); } } if (process) @@ -243,11 +239,11 @@ public abstract class IteratingCallback implements Callback catch (Throwable x) { failed(x); - break processing; + break; } // acted on the action we have just received - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -295,18 +291,11 @@ public abstract class IteratingCallback implements Callback case CALLED: { - switch (action) - { - case SCHEDULED: - { - // we lost the race, so we have to keep processing - _state = State.PROCESSING; - continue processing; - } - - default: - throw new IllegalStateException(String.format("%s[action=%s]", this, action)); - } + if (action != Action.SCHEDULED) + throw new IllegalStateException(String.format("%s[action=%s]", this, action)); + // we lost the race, so we have to keep processing + _state = State.PROCESSING; + continue processing; } case SUCCEEDED: @@ -335,7 +324,7 @@ public abstract class IteratingCallback implements Callback public void succeeded() { boolean process = false; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -375,7 +364,7 @@ public abstract class IteratingCallback implements Callback public void failed(Throwable x) { boolean failure = false; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -405,7 +394,7 @@ public abstract class IteratingCallback implements Callback public void close() { String failure = null; - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -434,7 +423,7 @@ public abstract class IteratingCallback implements Callback */ boolean isIdle() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.IDLE; } @@ -442,7 +431,7 @@ public abstract class IteratingCallback implements Callback public boolean isClosed() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.CLOSED; } @@ -453,7 +442,7 @@ public abstract class IteratingCallback implements Callback */ public boolean isFailed() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.FAILED; } @@ -464,7 +453,7 @@ public abstract class IteratingCallback implements Callback */ public boolean isSucceeded() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _state == State.SUCCEEDED; } @@ -481,7 +470,7 @@ public abstract class IteratingCallback implements Callback */ public boolean reset() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java index a8626a2bc27..75b9aa9f74f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java @@ -34,27 +34,39 @@ public abstract class AbstractLifeCycle implements LifeCycle { private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class); - public static final String STOPPED = "STOPPED"; - public static final String FAILED = "FAILED"; - public static final String STARTING = "STARTING"; - public static final String STARTED = "STARTED"; - public static final String STOPPING = "STOPPING"; - public static final String RUNNING = "RUNNING"; + enum State + { + STOPPED, + STARTING, + STARTED, + STOPPING, + FAILED + } + + public static final String STOPPED = State.STOPPED.toString(); + public static final String FAILED = State.FAILED.toString(); + public static final String STARTING = State.STARTING.toString(); + public static final String STARTED = State.STARTED.toString(); + public static final String STOPPING = State.STOPPING.toString(); private final CopyOnWriteArrayList _listeners = new CopyOnWriteArrayList(); private final Object _lock = new Object(); - private static final int STATE_FAILED = -1; - private static final int STATE_STOPPED = 0; - private static final int STATE_STARTING = 1; - private static final int STATE_STARTED = 2; - private static final int STATE_STOPPING = 3; - private volatile int _state = STATE_STOPPED; + private volatile State _state = State.STOPPED; private long _stopTimeout = 30000; + /** + * Method to override to start the lifecycle + * @throws StopException If thrown, the lifecycle will immediately be stopped. + * @throws Exception If there was a problem starting. Will cause a transition to FAILED state + */ protected void doStart() throws Exception { } + /** + * Method to override to stop the lifecycle + * @throws Exception If there was a problem stopping. Will cause a transition to FAILED state + */ protected void doStop() throws Exception { } @@ -66,11 +78,31 @@ public abstract class AbstractLifeCycle implements LifeCycle { try { - if (_state == STATE_STARTED || _state == STATE_STARTING) - return; - setStarting(); - doStart(); - setStarted(); + switch (_state) + { + case STARTED: + return; + + case STARTING: + case STOPPING: + throw new IllegalStateException(getState()); + + default: + try + { + setStarting(); + doStart(); + setStarted(); + } + catch (StopException e) + { + if (LOG.isDebugEnabled()) + LOG.debug(e); + setStopping(); + doStop(); + setStopped(); + } + } } catch (Throwable e) { @@ -87,11 +119,20 @@ public abstract class AbstractLifeCycle implements LifeCycle { try { - if (_state == STATE_STOPPING || _state == STATE_STOPPED) - return; - setStopping(); - doStop(); - setStopped(); + switch (_state) + { + case STOPPED: + return; + + case STARTING: + case STOPPING: + throw new IllegalStateException(getState()); + + default: + setStopping(); + doStop(); + setStopped(); + } } catch (Throwable e) { @@ -104,39 +145,45 @@ public abstract class AbstractLifeCycle implements LifeCycle @Override public boolean isRunning() { - final int state = _state; - - return state == STATE_STARTED || state == STATE_STARTING; + final State state = _state; + switch (state) + { + case STARTED: + case STARTING: + return true; + default: + return false; + } } @Override public boolean isStarted() { - return _state == STATE_STARTED; + return _state == State.STARTED; } @Override public boolean isStarting() { - return _state == STATE_STARTING; + return _state == State.STARTING; } @Override public boolean isStopping() { - return _state == STATE_STOPPING; + return _state == State.STOPPING; } @Override public boolean isStopped() { - return _state == STATE_STOPPED; + return _state == State.STOPPED; } @Override public boolean isFailed() { - return _state == STATE_FAILED; + return _state == State.FAILED; } @Override @@ -154,85 +201,73 @@ public abstract class AbstractLifeCycle implements LifeCycle @ManagedAttribute(value = "Lifecycle State for this instance", readonly = true) public String getState() { - switch (_state) - { - case STATE_FAILED: - return FAILED; - case STATE_STARTING: - return STARTING; - case STATE_STARTED: - return STARTED; - case STATE_STOPPING: - return STOPPING; - case STATE_STOPPED: - return STOPPED; - default: - return null; - } + return _state.toString(); } public static String getState(LifeCycle lc) { + if (lc instanceof AbstractLifeCycle) + return ((AbstractLifeCycle)lc)._state.toString(); if (lc.isStarting()) - return STARTING; + return State.STARTING.toString(); if (lc.isStarted()) - return STARTED; + return State.STARTED.toString(); if (lc.isStopping()) - return STOPPING; + return State.STOPPING.toString(); if (lc.isStopped()) - return STOPPED; - return FAILED; + return State.STOPPED.toString(); + return State.FAILED.toString(); } private void setStarted() { - _state = STATE_STARTED; - if (LOG.isDebugEnabled()) - LOG.debug(STARTED + " @{}ms {}", Uptime.getUptime(), this); - for (Listener listener : _listeners) + if (_state == State.STARTING) { - listener.lifeCycleStarted(this); + _state = State.STARTED; + if (LOG.isDebugEnabled()) + LOG.debug("STARTED @{}ms {}", Uptime.getUptime(), this); + for (Listener listener : _listeners) + listener.lifeCycleStarted(this); } } private void setStarting() { if (LOG.isDebugEnabled()) - LOG.debug("starting {}", this); - _state = STATE_STARTING; + LOG.debug("STARTING {}", this); + _state = State.STARTING; for (Listener listener : _listeners) - { listener.lifeCycleStarting(this); - } } private void setStopping() { if (LOG.isDebugEnabled()) - LOG.debug("stopping {}", this); - _state = STATE_STOPPING; + LOG.debug("STOPPING {}", this); + _state = State.STOPPING; for (Listener listener : _listeners) - { listener.lifeCycleStopping(this); - } } private void setStopped() { - _state = STATE_STOPPED; - if (LOG.isDebugEnabled()) - LOG.debug("{} {}", STOPPED, this); - for (Listener listener : _listeners) + if (_state == State.STOPPING) { - listener.lifeCycleStopped(this); + _state = State.STOPPED; + if (LOG.isDebugEnabled()) + LOG.debug("STOPPED {}", this); + for (Listener listener : _listeners) + { + listener.lifeCycleStopped(this); + } } } private void setFailed(Throwable th) { - _state = STATE_FAILED; + _state = State.FAILED; if (LOG.isDebugEnabled()) - LOG.warn(FAILED + " " + this + ": " + th, th); + LOG.warn("FAILED " + this + ": " + th, th); for (Listener listener : _listeners) { listener.lifeCycleFailure(this, th); @@ -290,4 +325,10 @@ public abstract class AbstractLifeCycle implements LifeCycle } return String.format("%s@%x{%s}", name, hashCode(), getState()); } + + /** + * An exception, which if thrown by doStart will immediately stop the component + */ + public class StopException extends RuntimeException + {} } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java index 188e5d65dc3..689f2a51d6f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java @@ -100,6 +100,8 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, { for (Bean b : _beans) { + if (!isStarting()) + break; if (b._bean instanceof LifeCycle) { LifeCycle l = (LifeCycle)b._bean; @@ -127,8 +129,6 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, } } } - - super.doStart(); } catch (Throwable th) { @@ -193,6 +193,8 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, MultiException mex = new MultiException(); for (Bean b : reverse) { + if (!isStopping()) + break; if (b._managed == Managed.MANAGED && b._bean instanceof LifeCycle) { LifeCycle l = (LifeCycle)b._bean; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java index 2660ee326fb..7408e042e17 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java @@ -21,17 +21,39 @@ package org.eclipse.jetty.util.component; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; -/* A Lifecycle that can be gracefully shutdown. +/** + *

    Jetty components that wish to be part of a Graceful shutdown implement this interface so that + * the {@link Graceful#shutdown()} method will be called to initiate a shutdown. Shutdown operations + * can fall into the following categories:

    + *
      + *
    • Preventing new load from being accepted (eg connectors stop accepting connections)
    • + *
    • Preventing existing load expanding (eg stopping existing connections accepting new requests)
    • + *
    • Waiting for existing load to complete (eg waiting for active request count to reduce to 0)
    • + *
    • Performing cleanup operations that may take time (eg closing an SSL connection)
    • + *
    + *

    The {@link Future} returned by the the shutdown call will be completed to indicate the shutdown operation is completed. + * Some shutdown operations may be instantaneous and always return a completed future. + *

    + * Graceful shutdown is typically orchestrated by the doStop methods of Server or ContextHandler (for a full or partial + * shutdown respectively). + *

    */ public interface Graceful { - public Future shutdown(); + Future shutdown(); - public boolean isShutdown(); + boolean isShutdown(); - public static class Shutdown implements Graceful + /** + * A utility Graceful that uses a {@link FutureCallback} to indicate if shutdown is completed. + * By default the {@link FutureCallback} is returned as already completed, but the {@link #newShutdownCallback()} method + * can be overloaded to return a non-completed callback that will require a {@link Callback#succeeded()} or + * {@link Callback#failed(Throwable)} call to be completed. + */ + class Shutdown implements Graceful { private final AtomicReference _shutdown = new AtomicReference<>(); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java index 9ed6d39e286..6a46f76e2c0 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java @@ -42,6 +42,7 @@ public class Constraint implements Cloneable, Serializable public static final String __SPNEGO_AUTH = "SPNEGO"; public static final String __NEGOTIATE_AUTH = "NEGOTIATE"; + public static final String __OPENID_AUTH = "OPENID"; public static boolean validateMethod(String method) { @@ -54,7 +55,8 @@ public class Constraint implements Cloneable, Serializable method.equals(__CERT_AUTH) || method.equals(__CERT_AUTH2) || method.equals(__SPNEGO_AUTH) || - method.equals(__NEGOTIATE_AUTH)); + method.equals(__NEGOTIATE_AUTH) || + method.equals(__OPENID_AUTH)); } public static final int DC_UNSET = -1; 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/AutoLock.java similarity index 68% rename from jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java rename to jetty-util/src/main/java/org/eclipse/jetty/util/thread/AutoLock.java index b56180bd90e..9bcab7eff2a 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/AutoLock.java @@ -22,37 +22,27 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** - *

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

    - * + * Reentrant lock that can be used in a try-with-resources statement. *
    - * try (Locker.Lock lock = locker.lock())
    + * try (AutoLock lock = this.lock.lock())
      * {
    - *   // something
    + *     // Something
      * }
      * 
    */ -public class Locker +public class AutoLock implements AutoCloseable { private final ReentrantLock _lock = new ReentrantLock(); - private final Lock _unlock = new Lock(); /** *

    Acquires the lock.

    * - * @return the lock to unlock + * @return this AutoLock for unlocking */ - public Lock lock() + public AutoLock lock() { _lock.lock(); - return _unlock; - } - - /** - * @return whether this lock has been acquired - */ - public boolean isLocked() - { - return _lock.isLocked(); + return this; } /** @@ -63,15 +53,15 @@ public class Locker return _lock.newCondition(); } - /** - *

    The unlocker object that unlocks when it is closed.

    - */ - public class Lock implements AutoCloseable + // Package-private for testing only. + boolean isLocked() { - @Override - public void close() - { - _lock.unlock(); - } + return _lock.isLocked(); + } + + @Override + public void close() + { + _lock.unlock(); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index b518634488d..fb9c17c7447 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -176,7 +176,6 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP removeBean(_tryExecutor); _tryExecutor = TryExecutor.NO_TRY; - // Signal the Runner threads that we are stopping int threads = _counts.getAndSetHi(Integer.MIN_VALUE); @@ -483,7 +482,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP public void execute(Runnable job) { // Determine if we need to start a thread, use and idle thread or just queue this job - boolean startThread; + int startThread; while (true) { // Get the atomic counts @@ -501,10 +500,10 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP // Start a thread if we have insufficient idle threads to meet demand // and we are not at max threads. - startThread = (idle <= 0 && threads < _maxThreads); + startThread = (idle <= 0 && threads < _maxThreads) ? 1 : 0; // The job will be run by an idle thread when available - if (!_counts.compareAndSet(counts, threads + (startThread ? 1 : 0), idle - 1)) + if (!_counts.compareAndSet(counts, threads + startThread, idle + startThread - 1)) continue; break; @@ -513,7 +512,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP if (!_jobs.offer(job)) { // reverse our changes to _counts. - if (addCounts(startThread ? -1 : 0, 1)) + if (addCounts(-startThread, 1 - startThread)) LOG.warn("{} rejected {}", this, job); throw new RejectedExecutionException(job.toString()); } @@ -522,7 +521,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP LOG.debug("queue {} startThread={}", job, startThread); // Start a thread if one was needed - if (startThread) + while (startThread-- > 0) startThread(); } @@ -617,7 +616,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP if (threads < _minThreads || (idle < 0 && threads < _maxThreads)) { // Then try to start a thread. - if (_counts.compareAndSet(counts, threads + 1, idle)) + if (_counts.compareAndSet(counts, threads + 1, idle + 1)) startThread(); // Otherwise continue to check state again. continue; @@ -645,7 +644,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP finally { if (!started) - addCounts(-1, 0); // threads, idle + addCounts(-1, -1); // threads, idle } } @@ -849,13 +848,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP LOG.debug("Runner started for {}", QueuedThreadPool.this); Runnable job = null; - try { - // All threads start idle (not yet taken a job) - if (!addCounts(0, 1)) - return; - while (true) { // If we had a job, signal that we are idle again @@ -863,6 +857,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP { if (!addCounts(0, 1)) break; + job = null; } // else check we are still running else if (_counts.getHi() == Integer.MIN_VALUE) 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 3c7fa26e90b..7480e2442a2 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 @@ -81,8 +81,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec _executor = executor; _capacity = reservedThreads(executor, capacity); _stack = new ConcurrentLinkedDeque<>(); - - LOG.debug("{}", this); + if (LOG.isDebugEnabled()) + LOG.debug("{}", this); } /** @@ -155,6 +155,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec public void doStart() throws Exception { _lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _capacity); + _size.set(0); super.doStart(); } @@ -163,15 +164,29 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec { if (_lease != null) _lease.close(); + + super.doStop(); + while (true) { + int size = _size.get(); + // If no reserved threads left try setting size to -1 to + // atomically prevent other threads adding themselves to stack. + if (size == 0 && _size.compareAndSet(size, -1)) + break; + ReservedThread thread = _stack.pollFirst(); if (thread == null) - break; + { + // Reserved thread must have incremented size but not yet added itself to queue. + // We will spin until it is added. + Thread.onSpinWait(); + continue; + } + _size.decrementAndGet(); thread.stop(); } - super.doStop(); } @Override @@ -194,9 +209,10 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec return false; ReservedThread thread = _stack.pollFirst(); - if (thread == null && task != STOP) + if (thread == null) { - startReservedThread(); + if (task != STOP) + startReservedThread(); return false; } @@ -248,8 +264,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec private class ReservedThread implements Runnable { - private final Locker _locker = new Locker(); - private final Condition _wakeup = _locker.newCondition(); + private final AutoLock _lock = new AutoLock(); + private final Condition _wakeup = _lock.newCondition(); private boolean _starting = true; private Runnable _task = null; @@ -258,7 +274,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec if (LOG.isDebugEnabled()) LOG.debug("{} offer {}", this, task); - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _task = task; _wakeup.signal(); @@ -275,12 +291,13 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec if (LOG.isDebugEnabled()) LOG.debug("{} waiting", this); - Runnable task = null; - while (task == null) + while (true) { - boolean idle = false; + if (!isRunning()) + return STOP; - try (Locker.Lock lock = _locker.lock()) + boolean idle = false; + try (AutoLock lock = _lock.lock()) { if (_task == null) { @@ -296,8 +313,16 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec LOG.ignore(e); } } - task = _task; - _task = null; + else + { + Runnable task = _task; + _task = null; + + if (LOG.isDebugEnabled()) + LOG.debug("{} task={}", this, task); + + return task; + } } if (idle) @@ -312,11 +337,6 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec tryExecute(STOP); } } - - if (LOG.isDebugEnabled()) - LOG.debug("{} task={}", this, task); - - return task; } @Override @@ -329,6 +349,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec while (true) { int size = _size.get(); + if (size < 0) + return; if (size >= _capacity) { if (LOG.isDebugEnabled()) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java index a0c889d162c..891b6f6d9f7 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java @@ -22,7 +22,10 @@ import java.io.IOException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.Dumpable; @@ -40,6 +43,8 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch private final boolean daemon; private final ClassLoader classloader; private final ThreadGroup threadGroup; + private final int threads; + private final AtomicInteger count = new AtomicInteger(); private volatile ScheduledThreadPoolExecutor scheduler; private volatile Thread thread; @@ -50,28 +55,48 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch public ScheduledExecutorScheduler(String name, boolean daemon) { - this(name, daemon, Thread.currentThread().getContextClassLoader()); + this(name, daemon, null); } - public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader) + public ScheduledExecutorScheduler(@Name("name") String name, @Name("daemon") boolean daemon, @Name("threads") int threads) { - this(name, daemon, threadFactoryClassLoader, null); + this(name, daemon, null, null, threads); } - public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader, ThreadGroup threadGroup) + public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader classLoader) { - this.name = name == null ? "Scheduler-" + hashCode() : name; + this(name, daemon, classLoader, null); + } + + public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader classLoader, ThreadGroup threadGroup) + { + this(name, daemon, classLoader, threadGroup, -1); + } + + /** + * @param name The name of the scheduler threads or null for automatic name + * @param daemon True if scheduler threads should be daemon + * @param classLoader The classloader to run the threads with or null to use the current thread context classloader + * @param threadGroup The threadgroup to use or null for no thread group + * @param threads The number of threads to pass to the the core {@link ScheduledThreadPoolExecutor} or -1 for a + * heuristic determined number of threads. + */ + public ScheduledExecutorScheduler(@Name("name") String name, @Name("daemon") boolean daemon, @Name("classLoader") ClassLoader classLoader, @Name("threadGroup") ThreadGroup threadGroup, @Name("threads") int threads) + { + this.name = StringUtil.isBlank(name) ? "Scheduler-" + hashCode() : name; this.daemon = daemon; - this.classloader = threadFactoryClassLoader == null ? Thread.currentThread().getContextClassLoader() : threadFactoryClassLoader; + this.classloader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader; this.threadGroup = threadGroup; + this.threads = threads; } @Override protected void doStart() throws Exception { - scheduler = new ScheduledThreadPoolExecutor(1, r -> + int size = threads > 0 ? threads : 1; + scheduler = new ScheduledThreadPoolExecutor(size, r -> { - Thread thread = ScheduledExecutorScheduler.this.thread = new Thread(threadGroup, r, name); + Thread thread = ScheduledExecutorScheduler.this.thread = new Thread(threadGroup, r, name + "-" + count.incrementAndGet()); thread.setDaemon(daemon); thread.setContextClassLoader(classloader); return thread; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java index d32a779f543..bb14efbeb76 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java @@ -22,11 +22,10 @@ import java.util.concurrent.Executor; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable.InvocationType; -import org.eclipse.jetty.util.thread.Locker; -import org.eclipse.jetty.util.thread.Locker.Lock; /** *

    A strategy where the thread that produces will always run the resulting task.

    @@ -45,7 +44,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable { private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class); - private final Locker _locker = new Locker(); + private final AutoLock _lock = new AutoLock(); private final Runnable _runProduce = new RunProduce(); private final Producer _producer; private final Executor _executor; @@ -67,7 +66,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable LOG.debug("{} execute", this); boolean produce = false; - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { // If we are idle and a thread is not producing if (_idle) @@ -98,7 +97,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable if (LOG.isDebugEnabled()) LOG.debug("{} spawning", this); boolean dispatch = false; - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { if (_idle) dispatch = true; @@ -115,7 +114,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable if (LOG.isDebugEnabled()) LOG.debug("{} run", this); boolean produce = false; - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { _pending = false; if (!_idle && !_producing) @@ -145,7 +144,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable LOG.debug("{} produced {}", this, task); boolean dispatch = false; - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { // Finished producing _producing = false; @@ -191,13 +190,12 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable // Run the task. if (LOG.isDebugEnabled()) LOG.debug("{} run {}", this, task); - if (task != null) - task.run(); + task.run(); if (LOG.isDebugEnabled()) LOG.debug("{} ran {}", this, task); // Once we have run the task, we can try producing again. - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { // Is another thread already producing or we are now idle? if (_producing || _idle) @@ -212,7 +210,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable public Boolean isIdle() { - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { return _idle; } @@ -223,7 +221,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable { StringBuilder builder = new StringBuilder(); builder.append("EPC "); - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { builder.append(_idle ? "Idle/" : ""); builder.append(_producing ? "Prod/" : ""); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java index f37a94a40cf..15de66f6694 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java @@ -22,8 +22,8 @@ import java.util.concurrent.Executor; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ExecutionStrategy; -import org.eclipse.jetty.util.thread.Locker; /** *

    A strategy where the caller thread iterates over task production, submitting each @@ -33,7 +33,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable { private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class); - private final Locker _locker = new Locker(); + private final AutoLock _lock = new AutoLock(); private final Producer _producer; private final Executor _executor; private State _state = State.IDLE; @@ -47,7 +47,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable @Override public void produce() { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -73,7 +73,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable if (task == null) { - try (Locker.Lock lock = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java index 0b7dc16c7bf..a648cc4e118 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java @@ -22,11 +22,10 @@ import java.util.concurrent.Executor; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable.InvocationType; -import org.eclipse.jetty.util.thread.Locker; -import org.eclipse.jetty.util.thread.Locker.Lock; /** *

    A strategy where the caller thread iterates over task production, submitting each @@ -36,7 +35,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy { private static final Logger LOG = Log.getLogger(ProduceExecuteConsume.class); - private final Locker _locker = new Locker(); + private final AutoLock _lock = new AutoLock(); private final Producer _producer; private final Executor _executor; private State _state = State.IDLE; @@ -50,7 +49,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy @Override public void produce() { - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -77,7 +76,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy if (task == null) { - try (Lock locked = _locker.lock()) + try (AutoLock lock = _lock.lock()) { switch (_state) { @@ -106,7 +105,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy @Override public void dispatch() { - _executor.execute(() -> produce()); + _executor.execute(this::produce); } private enum State diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java similarity index 71% rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java rename to jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java index 967969d771d..3b388d0771e 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java @@ -26,19 +26,15 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class LockerTest +public class AutoLockTest { - public LockerTest() - { - } - @Test public void testLocked() { - Locker lock = new Locker(); + AutoLock lock = new AutoLock(); assertFalse(lock.isLocked()); - try (Locker.Lock l = lock.lock()) + try (AutoLock l = lock.lock()) { assertTrue(lock.isLocked()); } @@ -53,10 +49,10 @@ public class LockerTest @Test public void testLockedException() { - Locker lock = new Locker(); + AutoLock lock = new AutoLock(); assertFalse(lock.isLocked()); - try (Locker.Lock l = lock.lock()) + try (AutoLock l = lock.lock()) { assertTrue(lock.isLocked()); throw new Exception(); @@ -76,27 +72,23 @@ public class LockerTest @Test public void testContend() throws Exception { - final Locker lock = new Locker(); + AutoLock lock = new AutoLock(); final CountDownLatch held0 = new CountDownLatch(1); final CountDownLatch hold0 = new CountDownLatch(1); - Thread thread0 = new Thread() + Thread thread0 = new Thread(() -> { - @Override - public void run() + try (AutoLock l = lock.lock()) { - try (Locker.Lock l = lock.lock()) - { - held0.countDown(); - hold0.await(); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } + held0.countDown(); + hold0.await(); } - }; + catch (InterruptedException e) + { + e.printStackTrace(); + } + }); thread0.start(); held0.await(); @@ -104,22 +96,18 @@ public class LockerTest final CountDownLatch held1 = new CountDownLatch(1); final CountDownLatch hold1 = new CountDownLatch(1); - Thread thread1 = new Thread() + Thread thread1 = new Thread(() -> { - @Override - public void run() + try (AutoLock l = lock.lock()) { - try (Locker.Lock l = lock.lock()) - { - held1.countDown(); - hold1.await(); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } + held1.countDown(); + hold1.await(); } - }; + catch (InterruptedException e) + { + e.printStackTrace(); + } + }); thread1.start(); // thread1 will be spinning here assertFalse(held1.await(100, TimeUnit.MILLISECONDS)); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java index edcd78a8137..892addbdeff 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java @@ -45,6 +45,61 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest private static final Logger LOG = Log.getLogger(QueuedThreadPoolTest.class); private final AtomicInteger _jobs = new AtomicInteger(); + private static class TestQueuedThreadPool extends QueuedThreadPool + { + private final AtomicInteger _started; + private final CountDownLatch _enteredRemoveThread; + private final CountDownLatch _exitRemoveThread; + + public TestQueuedThreadPool(AtomicInteger started, CountDownLatch enteredRemoveThread, CountDownLatch exitRemoveThread) + { + _started = started; + _enteredRemoveThread = enteredRemoveThread; + _exitRemoveThread = exitRemoveThread; + } + + public void superStartThread() + { + super.startThread(); + } + + @Override + protected void startThread() + { + switch (_started.incrementAndGet()) + { + case 1: + case 2: + case 3: + super.startThread(); + break; + + case 4: + // deliberately not start thread + break; + + default: + throw new IllegalStateException("too many threads started"); + } + } + + @Override + protected void removeThread(Thread thread) + { + try + { + _enteredRemoveThread.countDown(); + _exitRemoveThread.await(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + + super.removeThread(thread); + } + } + private class RunningJob implements Runnable { final CountDownLatch _run = new CountDownLatch(1); @@ -450,6 +505,63 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest tp.stop(); } + @Test + public void testEnsureThreads() throws Exception + { + AtomicInteger started = new AtomicInteger(0); + + CountDownLatch enteredRemoveThread = new CountDownLatch(1); + CountDownLatch exitRemoveThread = new CountDownLatch(1); + TestQueuedThreadPool tp = new TestQueuedThreadPool(started, enteredRemoveThread, exitRemoveThread); + + tp.setMinThreads(2); + tp.setMaxThreads(10); + tp.setIdleTimeout(400); + tp.setThreadsPriority(Thread.NORM_PRIORITY - 1); + + tp.start(); + waitForIdle(tp, 2); + waitForThreads(tp, 2); + + RunningJob job1 = new RunningJob(); + RunningJob job2 = new RunningJob(); + RunningJob job3 = new RunningJob(); + tp.execute(job1); + tp.execute(job2); + tp.execute(job3); + + waitForThreads(tp, 3); + waitForIdle(tp, 0); + + // We stop job3, the thread becomes idle, thread decides to shrink, and then blocks in removeThread(). + job3.stop(); + assertTrue(enteredRemoveThread.await(5, TimeUnit.SECONDS)); + waitForThreads(tp, 3); + waitForIdle(tp, 1); + + // Executing job4 will not start a new thread because we already have 1 idle thread. + RunningJob job4 = new RunningJob(); + tp.execute(job4); + + // Allow thread to exit from removeThread(). + // The 4th thread is not actually started in our startThread() until tp.superStartThread() is called. + // Delay by 1000ms to check that ensureThreads is only starting one thread even though it is slow to start. + assertThat(started.get(), is(3)); + exitRemoveThread.countDown(); + Thread.sleep(1000); + + // Now startThreads() should have been called 4 times. + // Actually start the thread, and job4 should be run. + assertThat(started.get(), is(4)); + tp.superStartThread(); + assertTrue(job4._run.await(5, TimeUnit.SECONDS)); + + job1.stop(); + job2.stop(); + job4.stop(); + tp.stop(); + } + @Test public void testMaxStopTime() throws Exception { diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java index fa357cb63c3..2ed1ede0717 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java @@ -26,7 +26,7 @@ import java.util.stream.Collectors; public class AbstractConfiguration implements Configuration { - private final boolean _disabledByDefault; + private final boolean _enabledByDefault; private final List _after = new ArrayList<>(); private final List _beforeThis = new ArrayList<>(); private final ClassMatcher _system = new ClassMatcher(); @@ -34,12 +34,12 @@ public class AbstractConfiguration implements Configuration protected AbstractConfiguration() { - this(false); + this(true); } - protected AbstractConfiguration(boolean disabledByDefault) + protected AbstractConfiguration(boolean enabledByDefault) { - _disabledByDefault = disabledByDefault; + _enabledByDefault = enabledByDefault; } /** @@ -196,9 +196,9 @@ public class AbstractConfiguration implements Configuration } @Override - public boolean isDisabledByDefault() + public boolean isEnabledByDefault() { - return _disabledByDefault; + return _enabledByDefault; } @Override diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java index 48e1bef7a26..ab1d90ae41c 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java @@ -34,12 +34,12 @@ import org.eclipse.jetty.util.TopologicalSort; *

    *

    Configuration instances are discovered by the {@link Configurations} class using either the * {@link ServiceLoader} mechanism or by an explicit call to {@link Configurations#setKnown(String...)}. - * By default, all Configurations that do not implement the {@link #isDisabledByDefault()} interface + * By default, all Configurations that do not return false from {@link #isEnabledByDefault()} * are applied to all {@link WebAppContext}s within the JVM. However a Server wide default {@link Configurations} * collection may also be defined with {@link Configurations#setServerDefault(org.eclipse.jetty.server.Server)}. * Furthermore, each individual Context may have its Configurations list explicitly set and/or amended with * {@link WebAppContext#setConfigurations(Configuration[])}, {@link WebAppContext#addConfiguration(Configuration...)} - * or {@link WebAppContext#getWebAppConfigurations()}. + * or {@link WebAppContext#getConfigurations()}. *

    *

    Since Jetty-9.4, Configurations are self ordering using the {@link #getDependencies()} and * {@link #getDependents()} methods for a {@link TopologicalSort} initiated by {@link Configurations#sort()} @@ -171,9 +171,9 @@ public interface Configuration void destroy(WebAppContext context) throws Exception; /** - * @return true if configuration is disabled by default + * @return true if configuration is enabled by default */ - boolean isDisabledByDefault(); + boolean isEnabledByDefault(); /** * @return true if configuration should be aborted diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java index 9868d7f570a..2839445e540 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java @@ -209,7 +209,7 @@ public class Configurations extends AbstractList implements Dumpa if (configurations == null) { configurations = new Configurations(Configurations.getKnown().stream() - .filter(c -> !c.isDisabledByDefault()) + .filter(c -> c.isEnabledByDefault()) .map(c -> c.getClass().getName()) .toArray(String[]::new)); } @@ -279,6 +279,27 @@ public class Configurations extends AbstractList implements Dumpa } } + public T get(Class configClass) + { + for (Configuration configuration : _configurations) + { + if (configClass.isAssignableFrom(configuration.getClass())) + return (T)configuration; + } + return null; + } + + public List getConfigurations(Class configClass) + { + List list = new ArrayList<>(); + for (Configuration configuration : _configurations) + { + if (configClass.isAssignableFrom(configuration.getClass())) + list.add((T)configuration); + } + return list; + } + public void clear() { _configurations.clear(); diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java index 0758e441616..3ce9d5cbf51 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java @@ -403,7 +403,7 @@ public class MetaData p.process(context, getWebXml()); for (WebDescriptor wd : getOverrideWebs()) { - LOG.debug("process {} {}", context, wd); + LOG.debug("process {} {} {}", context, p, wd); p.process(context, wd); } } diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index 786fcf1b1b2..7bdb0e1a387 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -95,7 +95,7 @@ import org.eclipse.jetty.util.resource.ResourceCollection; *

      *
    • Add all Server class inclusions from all known configurations {@link Configurations#getKnown()}
    • *
    • {@link #loadConfigurations()}, which uses either explicitly set Configurations or takes the server - * default (which is all known non {@link Configuration#isDisabledByDefault()} Configurations.
    • + * default (which is all known {@link Configuration#isEnabledByDefault()} Configurations. *
    • Sort the configurations using {@link TopologicalSort} in {@link Configurations#sort()}.
    • *
    • Add all Server class exclusions from this webapps {@link Configurations}
    • *
    • Add all System classes inclusions and exclusions for this webapps {@link Configurations}
    • @@ -183,10 +183,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL "org.eclipse.jetty." // hide jetty classes ); - private final Configurations _configurations = new Configurations(); private final ClassMatcher _systemClasses = new ClassMatcher(__dftSystemClasses); private final ClassMatcher _serverClasses = new ClassMatcher(__dftServerClasses); + private Configurations _configurations; private String _defaultsDescriptor = WEB_DEFAULTS_XML; private String _descriptor = null; private final List _overrideDescriptors = new ArrayList<>(); @@ -570,18 +570,21 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { // Prepare for configuration MultiException mx = new MultiException(); - for (Configuration configuration : _configurations) + if (_configurations != null) { - try + for (Configuration configuration : _configurations) { - configuration.destroy(this); - } - catch (Exception e) - { - mx.add(e); + try + { + configuration.destroy(this); + } + catch (Exception e) + { + mx.add(e); + } } } - _configurations.clear(); + _configurations = null; super.destroy(); mx.ifExceptionThrowRuntime(); } @@ -615,10 +618,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /** * @return Returns the configurations. */ - public Configurations getWebAppConfigurations() + public Configurations getConfigurations() { - if (_configurations.size() == 0) - loadConfigurations(); + loadConfigurations(); return _configurations; } @@ -885,10 +887,18 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL protected void loadConfigurations() { //if the configuration instances have been set explicitly, use them - if (!_configurations.isEmpty()) + if (_configurations != null) return; + if (isStarted()) + throw new IllegalStateException(); + _configurations = newConfigurations(); + } - _configurations.add(Configurations.getServerDefault(getServer()).toArray()); + protected Configurations newConfigurations() + { + Configurations configurations = new Configurations(); + configurations.add(Configurations.getServerDefault(getServer()).toArray()); + return configurations; } @Override @@ -958,8 +968,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL */ public void setConfigurationClasses(String[] configurations) { - if (isStarted()) - throw new IllegalStateException(); + if (_configurations == null) + _configurations = new Configurations(); _configurations.set(configurations); } @@ -973,15 +983,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL */ public void setConfigurations(Configuration[] configurations) { - if (isStarted()) - throw new IllegalStateException(); + if (_configurations == null) + _configurations = new Configurations(); _configurations.set(configurations); } public void addConfiguration(Configuration... configuration) { - if (isStarted()) - throw new IllegalStateException(); loadConfigurations(); _configurations.add(configuration); } @@ -989,12 +997,19 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL public T getConfiguration(Class configClass) { loadConfigurations(); - for (Configuration configuration : _configurations) - { - if (configClass.isAssignableFrom(configuration.getClass())) - return (T)configuration; - } - return null; + return _configurations.get(configClass); + } + + public void removeConfiguration(Configuration... configurations) + { + if (_configurations != null) + _configurations.remove(configurations); + } + + public void removeConfiguration(Class... configurations) + { + if (_configurations != null) + _configurations.remove(configurations); } /** diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java index a25f4e399bf..99a8c3202bd 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -38,6 +38,7 @@ public class WebInfConfiguration extends AbstractConfiguration private static final Logger LOG = Log.getLogger(WebInfConfiguration.class); public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured"; + public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase"; protected Resource _preUnpackBaseResource; @@ -338,8 +339,11 @@ public class WebInfConfiguration extends AbstractConfiguration } if (extractedWebAppDir == null) + { // Then extract it if necessary to the temporary location extractedWebAppDir = new File(context.getTempDirectory(), "webapp"); + context.setAttribute(TEMPORARY_RESOURCE_BASE, extractedWebAppDir); + } if (webApp.getFile() != null && webApp.getFile().isDirectory()) { 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 20e90ef451d..04f40e0f02d 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 @@ -100,7 +100,7 @@ public class WebAppContextTest { Configurations.cleanKnown(); String[] known_and_enabled = Configurations.getKnown().stream() - .filter(c -> !c.isDisabledByDefault()) + .filter(c -> c.isEnabledByDefault()) .map(c -> c.getClass().getName()) .toArray(String[]::new); @@ -108,7 +108,7 @@ public class WebAppContextTest //test if no classnames set, its the defaults WebAppContext wac = new WebAppContext(); - assertThat(wac.getWebAppConfigurations().stream() + assertThat(wac.getConfigurations().stream() .map(c -> c.getClass().getName()) .collect(Collectors.toList()), Matchers.containsInAnyOrder(known_and_enabled)); @@ -126,7 +126,7 @@ public class WebAppContextTest Configurations.cleanKnown(); WebAppContext wac = new WebAppContext(); wac.setServer(new Server()); - assertThat(wac.getWebAppConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()), + assertThat(wac.getConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()), Matchers.contains( "org.eclipse.jetty.webapp.JmxConfiguration", "org.eclipse.jetty.webapp.WebInfConfiguration", @@ -144,14 +144,14 @@ public class WebAppContextTest Configuration[] configs = {new WebInfConfiguration()}; WebAppContext wac = new WebAppContext(); wac.setConfigurations(configs); - assertThat(wac.getWebAppConfigurations(), Matchers.contains(configs)); + assertThat(wac.getConfigurations(), Matchers.contains(configs)); //test that explicit config instances override any from server String[] classNames = {"x.y.z"}; Server server = new Server(); server.setAttribute(Configuration.ATTR, classNames); wac.setServer(server); - assertThat(wac.getWebAppConfigurations(), Matchers.contains(configs)); + assertThat(wac.getConfigurations(), Matchers.contains(configs)); } @Test diff --git a/jetty-websocket/javax-websocket-client/pom.xml b/jetty-websocket/javax-websocket-client/pom.xml index c4a74057925..bdbaea8cb23 100644 --- a/jetty-websocket/javax-websocket-client/pom.xml +++ b/jetty-websocket/javax-websocket-client/pom.xml @@ -29,6 +29,12 @@ org.eclipse.jetty.toolchain jetty-javax-websocket-api + + org.eclipse.jetty + jetty-xml + ${project.version} + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureClientContainerExample.java b/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureClientContainerExample.java similarity index 81% rename from jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureClientContainerExample.java rename to jetty-websocket/javax-websocket-client/src/test/java/examples/SecureClientContainerExample.java index dbba5d2365c..31b53627d70 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/test/java/examples/SecureClientContainerExample.java +++ b/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureClientContainerExample.java @@ -24,9 +24,11 @@ import javax.websocket.ClientEndpointConfig; import javax.websocket.WebSocketContainer; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.websocket.jsr356.ClientContainer; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer; public class SecureClientContainerExample { @@ -73,11 +75,14 @@ public class SecureClientContainerExample */ public static WebSocketContainer getConfiguredWebSocketContainer() throws Exception { - SslContextFactory ssl = new SslContextFactory.Client(); + SslContextFactory.Client ssl = new SslContextFactory.Client(); ssl.setExcludeCipherSuites(); // echo.websocket.org use WEAK cipher suites - HttpClient httpClient = new HttpClient(ssl); - ClientContainer clientContainer = new ClientContainer(httpClient); - clientContainer.getClient().addManaged(httpClient); // allow clientContainer to own httpClient (for start/stop lifecycle) + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSslContextFactory(ssl); + + HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); + JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(httpClient); + clientContainer.addManaged(httpClient); // allow clientContainer to own httpClient (for start/stop lifecycle) clientContainer.start(); return clientContainer; } diff --git a/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureWebSocketContainerExample.java b/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureWebSocketContainerExample.java index b736f10a2e4..d8309eb9533 100644 --- a/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureWebSocketContainerExample.java +++ b/jetty-websocket/javax-websocket-client/src/test/java/examples/SecureWebSocketContainerExample.java @@ -21,14 +21,12 @@ package examples; import java.io.FileNotFoundException; import java.net.URI; import java.net.URL; -import java.net.URLClassLoader; import java.util.concurrent.TimeUnit; import javax.websocket.ClientEndpointConfig; import javax.websocket.ContainerProvider; import javax.websocket.WebSocketContainer; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; public class SecureWebSocketContainerExample { @@ -69,47 +67,27 @@ public class SecureWebSocketContainerExample * Since javax.websocket does not have an API for configuring SSL, each implementation * of javax.websocket has to come up with their own SSL configuration mechanism. *

      - * When the call to {@link javax.websocket.ContainerProvider}.{@link ContainerProvider#getWebSocketContainer()} - * occurs, that needs to have a started and available WebSocket Client. - * Jetty's {@code WebSocketClient} must have a Jetty {@code HttpClient} started as well. - * If you want SSL, then that configuration has to be passed into the Jetty {@code HttpClient} at initialization. + * When the {@link WebSocketContainer} is used it will need to create and start a Jetty WebSocket Client. + * The {@code WebSocketClient} must use a Jetty {@code HttpClient} which can be configured for SSL, this + * configuration needs to be passed into the Jetty {@code HttpClient} at initialization. *

      *

      * How Jetty makes this available, is via the {@code jetty-websocket-httpclient.xml} classloader resource * along with the jetty-xml artifact. *

      - *

      - * This method will look for the file in the classloader resources, and then - * sets up a {@link URLClassLoader} to make that {@code jetty-websocket-httpclient.xml} available - * for this specific example. - * If we had put the `jetty-websocket-httpclient.xml` in the root of a JAR file loaded by this - * project then you can skip all of the classloader trickery this method performs. - *

      - * * @return the client WebSocketContainer * @see javax.websocket issue #210 */ public static WebSocketContainer getConfiguredWebSocketContainer() throws Exception { URL jettyHttpClientConfigUrl = Thread.currentThread().getContextClassLoader() - .getResource("examples/jetty-websocket-httpclient.xml"); + .getResource("jetty-websocket-httpclient.xml"); if (jettyHttpClientConfigUrl == null) { throw new FileNotFoundException("Unable to find Jetty HttpClient configuration XML"); } - URI jettyConfigDirUri = jettyHttpClientConfigUrl.toURI().resolve("./"); - - ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); - URL[] urls = new URL[]{ - jettyConfigDirUri.toURL() - }; - URLClassLoader classLoader = new URLClassLoader(urls, parentClassLoader); - - try (ThreadClassLoaderScope ignore = new ThreadClassLoaderScope(classLoader)) - { - return ContainerProvider.getWebSocketContainer(); - } + return ContainerProvider.getWebSocketContainer(); } } diff --git a/jetty-websocket/javax-websocket-client/src/test/resources/examples/jetty-websocket-httpclient.xml b/jetty-websocket/javax-websocket-client/src/test/resources/examples/jetty-websocket-httpclient.xml deleted file mode 100644 index 672007a92cd..00000000000 --- a/jetty-websocket/javax-websocket-client/src/test/resources/examples/jetty-websocket-httpclient.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - false - - - - TLS/1.3 - - - - - - - - - - - 5000 - diff --git a/jetty-websocket/javax-websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-client/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..e6624861732 --- /dev/null +++ b/jetty-websocket/javax-websocket-client/src/test/resources/jetty-logging.properties @@ -0,0 +1,28 @@ +# +# +# ======================================================================== +# 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. +# ======================================================================== +# +# +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.LEVEL=INFO +# org.eclipse.jetty.websocket.LEVEL=DEBUG +# org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG +# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG +# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG +# org.eclipse.jetty.client.LEVEL=DEBUG +# org.eclipse.jetty.io.LEVEL=DEBUG +# org.eclipse.jetty.io.ManagedSelector.LEVEL=INFO \ No newline at end of file diff --git a/jetty-websocket/javax-websocket-client/src/test/resources/jetty-websocket-httpclient.xml b/jetty-websocket/javax-websocket-client/src/test/resources/jetty-websocket-httpclient.xml new file mode 100644 index 00000000000..f2bab081984 --- /dev/null +++ b/jetty-websocket/javax-websocket-client/src/test/resources/jetty-websocket-httpclient.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jetty-websocket/javax-websocket-common/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-common/src/test/resources/jetty-logging.properties index 4aebf62b5f8..ef7003a8baa 100644 --- a/jetty-websocket/javax-websocket-common/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/javax-websocket-common/src/test/resources/jetty-logging.properties @@ -1,25 +1,5 @@ -# -# -# ======================================================================== -# 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. -# ======================================================================== -# -# -# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.util.log.stderr.LONG=true # org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG diff --git a/jetty-websocket/javax-websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-server/src/test/resources/jetty-logging.properties index d9e757d813a..cfafdb369a4 100644 --- a/jetty-websocket/javax-websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/javax-websocket-server/src/test/resources/jetty-logging.properties @@ -1,11 +1,10 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO # org.eclipse.jetty.websocket.LEVEL=WARN # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.WebSocketSession.LEVEL=DEBUG -# org.eclipse.jetty.websocket.jsr356.LEVEL=DEBUG ### Show state changes on BrowserDebugTool # -- LEAVE THIS AT DEBUG LEVEL -- -org.eclipse.jetty.websocket.jsr356.server.browser.LEVEL=DEBUG +org.eclipse.jetty.websocket.javax.server.browser.LEVEL=DEBUG diff --git a/jetty-websocket/javax-websocket-tests/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-tests/src/test/resources/jetty-logging.properties index d078063b659..e9d6afe39e7 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/javax-websocket-tests/src/test/resources/jetty-logging.properties @@ -1,25 +1,5 @@ -# -# -# ======================================================================== -# 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. -# ======================================================================== -# -# -# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.util.log.stderr.LONG=true # org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG @@ -28,16 +8,8 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.LEVEL=DEBUG # org.eclipse.jetty.io.ManagedSelector.LEVEL=INFO # org.eclipse.jetty.websocket.LEVEL=DEBUG -# org.eclipse.jetty.websocket.core.internal.WebSocketCoreSessionsion.LEVEL=DEBUG -# org.eclipse.jetty.websocket.jsr356.tests.LEVEL=DEBUG +# org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO -# org.eclipse.jetty.websocket.jsr356.messages.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.LEVEL=DEBUG # org.eclipse.jetty.websocket.tests.client.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.client.jsr356.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.server.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.server.jsr356.LEVEL=DEBUG -### Showing any unintended (ignored) errors from CompletionCallback -# org.eclipse.jetty.websocket.common.CompletionCallback.LEVEL=ALL -### Disabling intentional error out of RFCSocket -org.eclipse.jetty.websocket.tests.server.RFCSocket.LEVEL=OFF +# org.eclipse.jetty.websocket.tests.server.LEVEL=DEBUG \ No newline at end of file diff --git a/jetty-websocket/jetty-websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/jetty-websocket-client/src/test/resources/jetty-logging.properties index b813365ce20..b88f6f45f02 100644 --- a/jetty-websocket/jetty-websocket-client/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/jetty-websocket-client/src/test/resources/jetty-logging.properties @@ -1,5 +1,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.io.LEVEL=INFO # org.eclipse.jetty.client.LEVEL=DEBUG @@ -8,11 +7,9 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.client.LEVEL=DEBUG # org.eclipse.jetty.websocket.client.ClientCloseTest.LEVEL=DEBUG -org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG +# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.test.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG -org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG -# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG -### Hide the stacktraces during testing -org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false +# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG +# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG \ No newline at end of file diff --git a/jetty-websocket/jetty-websocket-common/src/test/resources/jetty-logging.properties b/jetty-websocket/jetty-websocket-common/src/test/resources/jetty-logging.properties index af83047e755..07faa86dbce 100644 --- a/jetty-websocket/jetty-websocket-common/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/jetty-websocket-common/src/test/resources/jetty-logging.properties @@ -1,18 +1 @@ -# -# ======================================================================== -# 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. -# ======================================================================== -# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog \ No newline at end of file diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java index 0c1302b0bab..dafc7ad0bb2 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java @@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class SuspendResumeTest @@ -192,8 +193,7 @@ public class SuspendResumeTest assertNull(clientSocket.error); assertNull(serverSocket.error); - // suspend the client so that no read events occur - SuspendToken suspendToken = clientSocket.session.suspend(); - suspendToken.resume(); + // suspend after closed throws ISE + assertThrows(IllegalStateException.class, () -> clientSocket.session.suspend()); } } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WriteAfterStopTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WriteAfterStopTest.java index 434e12c90b1..30a7b37431d 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WriteAfterStopTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WriteAfterStopTest.java @@ -19,20 +19,16 @@ package org.eclipse.jetty.websocket.tests; import java.net.URI; -import java.nio.channels.ClosedChannelException; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.core.internal.compress.CompressExtension; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; @@ -108,7 +104,8 @@ public class WriteAfterStopTest assertThat(clientSocket.statusCode, is(StatusCode.NORMAL)); assertThat(serverSocket.statusCode, is(StatusCode.NORMAL)); - ((WebSocketSession)session).stop(); - assertThrows(IllegalStateException.class, () -> session.getRemote().sendString("hello world")); + IllegalStateException failure = assertThrows(IllegalStateException.class, + () -> session.getRemote().sendString("this should fail before ExtensionStack")); + assertThat(failure.getMessage(), is("CLOSED")); } } \ No newline at end of file diff --git a/jetty-websocket/jetty-websocket-tests/src/test/resources/jetty-logging.properties b/jetty-websocket/jetty-websocket-tests/src/test/resources/jetty-logging.properties index 8806e105177..c5aaeeb584b 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/jetty-websocket-tests/src/test/resources/jetty-logging.properties @@ -1,44 +1,10 @@ -# -# -# ======================================================================== -# 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. -# ======================================================================== -# -# -# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN -# org.eclipse.jetty.websocket.tests.LEVEL=DEBUG -# org.eclipse.jetty.util.log.stderr.LONG=true +# org.eclipse.jetty.LEVEL=DEBUG +# org.eclipse.jetty.websocket.LEVEL=DEBUG +# org.eclipse.jetty.websocket.test.LEVEL=DEBUG # org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG # org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG # org.eclipse.jetty.client.LEVEL=DEBUG # org.eclipse.jetty.io.LEVEL=DEBUG -# org.eclipse.jetty.io.ManagedSelector.LEVEL=INFO -# org.eclipse.jetty.websocket.LEVEL=DEBUG -# org.eclipse.jetty.websocket.core.internal.WebSocketCoreSessionsion.LEVEL=DEBUG -# org.eclipse.jetty.websocket.jsr356.tests.LEVEL=DEBUG -# org.eclipse.jetty.websocket.LEVEL=INFO -# org.eclipse.jetty.websocket.jsr356.messages.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.client.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.client.jsr356.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.server.LEVEL=DEBUG -# org.eclipse.jetty.websocket.tests.server.jsr356.LEVEL=DEBUG -### Showing any unintended (ignored) errors from CompletionCallback -# org.eclipse.jetty.websocket.common.CompletionCallback.LEVEL=ALL -### Disabling intentional error out of RFCSocket -org.eclipse.jetty.websocket.tests.server.RFCSocket.LEVEL=OFF +# org.eclipse.jetty.io.ManagedSelector.LEVEL=INFO \ No newline at end of file diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java index 139e355df63..c1b6057e41f 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.core.client; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Base64; @@ -247,19 +248,18 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon } Throwable failure = result.getFailure(); - boolean wrapFailure = !((failure instanceof java.net.SocketException) || - (failure instanceof java.io.InterruptedIOException) || - (failure instanceof UpgradeException)); + boolean wrapFailure = !((failure instanceof IOException) || (failure instanceof UpgradeException)); if (wrapFailure) failure = new UpgradeException(requestURI, responseStatusCode, responseLine, failure); handleException(failure); + return; } if (responseStatusCode != HttpStatus.SWITCHING_PROTOCOLS_101) { // Failed to upgrade (other reason) - handleException( - new UpgradeException(requestURI, responseStatusCode, "Failed to upgrade to websocket: Unexpected HTTP Response Status Code: " + responseLine)); + handleException(new UpgradeException(requestURI, responseStatusCode, + "Failed to upgrade to websocket: Unexpected HTTP Response Status Code: " + responseLine)); } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java index f53331d4b88..a99e193d78f 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java @@ -50,6 +50,8 @@ public class FrameFlusher extends IteratingCallback private static final Logger LOG = Log.getLogger(FrameFlusher.class); private static final Throwable CLOSED_CHANNEL = new ClosedChannelException(); + private final LongAdder messagesOut = new LongAdder(); + private final LongAdder bytesOut = new LongAdder(); private final ByteBufferPool bufferPool; private final EndPoint endPoint; private final int bufferSize; @@ -62,13 +64,12 @@ public class FrameFlusher extends IteratingCallback private final List previousEntries; private final List failedEntries; - private ByteBuffer batchBuffer = null; + private ByteBuffer batchBuffer; private boolean canEnqueue = true; private boolean flushed = true; private Throwable closedCause; - private LongAdder messagesOut = new LongAdder(); - private LongAdder bytesOut = new LongAdder(); - private long idleTimeout = 0; + private long idleTimeout; + private boolean useDirectByteBuffers; public FrameFlusher(ByteBufferPool bufferPool, Scheduler scheduler, Generator generator, EndPoint endPoint, int bufferSize, int maxGather) { @@ -84,6 +85,16 @@ public class FrameFlusher extends IteratingCallback this.timeoutScheduler = scheduler; } + public boolean isUseDirectByteBuffers() + { + return useDirectByteBuffers; + } + + public void setUseDirectByteBuffers(boolean useDirectByteBuffers) + { + this.useDirectByteBuffers = useDirectByteBuffers; + } + /** * Enqueue a Frame to be written to the endpoint. * @@ -225,7 +236,7 @@ public class FrameFlusher extends IteratingCallback // Acquire a batchBuffer if we don't have one if (batchBuffer == null) { - batchBuffer = bufferPool.acquire(bufferSize, true); + batchBuffer = acquireBuffer(bufferSize); buffers.add(batchBuffer); } @@ -249,7 +260,10 @@ public class FrameFlusher extends IteratingCallback else { // Add headers and payload to the list of buffers - buffers.add(entry.generateHeaderBytes()); + // TODO: release this buffer. + ByteBuffer buffer = acquireBuffer(Generator.MAX_HEADER_LENGTH); + buffers.add(buffer); + entry.generateHeaderBytes(buffer); flush = true; ByteBuffer payload = entry.frame.getPayload(); if (BufferUtil.hasContent(payload)) @@ -308,6 +322,11 @@ public class FrameFlusher extends IteratingCallback return Action.SCHEDULED; } + private ByteBuffer acquireBuffer(int capacity) + { + return bufferPool.acquire(capacity, isUseDirectByteBuffers()); + } + private int getQueueSize() { synchronized (this) @@ -474,11 +493,6 @@ public class FrameFlusher extends IteratingCallback super(frame, callback, batch); } - private ByteBuffer generateHeaderBytes() - { - return headerBuffer = generator.generateHeaderBytes(frame); - } - private void generateHeaderBytes(ByteBuffer buffer) { int pos = BufferUtil.flipToFill(buffer); diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java index dacc62bb685..d0a7706c049 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java @@ -68,6 +68,8 @@ public class WebSocketConnection extends AbstractConnection implements Connectio // Read / Parse variables private RetainableByteBuffer networkBuffer; + private boolean useInputDirectByteBuffers; + private boolean useOutputDirectByteBuffers; /** * Create a WSConnection. @@ -132,6 +134,26 @@ public class WebSocketConnection extends AbstractConnection implements Connectio return getEndPoint().getRemoteAddress(); } + public boolean isUseInputDirectByteBuffers() + { + return useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + this.useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + public boolean isUseOutputDirectByteBuffers() + { + return useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + /** * Physical connection disconnect. *

      @@ -222,7 +244,7 @@ public class WebSocketConnection extends AbstractConnection implements Connectio synchronized (this) { if (networkBuffer == null) - networkBuffer = new RetainableByteBuffer(bufferPool, getInputBufferSize()); + networkBuffer = newNetworkBuffer(getInputBufferSize()); } } @@ -237,10 +259,15 @@ public class WebSocketConnection extends AbstractConnection implements Connectio throw new IllegalStateException(); networkBuffer.release(); - networkBuffer = new RetainableByteBuffer(bufferPool, getInputBufferSize()); + networkBuffer = newNetworkBuffer(getInputBufferSize()); } } + private RetainableByteBuffer newNetworkBuffer(int capacity) + { + return new RetainableByteBuffer(bufferPool, capacity, isUseInputDirectByteBuffers()); + } + private void releaseNetworkBuffer() { synchronized (this) @@ -445,7 +472,7 @@ public class WebSocketConnection extends AbstractConnection implements Connectio { synchronized (this) { - networkBuffer = new RetainableByteBuffer(bufferPool, prefilled.remaining()); + networkBuffer = newNetworkBuffer(prefilled.remaining()); } ByteBuffer buffer = networkBuffer.getBuffer(); BufferUtil.clearToFill(buffer); @@ -572,6 +599,7 @@ public class WebSocketConnection extends AbstractConnection implements Connectio private Flusher(Scheduler scheduler, int bufferSize, Generator generator, EndPoint endpoint) { super(bufferPool, scheduler, generator, endpoint, bufferSize, 8); + setUseDirectByteBuffers(isUseOutputDirectByteBuffers()); } @Override diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/compress/CompressExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/compress/CompressExtension.java index adc99d598bc..b20386f3427 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/compress/CompressExtension.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/compress/CompressExtension.java @@ -119,8 +119,8 @@ public abstract class CompressExtension extends AbstractExtension public void releaseDeflater() { - getInflaterPool().release(inflaterImpl); - inflaterImpl = null; + getDeflaterPool().release(deflaterImpl); + deflaterImpl = null; } /** diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java index b7fd481cb94..2ecd11ef599 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java @@ -201,6 +201,11 @@ public final class RFC6455Handshaker implements Handshaker // Create a connection WebSocketConnection connection = newWebSocketConnection(httpChannel.getEndPoint(), connector.getExecutor(), connector.getScheduler(), connector.getByteBufferPool(), coreSession); + // TODO: perhaps use of direct buffers should be WebSocket specific + // rather than inheriting the setting from HttpConfiguration. + HttpConfiguration httpConfig = httpChannel.getHttpConfiguration(); + connection.setUseInputDirectByteBuffers(httpConfig.isUseInputDirectByteBuffers()); + connection.setUseOutputDirectByteBuffers(httpChannel.isUseOutputDirectByteBuffers()); if (LOG.isDebugEnabled()) LOG.debug("connection {}", connection); if (connection == null) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java index ee65299e348..0553d617ce8 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.Frame; @@ -88,7 +89,7 @@ public class ValidationExtensionTest extends WebSocketTester Frame frame = serverHandler.receivedFrames.poll(5, TimeUnit.SECONDS); assertNotNull(frame); assertThat(frame.getOpCode(), is(OpCode.BINARY)); - assertThat(frame.getPayload().array(), is(nonUtf8Payload)); + assertThat(BufferUtil.toArray(frame.getPayload()), is(nonUtf8Payload)); //close normally client.getOutputStream().write(RawFrameBuilder.buildClose(CloseStatus.NORMAL_STATUS, true)); @@ -113,13 +114,13 @@ public class ValidationExtensionTest extends WebSocketTester Frame frame = serverHandler.receivedFrames.poll(5, TimeUnit.SECONDS); assertNotNull(frame); assertThat(frame.getOpCode(), is(OpCode.TEXT)); - assertThat(frame.getPayload().array(), is(initialPayload)); + assertThat(BufferUtil.toArray(frame.getPayload()), is(initialPayload)); client.getOutputStream().write(RawFrameBuilder.buildFrame(OpCode.CONTINUATION, continuationPayload, true)); frame = serverHandler.receivedFrames.poll(5, TimeUnit.SECONDS); assertNotNull(frame); assertThat(frame.getOpCode(), is(OpCode.CONTINUATION)); - assertThat(frame.getPayload().array(), is(continuationPayload)); + assertThat(BufferUtil.toArray(frame.getPayload()), is(continuationPayload)); //close normally client.getOutputStream().write(RawFrameBuilder.buildClose(CloseStatus.NORMAL_STATUS, true)); @@ -144,7 +145,7 @@ public class ValidationExtensionTest extends WebSocketTester Frame frame = serverHandler.receivedFrames.poll(5, TimeUnit.SECONDS); assertNotNull(frame); assertThat(frame.getOpCode(), is(OpCode.TEXT)); - assertThat(frame.getPayload().array(), is(initialPayload)); + assertThat(BufferUtil.toArray(frame.getPayload()), is(initialPayload)); client.getOutputStream().write(RawFrameBuilder.buildFrame(OpCode.CONTINUATION, incompleteContinuationPayload, true)); frame = receiveFrame(client.getInputStream()); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java index 76b1e6866b1..d7f264d7fa7 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java @@ -164,12 +164,6 @@ public class MockEndpoint implements EndPoint throw new UnsupportedOperationException(NOT_SUPPORTED); } - @Override - public boolean isOptimizedForDirectBuffers() - { - throw new UnsupportedOperationException(NOT_SUPPORTED); - } - @Override public void upgrade(Connection newConnection) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java index 1f69e9810db..60ddf4b3a7d 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java @@ -24,7 +24,6 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -43,8 +42,6 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -207,29 +204,6 @@ public class WebSocketServerTest extends WebSocketTester } assertThat(serverHandler.receivedFrames.size(), is(5)); assertThat(receivedCallbacks.size(), is(5)); - - byte[] first = serverHandler.receivedFrames.poll().getPayload().array(); - assertThat(serverHandler.receivedFrames.poll().getPayload().array(), sameInstance(first)); - assertThat(serverHandler.receivedFrames.poll().getPayload().array(), sameInstance(first)); - byte[] second = serverHandler.receivedFrames.poll().getPayload().array(); - assertThat(serverHandler.receivedFrames.poll().getPayload().array(), sameInstance(second)); - assertThat(first, not(sameInstance(second))); - - ByteBufferPool pool = server.getServer().getConnectors()[0].getByteBufferPool(); - - assertThat(pool.acquire(first.length, false).array(), not(sameInstance(first))); - receivedCallbacks.poll().succeeded(); - assertThat(pool.acquire(first.length, false).array(), not(sameInstance(first))); - receivedCallbacks.poll().succeeded(); - assertThat(pool.acquire(first.length, false).array(), not(sameInstance(first))); - receivedCallbacks.poll().succeeded(); - assertThat(pool.acquire(first.length, false).array(), sameInstance(first)); - - assertThat(pool.acquire(second.length, false).array(), not(sameInstance(second))); - receivedCallbacks.poll().succeeded(); - assertThat(pool.acquire(second.length, false).array(), not(sameInstance(second))); - receivedCallbacks.poll().succeeded(); - assertThat(pool.acquire(second.length, false).array(), sameInstance(second)); } } diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties index 039b5a51133..03fa0413842 100644 --- a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties @@ -1,22 +1,5 @@ -# -# ======================================================================== -# 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. -# ======================================================================== -# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.core.LEVEL=DEBUG # org.eclipse.jetty.util.log.stderr.LONG=true @@ -28,6 +11,4 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.ManagedSelector.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO -# org.eclipse.jetty.websocket.core.LEVEL=DEBUG -### Showing any unintended (ignored) errors from CompletionCallback -# org.eclipse.jetty.websocket.core.CompletionCallback.LEVEL=ALL +# org.eclipse.jetty.websocket.core.LEVEL=DEBUG \ No newline at end of file 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 dd4000c758f..de09b819320 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 @@ -36,6 +36,7 @@ import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -1747,6 +1748,8 @@ public class XmlConfiguration properties.putAll(System.getProperties()); // For all arguments, load properties + if (LOG.isDebugEnabled()) + LOG.debug("args={}", Arrays.asList(args)); for (String arg : args) { if (arg.indexOf('=') >= 0) @@ -1785,17 +1788,34 @@ public class XmlConfiguration } } + if (LOG.isDebugEnabled()) + LOG.debug("objects={}", Arrays.asList(objects)); + // For all objects created by XmlConfigurations, start them if they are lifecycles. + List started = new ArrayList<>(objects.size()); for (Object obj : objects) { if (obj instanceof LifeCycle) { LifeCycle lc = (LifeCycle)obj; if (!lc.isRunning()) + { lc.start(); + if (lc.isStarted()) + started.add(lc); + else + { + // Failed to start a component, so stop all started components + Collections.reverse(started); + for (LifeCycle slc : started) + { + slc.stop(); + } + break; + } + } } } - return null; }); } diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java index 9617f972f23..0b026988436 100644 --- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java @@ -1146,6 +1146,7 @@ public class XmlConfigurationTest ByteArrayOutputStream logBytes = null; Logger logger = Log.getLogger(XmlConfiguration.class); + logger.setDebugEnabled(true); if (logger instanceof StdErrLog) { StdErrLog stdErrLog = (StdErrLog)logger; @@ -1155,6 +1156,7 @@ public class XmlConfigurationTest xmlConfiguration.configure(); + logger.setDebugEnabled(false); if (logBytes != null) { String[] lines = logBytes.toString(UTF_8.name()).split(System.lineSeparator()); diff --git a/pom.xml b/pom.xml index e80bf0c53f4..a6e68ce953c 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ jetty-server jetty-xml jetty-security + jetty-openid jetty-servlet jetty-webapp jetty-fcgi @@ -271,7 +272,7 @@ true false - javadoc:aggregate-jar deploy + deploy -Peclipse-release clean install forked-path @@ -505,7 +506,7 @@ org.apache.maven.plugins maven-invoker-plugin - 3.2.1-SNAPSHOT + 3.2.1 true true diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java index 9248a2dc5e4..21b1ffc4684 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java @@ -103,7 +103,7 @@ public class BadAppTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/badapp/"); @@ -143,7 +143,7 @@ public class BadAppTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/badapp/"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java index dd7d90cda29..f0d7f981e84 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java @@ -101,7 +101,7 @@ public class CDITests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/demo/greetings"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java index 40828383857..4590273f4c8 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java @@ -56,7 +56,7 @@ public class DemoBaseTests extends AbstractDistributionTest try (DistributionTester.Run run1 = distribution.start(args)) { - assertTrue(run1.awaitConsoleLogsFor("Started @", 20, TimeUnit.SECONDS)); + assertTrue(run1.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + httpPort + "/test/jsp/dump.jsp"); @@ -88,7 +88,7 @@ public class DemoBaseTests extends AbstractDistributionTest try (DistributionTester.Run run1 = distribution.start(args)) { - assertTrue(run1.awaitConsoleLogsFor("Started @", 20, TimeUnit.SECONDS)); + assertTrue(run1.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response; @@ -133,7 +133,7 @@ public class DemoBaseTests extends AbstractDistributionTest try (DistributionTester.Run run1 = distribution.start(args)) { - assertTrue(run1.awaitConsoleLogsFor("Started @", 20, TimeUnit.SECONDS)); + assertTrue(run1.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.POST("http://localhost:" + httpPort + "/test-spec/asy/xx").send(); @@ -142,4 +142,32 @@ public class DemoBaseTests extends AbstractDistributionTest assertThat(response.getContentAsString(), not(containsString("FAIL"))); } } + + @Test + public void testJPMS() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(Paths.get("demo-base")) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + int httpPort = distribution.freePort(); + int httpsPort = distribution.freePort(); + String[] args = { + "--jpms", + "jetty.http.port=" + httpPort, + "jetty.httpConfig.port=" + httpsPort, + "jetty.ssl.port=" + httpsPort + }; + try (DistributionTester.Run run = distribution.start(args)) + { + assertTrue(run.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + httpPort + "/test/hello"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 99bfbb945e0..6779e9d3ee6 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -41,6 +41,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -63,7 +64,7 @@ public class DistributionTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port); @@ -75,6 +76,57 @@ public class DistributionTests extends AbstractDistributionTest } } + @Test + public void testQuickStartGenerationAndRun() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + String[] args1 = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=resources,server,http,webapp,deploy,jsp,servlet,servlets,quickstart" + }; + + try (DistributionTester.Run run1 = distribution.start(args1)) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion); + distribution.installWarFile(war, "test"); + + + try (DistributionTester.Run run2 = distribution.start("jetty.quickstart.mode=GENERATE")) + { + assertTrue(run2.awaitConsoleLogsFor("QuickStartGeneratorConfiguration:main: Generated", 10, TimeUnit.SECONDS)); + Path unpackedWebapp = distribution.getJettyBase().resolve("webapps").resolve("test"); + assertTrue(Files.exists(unpackedWebapp)); + Path webInf = unpackedWebapp.resolve("WEB-INF"); + assertTrue(Files.exists(webInf)); + Path quickstartWebXml = webInf.resolve("quickstart-web.xml"); + assertTrue(Files.exists(quickstartWebXml)); + assertNotEquals(0, Files.size(quickstartWebXml)); + + int port = distribution.freePort(); + + try (DistributionTester.Run run3 = distribution.start("jetty.http.port=" + port, "jetty.quickstart.mode=QUICKSTART")) + { + assertTrue(run3.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertThat(response.getContentAsString(), containsString("Hello")); + assertThat(response.getContentAsString(), not(containsString("<%"))); + } + } + } + } + @Test public void testSimpleWebAppWithJSP() throws Exception { @@ -100,7 +152,7 @@ public class DistributionTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp"); @@ -141,7 +193,7 @@ public class DistributionTests extends AbstractDistributionTest }; try (DistributionTester.Run run2 = distribution.start(args2)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp"); @@ -177,7 +229,7 @@ public class DistributionTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); HTTP2Client h2Client = new HTTP2Client(); startHttpClient(() -> new HttpClient(new HttpClientTransportOverHTTP2(h2Client))); @@ -229,7 +281,7 @@ public class DistributionTests extends AbstractDistributionTest try (DistributionTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile.toString())) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(() -> new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString()))); ContentResponse response = client.GET("http://localhost/test/index.jsp"); @@ -272,7 +324,7 @@ public class DistributionTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DynamicListenerTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DynamicListenerTests.java index c80ca9c0941..a16a5f475d2 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DynamicListenerTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DynamicListenerTests.java @@ -78,7 +78,7 @@ public class DynamicListenerTests int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/test/testservlet/foo"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java index 09cb1ef8478..c509d7e95d3 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java @@ -57,7 +57,7 @@ public class OsgiAppTests extends AbstractDistributionTest int port = distribution.freePort(); try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) { - assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); startHttpClient(); ContentResponse response = client.GET("http://localhost:" + port + "/test/info"); diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java index 64b462b2c3a..c7338965105 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java @@ -193,7 +193,7 @@ public class DeploymentErrorTest assertThat("ContextHandler.isAvailable", context.isAvailable(), is(false)); WebAppContext webapp = (WebAppContext)context; TrackedConfiguration trackedConfiguration = null; - for (Configuration webappConfig : webapp.getWebAppConfigurations()) + for (Configuration webappConfig : webapp.getConfigurations()) { if (webappConfig instanceof TrackedConfiguration) trackedConfiguration = (TrackedConfiguration)webappConfig; @@ -239,7 +239,7 @@ public class DeploymentErrorTest assertThat("ContextHandler.isAvailable", context.isAvailable(), is(false)); WebAppContext webapp = (WebAppContext)context; TrackedConfiguration trackedConfiguration = null; - for (Configuration webappConfig : webapp.getWebAppConfigurations()) + for (Configuration webappConfig : webapp.getConfigurations()) { if (webappConfig instanceof TrackedConfiguration) trackedConfiguration = (TrackedConfiguration)webappConfig; @@ -285,7 +285,7 @@ public class DeploymentErrorTest assertThat("ContextHandler.isAvailable", context.isAvailable(), is(false)); WebAppContext webapp = (WebAppContext)context; TrackedConfiguration trackedConfiguration = null; - for (Configuration webappConfig : webapp.getWebAppConfigurations()) + for (Configuration webappConfig : webapp.getConfigurations()) { if (webappConfig instanceof TrackedConfiguration) trackedConfiguration = (TrackedConfiguration)webappConfig; diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java new file mode 100644 index 00000000000..c569fe3d26d --- /dev/null +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java @@ -0,0 +1,395 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.test; + +import java.io.IOException; +import java.net.URI; +import java.nio.channels.Selector; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.ManagedSelector; +import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.log.StacklessLogging; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.Scheduler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FailedSelectorTest +{ + private static final Logger LOG = Log.getLogger(FailedSelectorTest.class); + private HttpClient client; + private Server server; + private StacklessLogging stacklessManagedSelector; + + @AfterEach + public void stopServerAndClient() throws Exception + { + server.stop(); + client.stop(); + stacklessManagedSelector.close(); + } + + @BeforeEach + public void startClient() throws Exception + { + HttpClientTransport transport = new HttpClientTransportOverHTTP(1); + QueuedThreadPool qtp = new QueuedThreadPool(); + qtp.setName("Client"); + qtp.setStopTimeout(1000); + client = new HttpClient(transport); + client.setExecutor(qtp); + + client.setIdleTimeout(1000); + client.setMaxConnectionsPerDestination(1); + client.setMaxRequestsQueuedPerDestination(1); + client.start(); + } + + public void startServer(Function customizeServerConsumer) throws Exception + { + stacklessManagedSelector = new StacklessLogging(ManagedSelector.class); + + server = new Server(); + server.setStopTimeout(1000); + server.setStopAtShutdown(true); + + ServerConnector connector = customizeServerConsumer.apply(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + context.addServlet(HelloServlet.class, "/hello"); + + ServletHolder closeHolder = new ServletHolder(new CloseSelectorServlet(connector)); + context.addServlet(closeHolder, "/selector/close"); + + HandlerList handlers = new HandlerList(); + handlers.addHandler(context); + handlers.addHandler(new DefaultHandler()); + + server.setHandler(handlers); + + server.start(); + } + + @Test + public void testRestartServerOnSelectFailure() throws Exception + { + CountDownLatch failedLatch = new CountDownLatch(1); + + startServer((server) -> + { + RestartSelectorCustomConnector connector = new RestartSelectorCustomConnector(server, 1, 1, new RestartServerTask(server, failedLatch)); + connector.setPort(0); + connector.setIdleTimeout(1000); + return connector; + }); + + // Request /hello + assertRequestHello(); + + // Request /selector/close + assertRequestSelectorClose(); + + // Wait for selectors to close from action above + assertTrue(failedLatch.await(2, TimeUnit.SECONDS)); + + // Request /hello + assertRequestHello(); + } + + @Test + public void testRestartSelectorOnSelectFailure() throws Exception + { + CountDownLatch failedLatch = new CountDownLatch(1); + + startServer((server) -> + { + RestartSelectorCustomConnector connector = new RestartSelectorCustomConnector(server, 1, 1, new RestartSelectorTask(failedLatch)); + connector.setPort(0); + connector.setIdleTimeout(1000); + return connector; + }); + + // Request /hello + assertRequestHello(); + + // Request /selector/close + assertRequestSelectorClose(); + + // Wait for selectors to close from action above + assertTrue(failedLatch.await(2, TimeUnit.SECONDS)); + + // Request /hello + assertRequestHello(); + } + + private void assertRequestSelectorClose() throws InterruptedException, ExecutionException, TimeoutException + { + URI dest = server.getURI().resolve("/selector/close"); + LOG.info("Requesting GET on {}", dest); + + ContentResponse response = client.newRequest(dest) + .method(HttpMethod.GET) + .header(HttpHeader.CONNECTION, "close") + .send(); + + assertThat(dest + " status", response.getStatus(), is(HttpStatus.OK_200)); + assertThat(dest + " response", response.getContentAsString(), startsWith("Closing selectors ")); + } + + private void assertRequestHello() throws InterruptedException, ExecutionException, TimeoutException + { + URI dest = server.getURI().resolve("/hello"); + LOG.info("Requesting GET on {}", dest); + ContentResponse response = client.newRequest(dest) + .method(HttpMethod.GET) + .header(HttpHeader.CONNECTION, "close") + .send(); + + assertThat(dest + " status", response.getStatus(), is(HttpStatus.OK_200)); + assertThat(dest + " response", response.getContentAsString(), startsWith("Hello ")); + } + + public static class HelloServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("utf-8"); + resp.getWriter().printf("Hello %s:%d%n", req.getRemoteAddr(), req.getRemotePort()); + } + } + + public static class CloseSelectorServlet extends HttpServlet + { + private static final int DELAY_MS = 500; + private ServerConnector connector; + private ScheduledExecutorService scheduledExecutorService; + + public CloseSelectorServlet(ServerConnector connector) + { + this.connector = connector; + scheduledExecutorService = Executors.newScheduledThreadPool(5); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("utf-8"); + resp.setHeader("Connection", "close"); + resp.getWriter().printf("Closing selectors in %,d ms%n", DELAY_MS); + scheduledExecutorService.schedule(new ForceCloseSelectorTask(connector), DELAY_MS, TimeUnit.MILLISECONDS); + } + } + + public static class RestartSelectorCustomConnector extends ServerConnector + { + private final Consumer onSelectFailConsumer; + + public RestartSelectorCustomConnector(Server server, int acceptors, int selectors, Consumer onSelectFailConsumer) + { + super(server, acceptors, selectors); + this.onSelectFailConsumer = onSelectFailConsumer; + } + + @Override + protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors) + { + return new ServerConnectorManager(executor, scheduler, selectors) + { + @Override + protected ManagedSelector newSelector(int id) + { + return new CustomManagedSelector(this, id, onSelectFailConsumer); + } + }; + } + } + + public static class CustomManagedSelector extends ManagedSelector + { + private final Set endpoints = ConcurrentHashMap.newKeySet(); + private final Consumer onSelectFailConsumer; + + public CustomManagedSelector(SelectorManager selectorManager, int id, Consumer onSelectFailConsumer) + { + super(selectorManager, id); + this.onSelectFailConsumer = onSelectFailConsumer; + } + + @Override + protected void endPointOpened(EndPoint endPoint) + { + super.endPointOpened(endPoint); + endpoints.add(endPoint); + } + + @Override + protected void endPointClosed(EndPoint endPoint) + { + super.endPointClosed(endPoint); + endpoints.remove(endPoint); + } + + @Override + protected void onSelectFailed(Throwable cause) + { + endpoints.forEach((endpoint) -> + { + if (endpoint.getConnection() != null) + { + IO.close(endpoint.getConnection()); + } + IO.close(endpoint); + }); + endpoints.clear(); + + new Thread(() -> onSelectFailConsumer.accept(this), "OnSelectFailedTask").start(); + } + } + + private static class RestartSelectorTask implements Consumer + { + private static final Logger LOG = Log.getLogger(RestartSelectorTask.class); + private final CountDownLatch latch; + + public RestartSelectorTask(CountDownLatch latch) + { + this.latch = latch; + } + + @Override + public void accept(CustomManagedSelector customManagedSelector) + { + try + { + customManagedSelector.stop(); + customManagedSelector.start(); + } + catch (Exception e) + { + LOG.warn(e); + } + finally + { + latch.countDown(); + } + } + } + + private static class RestartServerTask implements Consumer + { + private static final Logger LOG = Log.getLogger(RestartServerTask.class); + private final Server server; + private final CountDownLatch latch; + + public RestartServerTask(Server server, CountDownLatch latch) + { + this.server = server; + this.latch = latch; + } + + @Override + public void accept(CustomManagedSelector customManagedSelector) + { + try + { + server.stop(); + server.start(); + } + catch (Exception e) + { + LOG.warn(e); + } + finally + { + latch.countDown(); + } + } + } + + private static class ForceCloseSelectorTask implements Runnable + { + private static final Logger LOG = Log.getLogger(ForceCloseSelectorTask.class); + private final ServerConnector connector; + + public ForceCloseSelectorTask(ServerConnector connector) + { + this.connector = connector; + } + + @Override + public void run() + { + SelectorManager selectorManager = connector.getSelectorManager(); + Collection managedSelectors = selectorManager.getBeans(ManagedSelector.class); + for (ManagedSelector managedSelector : managedSelectors) + { + if (managedSelector instanceof CustomManagedSelector) + { + CustomManagedSelector customManagedSelector = (CustomManagedSelector)managedSelector; + Selector selector = customManagedSelector.getSelector(); + LOG.debug("Closing selector {}}", selector); + IO.close(selector); + } + } + } + } +} \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/jetty-logging.properties b/tests/test-integration/src/test/resources/jetty-logging.properties index 1531468c2c1..fdc5a51caba 100644 --- a/tests/test-integration/src/test/resources/jetty-logging.properties +++ b/tests/test-integration/src/test/resources/jetty-logging.properties @@ -1,3 +1,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.LEVEL=WARN #org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.websocket.LEVEL=DEBUG diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java index f1470a41104..c6f31c01639 100644 --- a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java +++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java @@ -24,6 +24,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.plus.webapp.EnvConfiguration; +import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -31,6 +34,7 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebDescriptor; import org.eclipse.jetty.xml.XmlConfiguration; import org.eclipse.jetty.xml.XmlParser.Node; @@ -47,6 +51,7 @@ public class QuickStartTest @Test public void testStandardTestWar() throws Exception { + //Generate the quickstart PreconfigureStandardTestWar.main(new String[]{}); WebDescriptor descriptor = new WebDescriptor(Resource.newResource("./target/test-standard-preconfigured/WEB-INF/quickstart-web.xml")); @@ -65,8 +70,12 @@ public class QuickStartTest Server server = new Server(0); - QuickStartWebApp webapp = new QuickStartWebApp(); - webapp.setMode(QuickStartConfiguration.Mode.AUTO); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration(), + new EnvConfiguration(), + new PlusConfiguration(), + new AnnotationConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setWar(war); webapp.setContextPath("/"); @@ -93,6 +102,7 @@ public class QuickStartTest @Test public void testSpecWar() throws Exception { + //Generate the quickstart xml PreconfigureSpecWar.main(new String[]{}); Path webXmlPath = MavenTestingUtils.getTargetPath().resolve("test-spec-preconfigured/WEB-INF/quickstart-web.xml"); @@ -114,8 +124,12 @@ public class QuickStartTest Server server = new Server(0); - QuickStartWebApp webapp = new QuickStartWebApp(); - webapp.setMode(QuickStartConfiguration.Mode.AUTO); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration(), + new EnvConfiguration(), + new PlusConfiguration(), + new AnnotationConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setWar(war); webapp.setContextPath("/"); @@ -142,6 +156,7 @@ public class QuickStartTest @Test public void testJNDIWar() throws Exception { + //Generate the quickstart PreconfigureJNDIWar.main(new String[]{}); WebDescriptor descriptor = new WebDescriptor(Resource.newResource("./target/test-jndi-preconfigured/WEB-INF/quickstart-web.xml")); @@ -160,8 +175,12 @@ public class QuickStartTest Server server = new Server(0); - QuickStartWebApp webapp = new QuickStartWebApp(); - webapp.setMode(QuickStartConfiguration.Mode.AUTO); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration(), + new EnvConfiguration(), + new PlusConfiguration(), + new AnnotationConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setWar(war); webapp.setContextPath("/"); diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java index df47e4c038a..4f02f21ee4f 100644 --- a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java +++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java @@ -18,8 +18,12 @@ package org.eclipse.jetty.quickstart; +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.plus.webapp.EnvConfiguration; +import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlConfiguration; public class Quickstart @@ -40,8 +44,12 @@ public class Quickstart Server server = new Server(8080); - QuickStartWebApp webapp = new QuickStartWebApp(); - webapp.setMode(QuickStartConfiguration.Mode.AUTO); + WebAppContext webapp = new WebAppContext(); + webapp.addConfiguration(new QuickStartConfiguration(), + new EnvConfiguration(), + new PlusConfiguration(), + new AnnotationConfiguration()); + webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART); webapp.setWar(war); webapp.setContextPath("/"); diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/AbstractSessionCacheTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/AbstractSessionCacheTest.java new file mode 100644 index 00000000000..c889b27bb85 --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/AbstractSessionCacheTest.java @@ -0,0 +1,513 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.session; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionEvent; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Base class for all tests on all flavours of SessionCache + * + */ +public abstract class AbstractSessionCacheTest +{ + + public static class TestSessionActivationListener implements HttpSessionActivationListener + { + public int passivateCalls = 0; + public int activateCalls = 0; + + @Override + public void sessionWillPassivate(HttpSessionEvent se) + { + ++passivateCalls; + } + + @Override + public void sessionDidActivate(HttpSessionEvent se) + { + ++activateCalls; + } + } + + public abstract AbstractSessionCacheFactory newSessionCacheFactory(int evictionPolicy, boolean saveOnCreate, + boolean saveOnInactiveEvict, boolean removeUnloadableSessions, + boolean flushOnResponseCommit); + + /** + * Test that a new Session object can be created from + * previously persisted data (SessionData). + */ + @Test + public void testNewSessionFromPersistedData() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); + cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(true);//fake passivation + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + + context.start(); + + long now = System.currentTimeMillis(); + //fake persisted data + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + Session session = cache.newSession(data); + assertNotNull(session); + assertEquals("1234", session.getId()); + } + + + /** + * Test that the cache can load from the SessionDataStore + */ + @Test + public void testGetSessionNotInCache() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //put session data into the store + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + store.store("1234", data); + + assertFalse(cache.contains("1234")); + + Session session = cache.get("1234"); + assertEquals(1, session.getRequests()); + assertNotNull(session); + assertEquals("1234", session.getId()); + assertEquals(now - 20, session.getCreationTime()); + } + + @Test + public void testCommit() throws Exception + { + //Test state of session with call to commit + + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + //flushOnResponseCommit is true + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, true); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //Mimic various states of a session when a response is about + //to be committed: + + //call commit: session has not changed, should not be written + store._numSaves.set(0); //clear save counter + Session session = createUnExpiredSession(cache, store, "1234"); + cache.add("1234", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + + //call commit: session has changed, should be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "456"); + cache.add("456", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + session.setAttribute("foo", "bar"); + commitAndCheckSaveState(cache, store, session, true, true, false, false, 0, 1); + + //call commit: only the metadata has changed will not be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "678"); + cache.add("678", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + session.getSessionData().calcAndSetExpiry(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)); + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + + //Test again with a savePeriod set - as savePeriod only + //affects saving when the session is not dirty, the savePeriod + //should not affect whether or not the session is saved on call + //to commit + store.setSavePeriodSec(60); + + //call commit: session has not changed, should not be written anyway + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "890"); + cache.add("890", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + + //call commit: session has changed so session must be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "012"); + cache.add("012", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + session.setAttribute("foo", "bar"); + commitAndCheckSaveState(cache, store, session, true, true, false, false, 0, 1); + + //call commit: only the metadata has changed will not be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "234"); + session.getSessionData().setMetaDataDirty(true); + cache.add("234", session); + session.getSessionData().setLastSaved(100);//simulate previously saved + session.getSessionData().calcAndSetExpiry(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)); + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + } + + @Test + public void testCommitAndRelease() throws Exception + { + //test what happens with various states of a session when commit + //is called before release + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + //flushOnResponseCommit is true + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, true); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //Mimic various states of a session when a response is about + //to be committed: + + //call commit: session has not changed, should not be written + Session session = createUnExpiredSession(cache, store, "1234"); + cache.add("1234", session); + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + //call release: session has not changed, but metadata has, should be written + cache.release("1234", session); + assertEquals(1, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertFalse(session.getSessionData().isMetaDataDirty()); + + //call commit: session has changed, should be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "456"); + cache.add("456", session); + session.setAttribute("foo", "bar"); + session.getSessionData().setLastSaved(100);//simulate not "new" session, ie has been previously saved + commitAndCheckSaveState(cache, store, session, true, true, false, false, 0, 1); + //call release: session not dirty but release changes metadata, so it will be saved + cache.release("456", session); + assertEquals(2, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertFalse(session.getSessionData().isMetaDataDirty()); + + //call commit: only the metadata has changed will not be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "678"); + session.getSessionData().calcAndSetExpiry(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)); + session.getSessionData().setLastSaved(100); //simulate session not being "new", ie never previously saved + cache.add("678", session); + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + //call release: the metadata is dirty session should be written + cache.release("678", session); + assertEquals(1, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertFalse(session.getSessionData().isMetaDataDirty()); + + //Test again with a savePeriod set - only save if time last saved exceeds 60sec + store.setSavePeriodSec(60); + + //call commit: session has not changed, should not be written anyway + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "890"); + cache.add("890", session); + session.getSessionData().setLastSaved(100); //simulate last save long time ago + session.getSessionData().setMetaDataDirty(false); + commitAndCheckSaveState(cache, store, session, false, false, false, false, 0, 0); + //call release: not dirty but release sets metadata true, plus save period exceeded so write + cache.release("1234", session); + assertEquals(1, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertFalse(session.getSessionData().isMetaDataDirty()); + + //call commit: session has changed so session must be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "012"); + cache.add("012", session); + session.getSessionData().setLastSaved(100);//simulate previously saved session + session.setAttribute("foo", "bar"); + session.getSessionData().setMetaDataDirty(false); + commitAndCheckSaveState(cache, store, session, true, false, false, false, 0, 1); + //call release: not dirty, release sets metadirty true (recalc expiry) but previous save too recent to exceed save period --> no write + cache.release("012", session); + assertEquals(1, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertTrue(session.getSessionData().isMetaDataDirty()); + + //call commit: only the metadata has changed will not be written + store._numSaves.set(0); //clear save counter + session = createUnExpiredSession(cache, store, "234"); + session.getSessionData().calcAndSetExpiry(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)); + session.getSessionData().setLastSaved(System.currentTimeMillis());//simulate session last saved recently + commitAndCheckSaveState(cache, store, session, false, true, false, true, 0, 0); + //call release: not dirty, release sets metadirty true (recalc expiry) but not within saveperiod so skip write + cache.release("1234", session); + assertEquals(0, store._numSaves.get()); + assertFalse(session.getSessionData().isDirty()); + assertTrue(session.getSessionData().isMetaDataDirty()); + } + + /** + * Test the exist method. + */ + @Test + public void testExists() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = (SessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test one that doesn't exist at all + assertFalse(cache.exists("1234")); + + //test one that only exists in the store + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + store.store("1234", data); + assertTrue(cache.exists("1234")); + + //test one that exists in the cache also + Session session = cache.newSession(data); + cache.add("1234", session); + assertTrue(cache.exists("1234")); + } + + /** + * Test the delete method. + */ + @Test + public void testDelete() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, true, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test remove non-existent session + Session session = cache.delete("1234"); + assertNull(session); + + //test remove of existing session in store only + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + store.store("1234", data); + session = cache.delete("1234"); + assertNotNull(session); + assertFalse(store.exists("1234")); + assertFalse(cache.contains("1234")); + + //test remove of session in both store and cache + session = cache.newSession(null, "1234",now - 20, TimeUnit.MINUTES.toMillis(10));//saveOnCreate ensures write to store + cache.add("1234", session); + assertTrue(store.exists("1234")); + assertTrue(cache.contains("1234")); + session = cache.delete("1234"); + assertNotNull(session); + assertFalse(store.exists("1234")); + assertFalse(cache.contains("1234")); + } + + @Test + public void testExpiration() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test no candidates, no data in store + Set result = cache.checkExpiration(Collections.emptySet()); + assertTrue(result.isEmpty()); + + //test candidates that are in the cache and NOT expired + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); + Session session = cache.newSession(data); + cache.add("1234", session); + cache.release("1234", session); + assertTrue(cache.exists("1234")); + result = cache.checkExpiration(Collections.singleton("1234")); + assertTrue(result.isEmpty()); + + //test candidates that are in the cache AND expired + data.setExpiry(1); + result = cache.checkExpiration(Collections.singleton("1234")); + assertEquals(1, result.size()); + assertEquals("1234", result.iterator().next()); + + //test candidates that are not in the cache + SessionData data2 = store.newSessionData("567", now - 50, now - 40, now - 30, TimeUnit.MINUTES.toMillis(10)); + data2.setExpiry(1); + store.store("567", data2); + + result = cache.checkExpiration(Collections.emptySet()); + assertThat(result, containsInAnyOrder("1234", "567")); + } + + @Test + public void testSaveOnCreateTrue() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, true, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + long now = System.currentTimeMillis(); + cache.newSession(null, "1234", now, TimeUnit.MINUTES.toMillis(10)); + assertTrue(store.exists("1234")); + } + + @Test + public void testSaveOnCreateFalse() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + long now = System.currentTimeMillis(); + cache.newSession(null, "1234", now, TimeUnit.MINUTES.toMillis(10)); + assertFalse(store.exists("1234")); + } + + public void commitAndCheckSaveState(SessionCache cache, TestSessionDataStore store, Session session, + boolean expectedBeforeDirty, boolean expectedBeforeMetaDirty, + boolean expectedAfterDirty, boolean expectedAfterMetaDirty, + int expectedBeforeNumSaves, int expectedAfterNumSaves) + throws Exception + { + assertEquals(expectedBeforeDirty, session.getSessionData().isDirty()); + assertEquals(expectedBeforeMetaDirty, session.getSessionData().isMetaDataDirty()); + assertEquals(expectedBeforeNumSaves, store._numSaves.get()); + cache.commit(session); + assertEquals(expectedAfterDirty, session.getSessionData().isDirty()); + assertEquals(expectedAfterMetaDirty, session.getSessionData().isMetaDataDirty()); + assertEquals(expectedAfterNumSaves, store._numSaves.get()); + } + + public Session createUnExpiredSession(SessionCache cache, SessionDataStore store, String id) + { + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData(id, now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); + return cache.newSession(data); + } +} diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DefaultSessionCacheTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DefaultSessionCacheTest.java index 21b9bc44b83..46a812a5976 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DefaultSessionCacheTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DefaultSessionCacheTest.java @@ -18,25 +18,19 @@ package org.eclipse.jetty.server.session; -import java.util.Collections; import java.util.Random; -import java.util.Set; import java.util.concurrent.TimeUnit; + import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionEvent; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -44,25 +38,21 @@ import static org.junit.jupiter.api.Assertions.fail; /** * DefaultSessionCacheTest */ -public class DefaultSessionCacheTest +public class DefaultSessionCacheTest extends AbstractSessionCacheTest { - public static class TestSessionActivationListener implements HttpSessionActivationListener + @Override + public AbstractSessionCacheFactory newSessionCacheFactory(int evictionPolicy, boolean saveOnCreate, + boolean saveOnInactiveEvict, boolean removeUnloadableSessions, + boolean flushOnResponseCommit) { - public int passivateCalls = 0; - public int activateCalls = 0; - - @Override - public void sessionWillPassivate(HttpSessionEvent se) - { - ++passivateCalls; - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) - { - ++activateCalls; - } + DefaultSessionCacheFactory factory = new DefaultSessionCacheFactory(); + factory.setEvictionPolicy(evictionPolicy); + factory.setSaveOnCreate(saveOnCreate); + factory.setSaveOnInactiveEvict(saveOnInactiveEvict); + factory.setRemoveUnloadableSessions(removeUnloadableSessions); + factory.setFlushOnResponseCommit(flushOnResponseCommit); + return factory; } @Test @@ -72,9 +62,8 @@ public class DefaultSessionCacheTest int inactivePeriod = 20; int scavengePeriod = 3; - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setSaveOnCreate(true); //ensures that a session is persisted as soon as it is created - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); ServletContextHandler contextHandler = server.addContext("/test"); @@ -203,8 +192,7 @@ public class DefaultSessionCacheTest context.setContextPath("/test"); context.setServer(server); - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); TestSessionDataStore store = new TestSessionDataStore(true);//fake passivation @@ -234,38 +222,6 @@ public class DefaultSessionCacheTest assertEquals(1, listener.activateCalls); } - /** - * Test that a new Session object can be created from - * previously persisted data (SessionData). - */ - @Test - public void testNewSessionFromPersistedData() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(true);//fake passivation - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - - context.start(); - - long now = System.currentTimeMillis(); - //fake persisted data - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - Session session = cache.newSession(data); - assertNotNull(session); - assertEquals("1234", session.getId()); - } - /** * Test that a session id can be renewed. */ @@ -318,8 +274,7 @@ public class DefaultSessionCacheTest context.setContextPath("/test"); context.setServer(server); - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); TestSessionDataStore store = new TestSessionDataStore(); @@ -338,42 +293,6 @@ public class DefaultSessionCacheTest assertTrue(((AbstractSessionCache)cache).contains("1234")); } - /** - * Test that the cache can load from the SessionDataStore - */ - @Test - public void testGetSessionNotInCache() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - //put session data into the store - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - store.store("1234", data); - - assertFalse(cache.contains("1234")); - - Session session = cache.get("1234"); - assertEquals(1, session.getRequests()); - assertNotNull(session); - assertEquals("1234", session.getId()); - assertEquals(now - 20, session.getCreationTime()); - } - @Test public void testAdd() throws Exception @@ -384,8 +303,7 @@ public class DefaultSessionCacheTest context.setContextPath("/test"); context.setServer(server); - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); TestSessionDataStore store = new TestSessionDataStore(); @@ -418,8 +336,7 @@ public class DefaultSessionCacheTest context.setContextPath("/test"); context.setServer(server); - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); TestSessionDataStore store = new TestSessionDataStore(); @@ -458,9 +375,8 @@ public class DefaultSessionCacheTest context.setContextPath("/test"); context.setServer(server); - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = (SessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); TestSessionDataStore store = new TestSessionDataStore(); cache.setSessionDataStore(store); @@ -478,139 +394,6 @@ public class DefaultSessionCacheTest assertTrue(cache.contains("1234")); } - /** - * Test the exist method. - */ - @Test - public void testExists() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - //test one that doesn't exist at all - assertFalse(cache.exists("1234")); - - //test one that only exists in the store - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - store.store("1234", data); - assertTrue(cache.exists("1234")); - - //test one that exists in the cache also - Session session = cache.newSession(data); - cache.add("1234", session); - assertTrue(cache.exists("1234")); - } - - /** - * Test the delete method. - */ - @Test - public void testDelete() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - cacheFactory.setSaveOnCreate(true); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - //test remove non-existent session - Session session = cache.delete("1234"); - assertNull(session); - - //test remove of existing session in store only - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - store.store("1234", data); - session = cache.delete("1234"); - assertNotNull(session); - assertFalse(store.exists("1234")); - assertFalse(cache.contains("1234")); - - //test remove of session in both store and cache - session = cache.newSession(null, "1234",now - 20, TimeUnit.MINUTES.toMillis(10));//saveOnCreate ensures write to store - cache.add("1234", session); - assertTrue(store.exists("1234")); - assertTrue(cache.contains("1234")); - session = cache.delete("1234"); - assertNotNull(session); - assertFalse(store.exists("1234")); - assertFalse(cache.contains("1234")); - } - - @Test - public void testExpiration() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - //test no candidates, no data in store - Set result = cache.checkExpiration(Collections.emptySet()); - assertTrue(result.isEmpty()); - - //test candidates that are in the cache and NOT expired - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); - Session session = cache.newSession(data); - cache.add("1234", session); - cache.release("1234", session); - assertTrue(cache.exists("1234")); - result = cache.checkExpiration(Collections.singleton("1234")); - assertTrue(result.isEmpty()); - - //test candidates that are in the cache AND expired - data.setExpiry(1); - result = cache.checkExpiration(Collections.singleton("1234")); - assertEquals(1, result.size()); - assertEquals("1234", result.iterator().next()); - - //test candidates that are not in the cache - SessionData data2 = store.newSessionData("567", now - 50, now - 40, now - 30, TimeUnit.MINUTES.toMillis(10)); - data2.setExpiry(1); - store.store("567", data2); - - result = cache.checkExpiration(Collections.emptySet()); - assertThat(result, containsInAnyOrder("1234", "567")); - } - @Test public void testCheckInactiveSession() throws Exception @@ -725,54 +508,4 @@ public class DefaultSessionCacheTest SessionData retrieved = store.load("1234"); assertEquals(accessed, retrieved.getAccessed()); //check that we persisted the session before we evicted } - - @Test - public void testSaveOnCreateTrue() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - cacheFactory.setSaveOnCreate(true); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - long now = System.currentTimeMillis(); - cache.newSession(null, "1234", now, TimeUnit.MINUTES.toMillis(10)); - assertTrue(store.exists("1234")); - } - - @Test - public void testSaveOnCreateFalse() - throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); - cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); - cacheFactory.setSaveOnCreate(false); - DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - long now = System.currentTimeMillis(); - cache.newSession(null, "1234", now, TimeUnit.MINUTES.toMillis(10)); - assertFalse(store.exists("1234")); - } } diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java index c66713e9f72..1074f0f8cd9 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java @@ -34,11 +34,13 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StacklessLogging; -import org.eclipse.jetty.util.thread.Locker.Lock; +import org.eclipse.jetty.util.thread.AutoLock; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -94,7 +96,7 @@ public class IdleSessionTest ContentResponse response = client.GET(url + "?action=init"); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); - assertTrue(sessionCookie != null); + assertNotNull(sessionCookie); //ensure request has finished being handled synchronizer.await(5, TimeUnit.SECONDS); @@ -148,7 +150,7 @@ public class IdleSessionTest response = client.GET(url + "?action=init"); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); sessionCookie = response.getHeaders().get("Set-Cookie"); - assertTrue(sessionCookie != null); + assertNotNull(sessionCookie); id = TestServer.extractSessionId(sessionCookie); //ensure request has finished being handled @@ -220,7 +222,7 @@ public class IdleSessionTest ContentResponse response = client.GET(url + "?action=init"); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); - assertTrue(sessionCookie != null); + assertNotNull(sessionCookie); //ensure request has finished being handled synchronizer.await(5, TimeUnit.SECONDS); @@ -265,7 +267,7 @@ public class IdleSessionTest response = client.GET(url + "?action=init"); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); sessionCookie = response.getHeaders().get("Set-Cookie"); - assertTrue(sessionCookie != null); + assertNotNull(sessionCookie); id = TestServer.extractSessionId(sessionCookie); //ensure request has finished being handled @@ -317,7 +319,7 @@ public class IdleSessionTest session.setAttribute("value", 1); originalId = session.getId(); Session s = (Session)session; - try (Lock lock = s.lock()) + try (AutoLock lock = s.lock()) { assertTrue(s.isResident()); } @@ -326,21 +328,21 @@ public class IdleSessionTest else if ("test".equals(action)) { HttpSession session = request.getSession(false); - assertTrue(session != null); - assertTrue(originalId.equals(session.getId())); + assertNotNull(session); + assertEquals(originalId, session.getId()); Session s = (Session)session; - try (Lock lock = s.lock();) + try (AutoLock lock = s.lock()) { assertTrue(s.isResident()); } Integer v = (Integer)session.getAttribute("value"); - session.setAttribute("value", v.intValue() + 1); + session.setAttribute("value", v + 1); _session = session; } else if ("testfail".equals(action)) { HttpSession session = request.getSession(false); - assertTrue(session == null); + assertNull(session); _session = session; } } diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/NullSessionCacheTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/NullSessionCacheTest.java index 5e63b62101b..6dce6c5e0ef 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/NullSessionCacheTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/NullSessionCacheTest.java @@ -18,129 +18,37 @@ package org.eclipse.jetty.server.session; -import java.io.Serializable; import java.util.concurrent.TimeUnit; -import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionEvent; - import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; /** * NullSessionCacheTest */ -public class NullSessionCacheTest -{ - public static class SerializableTestObject implements Serializable, HttpSessionActivationListener +public class NullSessionCacheTest extends AbstractSessionCacheTest +{ + @Override + public AbstractSessionCacheFactory newSessionCacheFactory(int evictionPolicy, boolean saveOnCreate, + boolean saveOnInactiveEvict, boolean removeUnloadableSessions, + boolean flushOnResponseCommit) { - int count; - static int passivates = 0; - static int activates = 0; - - public SerializableTestObject(int i) - { - count = i; - } - - @Override - public void sessionWillPassivate(HttpSessionEvent se) - { - //should never be called, as we are replaced with the - //non-serializable object and thus passivate will be called on that - ++passivates; - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) - { - ++activates; - //remove myself, replace with something serializable - se.getSession().setAttribute("pv", new TestObject(count)); - } - } - - public static class TestObject implements HttpSessionActivationListener - { - int i; - static int passivates = 0; - static int activates = 0; - - public TestObject(int j) - { - i = j; - } - - @Override - public void sessionWillPassivate(HttpSessionEvent se) - { - ++passivates; - //remove myself, replace with something serializable - se.getSession().setAttribute("pv", new SerializableTestObject(i)); - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) - { - //this should never be called because we replace ourselves during passivation, - //so it is the SerializableTestObject that is activated instead - ++activates; - } - } - - @Test - public void testWritesWithPassivation() throws Exception - { - //Test that a session that is in the process of being saved cannot cause - //another save via a passivation listener - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory(); - cacheFactory.setWriteThroughMode(NullSessionCache.WriteThroughMode.ALWAYS); - - NullSessionCache cache = (NullSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(true); //pretend to passivate - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - - context.start(); - - //make a session - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); - Session session = cache.newSession(null, data); //mimic a request making a session - cache.add("1234", session); - //at this point the session should not be saved to the store - assertEquals(0, store._numSaves.get()); - - //set an attribute that is not serializable, should cause a save - TestObject obj = new TestObject(1); - session.setAttribute("pv", obj); - assertTrue(cache._listener._sessionsBeingWritten.isEmpty()); - assertTrue(store.exists("1234")); - assertEquals(1, store._numSaves.get()); - assertEquals(1, TestObject.passivates); - assertEquals(0, TestObject.activates); - assertEquals(1, SerializableTestObject.activates); - assertEquals(0, SerializableTestObject.passivates); + NullSessionCacheFactory factory = new NullSessionCacheFactory(); + factory.setSaveOnCreate(saveOnCreate); + factory.setRemoveUnloadableSessions(removeUnloadableSessions); + factory.setFlushOnResponseCommit(flushOnResponseCommit); + return factory; } @Test - public void testChangeWriteThroughMode() throws Exception + public void testShutdownWithSessionStore() + throws Exception { Server server = new Server(); @@ -148,157 +56,45 @@ public class NullSessionCacheTest context.setContextPath("/test"); context.setServer(server); - NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory(); + AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); - NullSessionCache cache = (NullSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); + TestSessionDataStore store = new TestSessionDataStore(true);//fake passivation cache.setSessionDataStore(store); context.getSessionHandler().setSessionCache(cache); - - assertEquals(NullSessionCache.WriteThroughMode.ON_EXIT, cache.getWriteThroughMode()); - assertNull(cache._listener); - - //change mode to NEW - cache.setWriteThroughMode(NullSessionCache.WriteThroughMode.NEW); - assertEquals(NullSessionCache.WriteThroughMode.NEW, cache.getWriteThroughMode()); - assertNotNull(cache._listener); - assertEquals(1, context.getSessionHandler()._sessionAttributeListeners.size()); - assertTrue(context.getSessionHandler()._sessionAttributeListeners.contains(cache._listener)); - - //change mode to ALWAYS from NEW, listener should remain - NullSessionCache.WriteThroughAttributeListener old = cache._listener; - cache.setWriteThroughMode(NullSessionCache.WriteThroughMode.ALWAYS); - assertEquals(NullSessionCache.WriteThroughMode.ALWAYS, cache.getWriteThroughMode()); - assertNotNull(cache._listener); - assertSame(old,cache._listener); - assertEquals(1, context.getSessionHandler()._sessionAttributeListeners.size()); - - //check null is same as ON_EXIT - cache.setWriteThroughMode(null); - assertEquals(NullSessionCache.WriteThroughMode.ON_EXIT, cache.getWriteThroughMode()); - assertNull(cache._listener); - assertEquals(0, context.getSessionHandler()._sessionAttributeListeners.size()); - - //change to ON_EXIT - cache.setWriteThroughMode(NullSessionCache.WriteThroughMode.ON_EXIT); - assertEquals(NullSessionCache.WriteThroughMode.ON_EXIT, cache.getWriteThroughMode()); - assertNull(cache._listener); - assertEquals(0, context.getSessionHandler()._sessionAttributeListeners.size()); - } - - @Test - public void testWriteThroughAlways() throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory(); - cacheFactory.setWriteThroughMode(NullSessionCache.WriteThroughMode.ALWAYS); - - NullSessionCache cache = (NullSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); context.start(); - //make a session + //put a session in the cache and store long now = System.currentTimeMillis(); SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); - Session session = cache.newSession(null, data); //mimic a request making a session + Session session = cache.newSession(data); + TestSessionActivationListener listener = new TestSessionActivationListener(); cache.add("1234", session); - //at this point the session should not be saved to the store - assertEquals(0, store._numSaves.get()); - - //check each call to set attribute results in a store - session.setAttribute("colour", "blue"); - assertTrue(store.exists("1234")); - assertEquals(1, store._numSaves.get()); - - //mimic releasing the session after the request is finished - cache.release("1234", session); - assertTrue(store.exists("1234")); + //cache never contains the session assertFalse(cache.contains("1234")); - assertEquals(2, store._numSaves.get()); - - //simulate a new request using the previously created session - //the session should not now be new - session = cache.get("1234"); //get the session again - session.access(now); //simulate a request - session.setAttribute("spin", "left"); - assertTrue(store.exists("1234")); - assertEquals(3, store._numSaves.get()); - cache.release("1234", session); //finish with the session + session.setAttribute("aaa", listener); + //write session out on release + cache.release("1234", session); + assertEquals(1, store._numSaves.get()); + assertEquals(1, listener.passivateCalls); + assertEquals(0, listener.activateCalls); //NullSessionCache always evicts on release, so never reactivates - assertFalse(session.isResident()); + assertTrue(store.exists("1234")); + //cache never contains session + assertFalse(cache.contains("1234")); + + context.stop(); //calls shutdown + + //session should still exist in store + assertTrue(store.exists("1234")); + //cache never contains the session + assertFalse(cache.contains("1234")); + //shutdown does not save session + assertEquals(1, listener.passivateCalls); + assertEquals(0, listener.activateCalls); } - @Test - public void testWriteThroughNew() throws Exception - { - Server server = new Server(); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/test"); - context.setServer(server); - - NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory(); - cacheFactory.setWriteThroughMode(NullSessionCache.WriteThroughMode.NEW); - - NullSessionCache cache = (NullSessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); - - TestSessionDataStore store = new TestSessionDataStore(); - cache.setSessionDataStore(store); - context.getSessionHandler().setSessionCache(cache); - context.start(); - - //make a session - long now = System.currentTimeMillis(); - SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); - data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); - Session session = cache.newSession(null, data); //mimic a request making a session - cache.add("1234", session); - //at this point the session should not be saved to the store - assertEquals(0, store._numSaves.get()); - assertTrue(session.isNew()); - - //check each call to set attribute results in a store while the session is new - session.setAttribute("colour", "blue"); - assertTrue(store.exists("1234")); - assertEquals(1, store._numSaves.get()); - session.setAttribute("charge", "positive"); - assertEquals(2, store._numSaves.get()); - - //mimic releasing the session after the request is finished - cache.release("1234", session); - assertTrue(store.exists("1234")); - assertFalse(cache.contains("1234")); - assertEquals(3, store._numSaves.get()); //even if the session isn't dirty, we will save the access time - - - //simulate a new request using the previously created session - //the session should not now be new, so setAttribute should - //not result in a save - session = cache.get("1234"); //get the session again - session.access(now); //simulate a request - assertFalse(session.isNew()); - assertEquals(3, store._numSaves.get()); - session.setAttribute("spin", "left"); - assertTrue(store.exists("1234")); - assertEquals(3, store._numSaves.get()); - session.setAttribute("flavor", "charm"); - assertEquals(3, store._numSaves.get()); - cache.release("1234", session); //finish with the session - assertEquals(4, store._numSaves.get());//release session should write it out - assertFalse(session.isResident()); - } - @Test public void testNotCached() throws Exception { @@ -324,7 +120,7 @@ public class NullSessionCacheTest data.setExpiry(now + TimeUnit.DAYS.toMillis(1)); Session session = cache.newSession(null, data); //mimic a request making a session cache.add("1234", session); - assertFalse(cache.contains("1234"));//null cache doesn't actually store the session + assertFalse(cache.contains("1234"));//null cache doesn't actually retain the session //mimic releasing the session after the request is finished cache.release("1234", session); @@ -336,7 +132,104 @@ public class NullSessionCacheTest session.access(now); //simulate a request cache.release("1234", session); //finish with the session assertFalse(cache.contains("1234")); - assertFalse(session.isResident()); } + + /** + * Test contains method. + */ + @Test + public void testContains() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = (SessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test one that doesn't exist + assertFalse(cache.contains("1234")); + + //test one that exists + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + Session session = cache.newSession(data); + cache.add("1234", session); + assertFalse(cache.contains("1234")); + } + + /** + * Test the exist method. + */ + @Test + public void testExists() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false); + SessionCache cache = (SessionCache)cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test one that doesn't exist anywhere at all + assertFalse(cache.exists("1234")); + + //test one that only exists in the store + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + store.store("1234", data); + assertTrue(cache.exists("1234")); + } + + /** + * Test the delete method. + */ + @Test + public void testDelete() + throws Exception + { + Server server = new Server(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + context.setServer(server); + + SessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, true, false, false, false); + SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler()); + + TestSessionDataStore store = new TestSessionDataStore(); + cache.setSessionDataStore(store); + context.getSessionHandler().setSessionCache(cache); + context.start(); + + //test remove non-existent session + Session session = cache.delete("1234"); + assertNull(session); + + //test remove of existing session in store only + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("1234", now - 20, now - 10, now - 20, TimeUnit.MINUTES.toMillis(10)); + store.store("1234", data); + session = cache.delete("1234"); + assertNull(session); //NullSessionCache never returns the session that was removed from the cache because it was never in the cache! + assertFalse(store.exists("1234")); + assertFalse(cache.contains("1234")); + } } diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionEvictionFailureTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionEvictionFailureTest.java index d6eac23dc6f..237d74bd18e 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionEvictionFailureTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionEvictionFailureTest.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * SessionEvictionFailureTest @@ -192,6 +193,7 @@ public class SessionEvictionFailureTest // Make another request to see if the session is still in the cache and can be used, //allow it to be saved this time + assertTrue(context.getSessionHandler().getSessionCache().contains(TestServer.extractSessionId(sessionCookie))); Request request = client.newRequest(url + "?action=test"); response = request.send(); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/modules/demo.mod b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/modules/demo.mod index 8bb82d2cfcd..96301d9b07d 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/modules/demo.mod +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/modules/demo.mod @@ -28,3 +28,5 @@ org.eclipse.jetty.websocket.jsr356=false # Create and configure the test realm jetty.demo.realm=etc/realm.properties +# JDBC needed by test-jndi and test-spec +--module=jdbc diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java index 0aead59d67c..e7a99fa2fd8 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java @@ -295,19 +295,19 @@ public class AnnotationTest extends HttpServlet out.println("private Double minAmount;"); out.println(""); if (maxAmount == null) - out.println("

      Result: " + envResult + ": FAIL"); + out.println("

      Result: " + envResult + ": FAIL"); else out.println("

      Result: " + envResult + ": " + (maxAmount.compareTo(55D) == 0 ? " PASS" : " FAIL") + ""); out.println("
      JNDI Lookup Result: " + envLookupResult + ""); if (minAmount == null) - out.println("

      Result: " + envResult2 + ": FAIL"); + out.println("

      Result: " + envResult2 + ": FAIL"); else out.println("
      Result: " + envResult2 + ": " + (minAmount.compareTo(0.99D) == 0 ? " PASS" : " FAIL") + ""); out.println("
      JNDI Lookup Result: " + envLookupResult2 + ""); if (avgAmount == null) - out.println("

      Result: " + envResult3 + ": FAIL"); + out.println("

      Result: " + envResult3 + ": FAIL"); else out.println("
      Result: " + envResult3 + ": " + (avgAmount.compareTo(1.25D) == 0 ? " PASS" : " FAIL") + ""); out.println("
      JNDI Lookup Result: " + envLookupResult3 + "

      ");