diff --git a/build.gradle b/build.gradle index f04457456..2cea34857 100755 --- a/build.gradle +++ b/build.gradle @@ -1346,24 +1346,31 @@ project(':wres-tasker') { // Easy byte[] to hex conversion implementation 'commons-codec:commons-codec:1.17.1' + //Jakarta servlet + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + // Server library to run a web server: - implementation 'org.eclipse.jetty:jetty-server:11.0.24' + implementation 'org.eclipse.jetty:jetty-server:12.0.16' + + // Servlet library is now tied to ee10. + implementation 'org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16' + implementation 'org.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.16' // Support HTTP/2 - implementation 'org.eclipse.jetty.http2:http2-server:11.0.24' + implementation 'org.eclipse.jetty.http2:jetty-http2-server:12.0.16' // Support ALPN - implementation 'org.eclipse.jetty:jetty-alpn-java-server:11.0.24' + implementation 'org.eclipse.jetty:jetty-alpn-java-server:12.0.16' // Servlet container library to run a web application with: - implementation 'org.eclipse.jetty:jetty-webapp:11.0.24' + implementation 'org.eclipse.jetty.ee10:jetty-ee10-webapp:12.0.16' // Needed at compile-time to reference ServletContainer.class and // DefaultServlet.class - implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:3.1.8' + implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:3.1.9' // To handle multipart post - implementation 'org.glassfish.jersey.media:jersey-media-multipart:3.1.8' + implementation 'org.glassfish.jersey.media:jersey-media-multipart:3.1.9' // To temporarily cache/store-in-RAM job metadata when redis unavailable implementation('com.github.ben-manes.caffeine:caffeine:3.1.8') { @@ -1408,11 +1415,11 @@ project(':wres-tasker') { exclude group: 'edu.washington.cs.types.checker', module: 'checker-framework' } - runtimeOnly 'org.glassfish.jersey.core:jersey-server:3.1.8' + runtimeOnly 'org.glassfish.jersey.core:jersey-server:3.1.9' // Needed to prevent "java.lang.IllegalStateException: InjectionManagerFactory not found." // Thanks, https://luohuahuang.org/2017/11/13/jersey-illegalstateexception-injectionmanagerfactory/ - runtimeOnly 'org.glassfish.jersey.inject:jersey-hk2:3.1.8' + runtimeOnly 'org.glassfish.jersey.inject:jersey-hk2:3.1.9' testImplementation 'junit:junit:4.13.2' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.11.3' @@ -1884,15 +1891,22 @@ dependencies { // For Java 9+ compatibility: implementation 'jakarta.xml.bind:jakarta.xml.bind-api:3.0.1' + //Jakarta servlet + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + + //Jetty servlet stuff now tied to ee10, with the move to Jetty 12. + implementation 'org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16' + implementation 'org.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.16' + // Servlet container library to run a web application with: implementation 'org.eclipse.jetty:jetty-webapp:11.0.24' // Needed at compile-time to reference ServletContainer.class and // DefaultServlet.class - implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:3.1.8' + implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:3.1.9' // Jetty server - implementation 'org.eclipse.jetty:jetty-server:11.0.24' + implementation 'org.eclipse.jetty:jetty-server:12.0.16' implementation('com.github.ben-manes.caffeine:caffeine:3.1.8') { // Not used at runtime, bloat @@ -1924,18 +1938,19 @@ dependencies { // JCIP annotations compileOnly group: 'net.jcip', name: 'jcip-annotations', version: '1.0' - runtimeOnly 'org.glassfish.jersey.containers:jersey-container-servlet:3.1.8' - runtimeOnly 'org.glassfish.jersey.core:jersey-server:3.1.8' + runtimeOnly 'org.glassfish.jersey.containers:jersey-container-servlet:3.1.9' + runtimeOnly 'org.glassfish.jersey.core:jersey-server:3.1.9' // currently locked at this version, check this ticket for more information // https://vlab.***REMOVED***/redmine/issues/124200 - runtimeOnly 'org.glassfish.jersey.containers:jersey-container-jetty-http:3.1.3' - runtimeOnly 'org.glassfish.jersey.inject:jersey-hk2:3.1.8' + runtimeOnly 'org.glassfish.jersey.containers:jersey-container-jetty-http:3.1.9' + runtimeOnly 'org.glassfish.jersey.inject:jersey-hk2:3.1.9' // Support HTTP/2 - implementation 'org.eclipse.jetty.http2:http2-server:11.0.24' + //implementation 'org.eclipse.jetty.http2:http2-server:11.0.24' + implementation 'org.eclipse.jetty.http2:jetty-http2-server:12.0.16' // Support ALPN - implementation 'org.eclipse.jetty:jetty-alpn-java-server:11.0.24' + implementation 'org.eclipse.jetty:jetty-alpn-java-server:12.0.16' runtimeOnly('ch.qos.logback:logback-classic:1.5.12') { // Not used at runtime, bloat diff --git a/src/wres/server/WebServer.java b/src/wres/server/WebServer.java index 4f40cce8e..fd100aab7 100644 --- a/src/wres/server/WebServer.java +++ b/src/wres/server/WebServer.java @@ -1,30 +1,26 @@ package wres.server; -import jakarta.servlet.Servlet; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; 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.server.handler.ErrorHandler; -import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.server.handler.ShutdownHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * Runs the core application as a long-running instance or web server that accepts evaluation requests */ @@ -43,25 +39,6 @@ public class WebServer */ private static final int MAX_SERVER_THREADS = 20; - - /** - * Ensure that the X-Frame-Options header element is included in the response. - * If not already set, then it will be set to DENY. If it is already set when this - * is called, then it will be left unchanged (there is no check for DENY). - */ - private static final HttpChannel.Listener HTTP_CHANNEL_LISTENER = new HttpChannel.Listener() - { - @Override - public void onResponseBegin( Request request ) - { - if ( request.getResponse().getHeader( "X-Frame-Options" ) == null ) - { - request.getResponse().addHeader( "X-Frame-Options", "DENY" ); - } - } - }; - - /** * Get the port that is passed in from args, if not present then use default port * @param args args potentially containing the port @@ -104,8 +81,23 @@ public static void main( String[] args ) throws Exception dynamicHolder.setServlet( servlet ); // Static handler: - ResourceHandler resourceHandler = new ResourceHandler(); - Resource resource = Resource.newClassPathResource( "html" ); + ResourceHandler resourceHandler = new ResourceHandler() + { + @Override + public boolean handle( Request request, + Response response, + Callback callback ) + throws Exception + { + response.getHeaders() + .add( "X-Frame-Options", "DENY" ); + response.getHeaders() + .add( "strict-transport-security", "max-age=31536000; includeSubDomains; preload;" ); + return super.handle( request, response, callback ); + } + }; + ResourceFactory resourceFactory = ResourceFactory.root(); + Resource resource = resourceFactory.newClassLoaderResource( "html", false ); resourceHandler.setBaseResource( resource ); // Have to chain/wrap the handler this way to get both static/dynamic: @@ -156,13 +148,12 @@ public static void main( String[] args ) throws Exception // by other processes running locally, e.g. a shim or a UI. serverConnector.setHost( "127.0.0.1" ); serverConnector.setPort( port ); - serverConnector.addBean( HTTP_CHANNEL_LISTENER ); - serverConnector.setAcceptedSendBufferSize( 0 ); + serverConnector.setAcceptedSendBufferSize( -1 ); ServerConnector[] serverConnectors = { serverConnector }; jettyServer.setConnectors( serverConnectors ); jettyServer.start(); - jettyServer.dump( System.err ); // NOSONAR + jettyServer.dump( System.err ); // NOSONAR String helpMessage = String.format( "Server started. Visit localhost:%d/evaluation for usage instructions", port ); LOGGER.info( helpMessage ); @@ -182,3 +173,5 @@ public static void main( String[] args ) throws Exception } } } + + diff --git a/wres-tasker/src/wres/tasker/Tasker.java b/wres-tasker/src/wres/tasker/Tasker.java index 948754dbe..890315618 100644 --- a/wres-tasker/src/wres/tasker/Tasker.java +++ b/wres-tasker/src/wres/tasker/Tasker.java @@ -1,7 +1,16 @@ package wres.tasker; +import java.io.IOException; import java.security.Security; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.server.CustomRequestLog; @@ -9,15 +18,20 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.Slf4jRequestLogWriter; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.servlet.FilterHolder; +import org.eclipse.jetty.ee10.servlets.HeaderFilter; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.glassfish.jersey.media.multipart.MultiPartFeature; @@ -101,30 +115,6 @@ public class Tasker */ static final int MAX_SERVER_THREADS = 100; - - /** - * Ensure that the X-Frame-Options header element is included in the response. - * If not already set, then it will be set to DENY. If it is already set when this - * is called, then it will be left unchanged (there is no check for DENY). - */ - private static final HttpChannel.Listener HTTP_CHANNEL_LISTENER = new HttpChannel.Listener() - { - @Override - public void onResponseBegin( Request request ) - { - if ( request.getResponse().getHeader( "X-Frame-Options" ) == null ) - { - request.getResponse().addHeader( "X-Frame-Options", "DENY" ); - } - if ( request.getResponse() - .getHeader( "strict-transport-security" ) == null ) - { - request.getResponse() - .addHeader( "strict-transport-security", "max-age=31536000; includeSubDomains; preload;" ); - } - } - }; - /** * Tasker receives requests for wres runs and passes them along to queue. * Actual work is done in WresJob restlet class, Tasker sets up a server. @@ -155,6 +145,8 @@ public static void main( String[] args ) //Set up the ServlentContextHandler and add the ContextFilter for X-Frame-Options for all requests. ServletContextHandler context = new ServletContextHandler( ServletContextHandler.NO_SESSIONS ); context.setContextPath( "/" ); + + //The servlet holder is declared. ServletHolder dynamicHolder = context.addServlet( ServletContainer.class, "/*" ); @@ -168,11 +160,30 @@ public static void main( String[] args ) dynamicHolder.setInitParameter( "jersey.config.server.provider.classnames", MultiPartFeature.class.getCanonicalName() ); - // Static handler: - ResourceHandler resourceHandler = new ResourceHandler(); - resourceHandler.setBaseResource( Resource.newClassPathResource( "html" ) ); + //Static handler for index.html. This also impacts the dynamic resources + //by being chained in setHandler, below. + ResourceHandler resourceHandler = new ResourceHandler() + { + @Override + public boolean handle( Request request, + Response response, + Callback callback ) + throws Exception + { + response.getHeaders() + .add( "X-Frame-Options", "DENY" ); + response.getHeaders() + .add( "strict-transport-security", "max-age=31536000; includeSubDomains; preload;" ); + return super.handle( request, response, callback ); + } + }; + + ResourceFactory resourceFactory = ResourceFactory.root(); + Resource resource = resourceFactory.newClassLoaderResource( "html", false ); + resourceHandler.setBaseResource( resource ); - // Have to chain/wrap the handler this way to get both static/dynamic: + // Have to chain/wrap the handler this way to get both static/dynamic. + // This applies the handle method, above, to dynamic and static resources. resourceHandler.setHandler( context ); // Fix the max server threads for better stack memory predictability, @@ -212,7 +223,6 @@ public static void main( String[] args ) httpTwo ) ) { serverConnector.setPort( Tasker.SERVER_PORT ); - serverConnector.addBean( HTTP_CHANNEL_LISTENER ); ServerConnector[] serverConnectors = { serverConnector }; jettyServer.setConnectors( serverConnectors );