diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cd1f551a..acb957b0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,11 +5,6 @@ updates: schedule: interval: "weekly" target-branch: "master" - ignore: - # Restrict updates to jetty in the 10.x space - - dependency-name: "org.eclipse.jetty:*" - versions: [ ">=11" ] - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/Jenkinsfile b/Jenkinsfile index 5fc52de4..cc6dd186 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,5 @@ */ buildPlugin(useContainerAgent: true, configurations: [ [platform: 'linux', jdk: 21], - [platform: 'windows', jdk: 17], - [platform: 'linux', jdk: 11] + [platform: 'windows', jdk: 17] ]) diff --git a/README.md b/README.md index cf91dc88..fe83999d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # What is Winstone? -Winstone is a command line interface around Jetty 10.0.x, which implements +Winstone is a command line interface around Jetty 12.0.x, which implements Servlet 4.0 (JakartaEE 8/`javax.servlet.*`), WebSocket/JSR-356, and HTTP/2 support. It is used as the default embedded servlet container in Jenkins (via the `executable` package in the `war` module) and can be used by any other web applications that wants to be self-contained. diff --git a/pom.xml b/pom.xml index 221edcd7..db38dabf 100644 --- a/pom.xml +++ b/pom.xml @@ -35,14 +35,12 @@ - 6.22 + 7.0 -SNAPSHOT - 10.0.21 + 12.0.10 2.0.13 jenkinsci/winstone false - - 11 @@ -54,6 +52,13 @@ pom import + + org.eclipse.jetty.ee8 + jetty-ee8-bom + ${jetty.version} + pom + import + org.slf4j @@ -101,10 +106,6 @@ org.eclipse.jetty jetty-server - - org.eclipse.jetty - jetty-servlet - org.eclipse.jetty jetty-unixdomain-server @@ -114,21 +115,25 @@ jetty-util - org.eclipse.jetty - jetty-webapp + org.eclipse.jetty.ee8 + jetty-ee8-servlet - org.eclipse.jetty.http2 - http2-hpack + org.eclipse.jetty.ee8 + jetty-ee8-webapp + + + org.eclipse.jetty.ee8.websocket + jetty-ee8-websocket-jetty-server + org.eclipse.jetty.http2 - http2-server + jetty-http2-hpack - org.eclipse.jetty.websocket - websocket-jetty-server - + org.eclipse.jetty.http2 + jetty-http2-server org.slf4j diff --git a/src/main/java/winstone/HostConfiguration.java b/src/main/java/winstone/HostConfiguration.java index 6c1effbd..fca6314c 100644 --- a/src/main/java/winstone/HostConfiguration.java +++ b/src/main/java/winstone/HostConfiguration.java @@ -30,15 +30,12 @@ import java.util.jar.JarFile; import java.util.logging.Level; import javax.servlet.SessionTrackingMode; -import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.ee8.webapp.WebAppContext; +import org.eclipse.jetty.ee8.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.security.LoginService; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import winstone.cmdline.CompressionScheme; import winstone.cmdline.Option; @@ -55,7 +52,6 @@ public class HostConfiguration { private Map args; private Map webapps; private ClassLoader commonLibCL; - private MimeTypes mimeTypes = new MimeTypes(); private final LoginService loginService; public HostConfiguration(Server server, String hostname, ClassLoader commonLibCL, @NonNull Map args) @@ -85,7 +81,11 @@ public HostConfiguration(Server server, String hostname, ClassLoader commonLibCL // trim off the trailing '/' that Jetty doesn't like prefix = prefix.substring(0, prefix.length() - 1); } - Handler handler = configureAccessLog(create(getWebRoot(webroot, warfile), prefix), "webapp"); + WebAppContext webAppContext = create(getWebRoot(webroot, warfile), prefix); + RequestLog requestLog = configureAccessLog("webapp"); + if (requestLog != null) { + server.setRequestLog(requestLog); + } { // load additional mime types loadBuiltinMimeTypes(); @@ -100,7 +100,7 @@ public HostConfiguration(Server server, String hostname, ClassLoader commonLibCL } String extension = mapping.substring(0, delimPos); String mimeType = mapping.substring(delimPos + 1); - this.mimeTypes.addMimeMapping(extension.toLowerCase(), mimeType); + server.getMimeTypes().addMimeMapping(extension.toLowerCase(), mimeType); } } } @@ -109,11 +109,11 @@ public HostConfiguration(Server server, String hostname, ClassLoader commonLibCL switch (compressionScheme) { case GZIP: GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.setHandler(handler); + gzipHandler.setHandler(webAppContext); server.setHandler(gzipHandler); break; case NONE: - server.setHandler(handler); + server.setHandler(webAppContext); break; default: throw new IllegalArgumentException("Unexpected compression scheme: " + compressionScheme); @@ -132,7 +132,8 @@ private void loadBuiltinMimeTypes() { Properties props = new Properties(); props.load(in); for (Entry e : props.entrySet()) { - mimeTypes.addMimeMapping(e.getKey().toString(), e.getValue().toString()); + server.getMimeTypes() + .addMimeMapping(e.getKey().toString(), e.getValue().toString()); } } catch (IOException e) { throw new UncheckedIOException("Failed to load the built-in MIME types", e); @@ -143,24 +144,21 @@ private void loadBuiltinMimeTypes() { * @param webAppName * Unique name given to the access logger. */ - private Handler configureAccessLog(Handler handler, String webAppName) { + private RequestLog configureAccessLog(String webAppName) { try { Class loggerClass = Option.ACCESS_LOGGER_CLASSNAME.get(args, RequestLog.class, commonLibCL); if (loggerClass != null) { // Build the realm Constructor loggerConstr = loggerClass.getConstructor(String.class, Map.class); - RequestLogHandler rlh = new RequestLogHandler(); - rlh.setHandler(handler); - rlh.setRequestLog(loggerConstr.newInstance(webAppName, args)); - return rlh; + return loggerConstr.newInstance(webAppName, args); } else { Logger.log(Level.FINER, Launcher.RESOURCES, "WebAppConfig.LoggerDisabled"); } } catch (Throwable err) { Logger.log(Level.SEVERE, Launcher.RESOURCES, "WebAppConfig.LoggerError", "", err); } - return handler; + return null; } private WebAppContext create(File app, String prefix) { @@ -199,7 +197,6 @@ public void postConfigure() throws Exception { wac.getSecurityHandler().setLoginService(loginService); wac.setThrowUnavailableOnStartupException( true); // if boot fails, abort the process instead of letting empty Jetty run - wac.setMimeTypes(mimeTypes); wac.getSessionHandler().setSessionTrackingModes(Set.of(SessionTrackingMode.COOKIE)); wac.getSessionHandler().setSessionCookie(WinstoneSession.SESSION_COOKIE_NAME); this.webapps.put(wac.getContextPath(), wac); diff --git a/src/main/java/winstone/HttpsConnectorFactory.java b/src/main/java/winstone/HttpsConnectorFactory.java index 6eacd11e..4b29916e 100644 --- a/src/main/java/winstone/HttpsConnectorFactory.java +++ b/src/main/java/winstone/HttpsConnectorFactory.java @@ -11,7 +11,6 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.SecuredRedirectHandler; import winstone.cmdline.Option; @@ -40,14 +39,9 @@ public Connector start(Map args, Server server) throws IOExcepti if (currentHandler == null) { server.setHandler(new SecuredRedirectHandler()); } else { - if (currentHandler instanceof HandlerList) { - ((HandlerList) currentHandler).addHandler(new SecuredRedirectHandler()); - } else { - HandlerList handlers = new HandlerList(); - handlers.addHandler(new SecuredRedirectHandler()); - handlers.addHandler(currentHandler); - server.setHandler(handlers); - } + Handler.Wrapper securedRedirectHandler = new SecuredRedirectHandler(); + securedRedirectHandler.setHandler(currentHandler); + server.setHandler(securedRedirectHandler); } } configureSsl(args, server); diff --git a/src/main/java/winstone/accesslog/SimpleAccessLogger.java b/src/main/java/winstone/accesslog/SimpleAccessLogger.java index 004f57db..1b9601e8 100644 --- a/src/main/java/winstone/accesslog/SimpleAccessLogger.java +++ b/src/main/java/winstone/accesslog/SimpleAccessLogger.java @@ -16,12 +16,13 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.StandardOpenOption; +import java.security.Principal; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.logging.Level; -import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; @@ -95,44 +96,41 @@ public SimpleAccessLogger(String webAppName, Map startupArgs) th @Override public void log(Request request, Response response) { - String uriLine = request.getMethod() + " " + request.getRequestURI() + " " + request.getProtocol(); + String uriLine = request.getMethod() + " " + request.getHttpURI().getPath() + " " + + request.getConnectionMetaData().getProtocol(); int status = response.getStatus(); - long size = response.getContentCount(); + long size = Response.getContentBytesWritten(response); String date; synchronized (DF) { date = DF.format(new Date()); } // mimic - // https://github.com/eclipse/jetty.project/blob/jetty-9.4.0.v20161208/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java#L130 - Authentication authentication = request.getAuthentication(); - String remoteUser; - if (authentication instanceof Authentication.User) { - Authentication.User user = (Authentication.User) authentication; - remoteUser = user.getUserIdentity().getUserPrincipal().getName(); - } else { - remoteUser = null; - } + // https://github.com/jetty/jetty.project/blob/c5b2533fdecce21b54c6fbaf36f79bc3ba909775/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java#L1093-L1099 + Request.AuthenticationState authenticationState = Request.getAuthenticationState(request); + Principal principal = authenticationState == null ? null : authenticationState.getUserPrincipal(); + String remoteUser = principal == null ? "-" : principal.getName(); + HttpFields httpFields = request.getHeaders(); String logLine = WinstoneResourceBundle.globalReplace(this.pattern, new String[][] { - {"###x-forwarded-for###", nvl(request.getHeader("X-Forwarded-For"))}, - {"###x-forwarded-host###", nvl(request.getHeader("X-Forwarded-Host"))}, - {"###x-forwarded-proto###", nvl(request.getHeader("X-Forwarded-Proto"))}, - {"###x-forwarded-protocol###", nvl(request.getHeader("X-Forwarded-Protocol"))}, - {"###x-forwarded-server###", nvl(request.getHeader("X-Forwarded-Server"))}, - {"###x-forwarded-ssl###", nvl(request.getHeader("X-Forwarded-Ssl"))}, - {"###x-requested-with###", nvl(request.getHeader("X-Requested-With"))}, - {"###x-do-not-track###", nvl(request.getHeader("X-Do-Not-Track"))}, - {"###dnt###", nvl(request.getHeader("DNT"))}, - {"###via###", nvl(request.getHeader("Via"))}, - {"###ip###", request.getRemoteHost()}, + {"###x-forwarded-for###", nvl(httpFields.get("X-Forwarded-For"))}, + {"###x-forwarded-host###", nvl(httpFields.get("X-Forwarded-Host"))}, + {"###x-forwarded-proto###", nvl(httpFields.get("X-Forwarded-Proto"))}, + {"###x-forwarded-protocol###", nvl(httpFields.get("X-Forwarded-Protocol"))}, + {"###x-forwarded-server###", nvl(httpFields.get("X-Forwarded-Server"))}, + {"###x-forwarded-ssl###", nvl(httpFields.get("X-Forwarded-Ssl"))}, + {"###x-requested-with###", nvl(httpFields.get("X-Requested-With"))}, + {"###x-do-not-track###", nvl(httpFields.get("X-Do-Not-Track"))}, + {"###dnt###", nvl(httpFields.get("DNT"))}, + {"###via###", nvl(httpFields.get("Via"))}, + {"###ip###", Request.getRemoteAddr(request)}, {"###user###", nvl(remoteUser)}, {"###time###", "[" + date + "]"}, {"###uriLine###", uriLine}, {"###status###", "" + status}, {"###size###", "" + size}, - {"###referer###", nvl(request.getHeader("Referer"))}, - {"###userAgent###", nvl(request.getHeader("User-Agent"))} + {"###referer###", nvl(httpFields.get("Referer"))}, + {"###userAgent###", nvl(httpFields.get("User-Agent"))} }); this.outWriter.println(logLine); } diff --git a/src/main/resources/winstone/mime.properties b/src/main/resources/winstone/mime.properties index 76b659ab..172de177 100644 --- a/src/main/resources/winstone/mime.properties +++ b/src/main/resources/winstone/mime.properties @@ -766,7 +766,6 @@ sxw=application/vnd.sun.xml.writer taglet=application/vnd.mynfc tao=application/vnd.tao.intent-module-archive tar=application/x-tar -tar.gz=application/gzip tcap=application/vnd.3gpp2.tcap tcl=application/x-tcl teacher=application/vnd.smart.teacher diff --git a/src/test/java/winstone/AbstractWinstoneTest.java b/src/test/java/winstone/AbstractWinstoneTest.java index 5b1b8e20..6bf15f8d 100644 --- a/src/test/java/winstone/AbstractWinstoneTest.java +++ b/src/test/java/winstone/AbstractWinstoneTest.java @@ -6,9 +6,9 @@ import java.net.ConnectException; import java.net.Socket; import java.nio.file.Path; +import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ClientConnector; import org.junit.After;