From: Mathieu Baudier Date: Sun, 22 Sep 2024 06:04:48 +0000 (+0200) Subject: Refactor Jetty support in order to clearly distinguish between the X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=13cc2521bad06c8012d7bd0bbe24fe3a7da06b3b;p=lgpl%2Fargeo-commons.git Refactor Jetty support in order to clearly distinguish between the various APIs being used. --- diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml index 00fcaff99..05978e669 100644 --- a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml +++ b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml @@ -2,7 +2,7 @@ - + diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml index 7540a2cdb..6ddac16e3 100644 --- a/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml +++ b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml @@ -2,7 +2,7 @@ - + diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java index 3827a6d59..60e809d68 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java @@ -6,8 +6,6 @@ import java.util.Map; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.argeo.api.cms.CmsAuth; import org.argeo.api.cms.CmsLog; @@ -16,8 +14,12 @@ import org.argeo.cms.auth.RemoteAuthRequest; import org.argeo.cms.auth.RemoteAuthResponse; import org.argeo.cms.auth.RemoteAuthUtils; import org.argeo.cms.servlet.internal.HttpUtils; +import org.eclipse.rap.service.http.HttpContext; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.osgi.service.http.context.ServletContextHelper; /** @@ -117,4 +119,10 @@ public class CmsServletContext extends ServletContextHelper { return bundle.getResource(name); } + @Override + public String getMimeType(String name) { + return null; + } + + } diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java index ca4b6f74d..07e3ccca0 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java @@ -9,10 +9,10 @@ import java.util.Collection; import java.util.SortedMap; import java.util.TreeMap; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.argeo.cms.osgi.FilterRequirement; import org.argeo.cms.osgi.PublishNamespace; diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletContext.java new file mode 100644 index 000000000..6ffc1542c --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletContext.java @@ -0,0 +1,121 @@ +package org.argeo.cms.servlet.javax; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.servlet.internal.HttpUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.http.context.ServletContextHelper; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Default servlet context degrading to anonymous if the the session is not + * pre-authenticated. + */ +public class JavaxServletContext extends ServletContextHelper { + private final static CmsLog log = CmsLog.getLog(JavaxServletContext.class); + // use CMS bundle for resources + private Bundle bundle = FrameworkUtil.getBundle(getClass()); + + private final String httpAuthRealm = "Argeo"; + private final boolean forceBasic = false; + + public void init(Map properties) { + + } + + public void destroy() { + + } + + @Override + public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { +// if (log.isTraceEnabled()) +// HttpUtils.logRequestHeaders(log, request); + RemoteAuthRequest remoteAuthRequest = new JavaxServletHttpRequest(request); + RemoteAuthResponse remoteAuthResponse = new JavaxServletHttpResponse(response); + ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(JavaxServletContext.class.getClassLoader()); + LoginContext lc; + try { + lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse)); + lc.login(); + } catch (LoginException e) { + if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) { + int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, + remoteAuthResponse, httpAuthRealm, + forceBasic); + response.setStatus(statusCode); + return false; + + } else { + lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse); + } + if (lc == null) + return false; + } finally { + Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); + } + +// Subject subject = lc.getSubject(); +// Subject.doAs(subject, new PrivilegedAction() { +// +// @Override +// public Void run() { +// // TODO also set login context in order to log out ? +// RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest); +// return null; +// } +// +// }); + return true; + } + +// @Override +// public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { +// RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request)); +// } + + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return false; + } + +// protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { +// // anonymous +// ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); +// try { +// Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); +// LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext( +// new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); +// lc.login(); +// return lc; +// } catch (LoginException e1) { +// if (log.isDebugEnabled()) +// log.error("Cannot log in as anonymous", e1); +// return null; +// } finally { +// Thread.currentThread().setContextClassLoader(currentContextClassLoader); +// } +// } + + @Override + public URL getResource(String name) { + // TODO make it more robust and versatile + // if used directly it can only load from within this bundle + return bundle.getResource(name); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletHttpResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletHttpResponse.java new file mode 100644 index 000000000..0f29aef18 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletHttpResponse.java @@ -0,0 +1,27 @@ +package org.argeo.cms.servlet.javax; + +import java.util.Objects; + +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.RemoteAuthResponse; + +public class JavaxServletHttpResponse implements RemoteAuthResponse { + private final HttpServletResponse response; + + public JavaxServletHttpResponse(HttpServletResponse response) { + Objects.requireNonNull(response); + this.response = response; + } + + @Override + public void setHeader(String headerName, String value) { + response.setHeader(headerName, value); + } + + @Override + public void addHeader(String headerName, String value) { + response.addHeader(headerName, value); + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/AbstractJettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/AbstractJettyHttpContext.java new file mode 100644 index 000000000..76336b118 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/AbstractJettyHttpContext.java @@ -0,0 +1,79 @@ +package org.argeo.cms.jetty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jetty.server.handler.ContextHandler; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** + * An @{HttpContext} implementation based on Jetty. + */ +public abstract class AbstractJettyHttpContext extends HttpContext { + private final JettyHttpServer httpServer; + private final String path; + private final List filters = new ArrayList<>(); + + private HttpHandler handler; + private Authenticator authenticator; + + public AbstractJettyHttpContext(JettyHttpServer httpServer, String path) { + this.httpServer = httpServer; + if (!path.endsWith("/")) + throw new IllegalArgumentException("Path " + path + " should end with a /"); + this.path = path; + } + + protected abstract ContextHandler getJettyHandler(); + + @Override + public HttpHandler getHandler() { + return handler; + } + + @Override + public void setHandler(HttpHandler handler) { + if (this.handler != null) + throw new IllegalArgumentException("Handler is already set"); + Objects.requireNonNull(handler); + this.handler = handler; + } + + @Override + public String getPath() { + return path; + } + + @Override + public HttpServer getServer() { + return getJettyHttpServer(); + } + + protected JettyHttpServer getJettyHttpServer() { + return httpServer; + } + + @Override + public List getFilters() { + return filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) { + Authenticator previousAuthenticator = authenticator; + this.authenticator = auth; + return previousAuthenticator; + } + + @Override + public Authenticator getAuthenticator() { + return authenticator; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java deleted file mode 100644 index 6bbc79928..000000000 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.cms.jetty; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; -import org.eclipse.jetty.ee10.servlet.SessionHandler; -import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.Configurator; -import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.websocket.DeploymentException; -import jakarta.websocket.server.ServerContainer; - -/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */ -public class CmsJettyServer extends JettyHttpServer { - private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; - // Equinox compatibility - private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; - private Path tempDir; - - private CompletableFuture serverContainer = new CompletableFuture<>(); - - protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { - } - - @Override - public void start() { - try { - tempDir = Files.createTempDirectory("jetty"); - } catch (IOException e) { - throw new IllegalStateException("Cannot create temp dir", e); - } - super.start(); - } - - @Override - protected ServletContextHandler createRootContextHandler() { - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, - Thread.currentThread().getContextClassLoader()); - servletContextHandler.setClassLoader(this.getClass().getClassLoader()); - servletContextHandler.setContextPath("/"); - - servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile()); - SessionHandler handler = new SessionHandler(); - // FIXME deal with long running session - // handler.setMaxInactiveInterval(-1); - servletContextHandler.setSessionHandler(handler); - - JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { - - @Override - public void accept(ServletContext servletContext, ServerContainer serverContainer) - throws DeploymentException { - CmsJettyServer.this.serverContainer.complete(serverContainer); - } - }); - - return servletContextHandler; - } - - @Override - protected ServerContainer getRootServerContainer() { - return serverContainer.join(); - } - - @Override - protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException { - addServlets(servletContextHandler); - } - - /* - * WEB SOCKET - */ - -} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java index 105aa2945..8d19af3ba 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java @@ -11,7 +11,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; * A {@link Map} implementation wrapping the attributes of a Jetty * {@link ContextHandler}. */ -class ContextHandlerAttributes extends AbstractMap { +public class ContextHandlerAttributes extends AbstractMap { private ContextHandler contextHandler; public ContextHandlerAttributes(ContextHandler contextHandler) { diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java deleted file mode 100644 index 5e0cb4c23..000000000 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.cms.jetty; - -import java.util.Map; - -import jakarta.servlet.ServletContext; -import jakarta.websocket.DeploymentException; -import jakarta.websocket.server.ServerContainer; - -import org.argeo.cms.servlet.httpserver.HttpContextServlet; -import org.argeo.cms.websocket.server.WebsocketEndpoints; -import org.eclipse.jetty.ee10.servlet.SessionHandler; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; -import org.eclipse.jetty.ee10.servlet.ServletHolder; -import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; -import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.Configurator; - -import com.sun.net.httpserver.HttpHandler; - -/** - * An @{HttpContext} implementation based on a Jetty - * {@link ServletContextHandler}. - */ -class ContextHandlerHttpContext extends JettyHttpContext { - private final ServletContextHandler servletContextHandler; - private final ContextHandlerAttributes attributes; - - public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) { - super(httpServer, path); - - // Jetty context handler - this.servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath(path); - HttpContextServlet servlet = new HttpContextServlet(this); - servletContextHandler.addServlet(new ServletHolder(servlet), "/*"); - SessionHandler sessionHandler = new SessionHandler(); - // FIXME find a better default - // FIXME find out how to have long-running sessions - // sessionHandler.setMaxInactiveInterval(-1); - servletContextHandler.setSessionHandler(sessionHandler); - - attributes = new ContextHandlerAttributes(servletContextHandler); - } - - @Override - public void setHandler(HttpHandler handler) { - super.setHandler(handler); - - // web socket - if (handler instanceof WebsocketEndpoints) { - JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { - - @Override - public void accept(ServletContext servletContext, ServerContainer serverContainer) - throws DeploymentException { - for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { - serverContainer.addEndpoint(clss); - } - } - }); - } - - if (getJettyHttpServer().isStarted()) - try { - servletContextHandler.start(); - } catch (Exception e) { - throw new IllegalStateException("Cannot start context handler", e); - } - } - - @Override - public Map getAttributes() { - return attributes; - } - - @Override - protected ServletContextHandler getServletContextHandler() { - return servletContextHandler; - } - -} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java deleted file mode 100644 index 3aae466bb..000000000 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.argeo.cms.jetty; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.argeo.cms.websocket.server.WebsocketEndpoints; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; - -import com.sun.net.httpserver.Authenticator; -import com.sun.net.httpserver.Filter; -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -/** - * An @{HttpContext} implementation based on Jetty. It supports web sockets if - * the handler implements {@link WebsocketEndpoints}. - */ -abstract class JettyHttpContext extends HttpContext { - private final JettyHttpServer httpServer; - private final String path; - private final List filters = new ArrayList<>(); - - private HttpHandler handler; - private Authenticator authenticator; - - public JettyHttpContext(JettyHttpServer httpServer, String path) { - this.httpServer = httpServer; - if (!path.endsWith("/")) - throw new IllegalArgumentException("Path " + path + " should end with a /"); - this.path = path; - } - - protected abstract ServletContextHandler getServletContextHandler(); - - @Override - public HttpHandler getHandler() { - return handler; - } - - @Override - public void setHandler(HttpHandler handler) { - if (this.handler != null) - throw new IllegalArgumentException("Handler is already set"); - Objects.requireNonNull(handler); - this.handler = handler; - } - - @Override - public String getPath() { - return path; - } - - @Override - public HttpServer getServer() { - return getJettyHttpServer(); - } - - protected JettyHttpServer getJettyHttpServer() { - return httpServer; - } - - @Override - public List getFilters() { - return filters; - } - - @Override - public Authenticator setAuthenticator(Authenticator auth) { - Authenticator previousAuthenticator = authenticator; - this.authenticator = auth; - return previousAuthenticator; - } - - @Override - public Authenticator getAuthenticator() { - return authenticator; - } - -} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index dbdbdc7cc..b3ffe3ddb 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -9,15 +9,15 @@ import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import javax.net.ssl.SSLContext; -import jakarta.servlet.ServletException; -import jakarta.websocket.server.ServerContainer; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsState; import org.argeo.cms.CmsDeployProperty; import org.argeo.cms.http.server.HttpServerUtils; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.argeo.cms.jetty.ee10.ContextHandlerHttpContext; +import org.argeo.cms.jetty.server.JettyHttpContext; import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; @@ -36,6 +36,8 @@ import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; +import jakarta.websocket.server.ServerContainer; + /** An {@link HttpServer} implementation based on Jetty. */ public class JettyHttpServer extends HttpsServer { private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class); @@ -55,9 +57,9 @@ public class JettyHttpServer extends HttpsServer { private HttpsConfigurator httpsConfigurator; - private final Map contexts = new TreeMap<>(); + private final Map contexts = new TreeMap<>(); - private ServletContextHandler rootContextHandler; + private Handler rootContextHandler; protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); private boolean started; @@ -263,10 +265,10 @@ public class JettyHttpServer extends HttpsServer { if (contexts.containsKey(path)) throw new IllegalArgumentException("Context " + path + " already exists"); - JettyHttpContext httpContext = new ServletHttpContext(this, path); + AbstractJettyHttpContext httpContext = new JettyHttpContext(this, path); contexts.put(path, httpContext); - contextHandlerCollection.addHandler(httpContext.getServletContextHandler()); + contextHandlerCollection.addHandler(httpContext.getJettyHandler()); return httpContext; } @@ -276,7 +278,7 @@ public class JettyHttpServer extends HttpsServer { path = path + "/"; if (!contexts.containsKey(path)) throw new IllegalArgumentException("Context " + path + " does not exist"); - JettyHttpContext httpContext = contexts.remove(path); + AbstractJettyHttpContext httpContext = contexts.remove(path); if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) { // TODO stop handler first? // FIXME understand compatibility with Jetty 12 @@ -340,27 +342,29 @@ public class JettyHttpServer extends HttpsServer { return httpsConnector.getLocalPort(); } - protected ServletContextHandler createRootContextHandler() { + protected Handler createRootContextHandler() { return null; } - protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException { + protected void configureRootContextHandler(Handler servletContextHandler) { } + // TODO protect it? + public Handler getRootContextHandler() { + return rootContextHandler; + } + public void setCmsState(CmsState cmsState) { this.cmsState = cmsState; } - boolean isStarted() { + public boolean isStarted() { return started; } - ServletContextHandler getRootContextHandler() { - return rootContextHandler; - } - - ServerContainer getRootServerContainer() { + // TODO protect it? + public ServerContainer getRootServerContainer() { throw new UnsupportedOperationException(); } diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java deleted file mode 100644 index 987e5df0d..000000000 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.argeo.cms.jetty; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import jakarta.websocket.DeploymentException; -import jakarta.websocket.server.ServerContainer; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.servlet.httpserver.HttpContextServlet; -import org.argeo.cms.websocket.server.WebsocketEndpoints; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; -import org.eclipse.jetty.ee10.servlet.ServletHolder; - -import com.sun.net.httpserver.HttpHandler; - -/** - * A {@link JettyHttpContext} based on registering a servlet to the root handler - * of the {@link JettyHttpServer}, in order to integrate the sessions. - */ -public class ServletHttpContext extends JettyHttpContext { - private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class); - - private Map attributes = Collections.synchronizedMap(new HashMap<>()); - - public ServletHttpContext(JettyHttpServer httpServer, String path) { - super(httpServer, path); - - ServletContextHandler rootContextHandler = httpServer.getRootContextHandler(); - HttpContextServlet servlet = new HttpContextServlet(this); - rootContextHandler.addServlet(new ServletHolder(servlet), path + "*"); - } - - @Override - public void setHandler(HttpHandler handler) { - super.setHandler(handler); - - // web socket - if (handler instanceof WebsocketEndpoints) { - ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer(); - for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { - try { - serverContainer.addEndpoint(clss); - log.debug(() -> "Added web socket " + clss + " to " + getPath()); - } catch (DeploymentException e) { - log.error("Cannot deploy Web Socket " + clss, e); - } - } - } - } - - @Override - public Map getAttributes() { - return attributes; - } - - @Override - protected ServletContextHandler getServletContextHandler() { - return getJettyHttpServer().getRootContextHandler(); - } - -} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/CmsJettyServer.java new file mode 100644 index 000000000..aa3731496 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/CmsJettyServer.java @@ -0,0 +1,81 @@ +package org.argeo.cms.jetty.ee10; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +import org.argeo.cms.jetty.JettyHttpServer; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.SessionHandler; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.Configurator; +import org.eclipse.jetty.server.Handler; + +import jakarta.servlet.ServletContext; +import jakarta.websocket.DeploymentException; +import jakarta.websocket.server.ServerContainer; + +/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */ +public class CmsJettyServer extends JettyHttpServer { + private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; + // Equinox compatibility + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + private Path tempDir; + + private CompletableFuture serverContainer = new CompletableFuture<>(); + + protected void addServlets(ServletContextHandler servletContextHandler) { + } + + @Override + public void start() { + try { + tempDir = Files.createTempDirectory("jetty"); + } catch (IOException e) { + throw new IllegalStateException("Cannot create temp dir", e); + } + super.start(); + } + + @Override + protected ServletContextHandler createRootContextHandler() { + ServletContextHandler servletContextHandler = new ServletContextHandler(); + servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, + Thread.currentThread().getContextClassLoader()); + servletContextHandler.setClassLoader(this.getClass().getClassLoader()); + servletContextHandler.setContextPath("/"); + + servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile()); + SessionHandler handler = new SessionHandler(); + // FIXME deal with long running session + // handler.setMaxInactiveInterval(-1); + servletContextHandler.setSessionHandler(handler); + + JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + CmsJettyServer.this.serverContainer.complete(serverContainer); + } + }); + + return servletContextHandler; + } + + @Override + public ServerContainer getRootServerContainer() { + return serverContainer.join(); + } + + @Override + protected void configureRootContextHandler(Handler servletContextHandler) { + addServlets((ServletContextHandler) servletContextHandler); + } + + /* + * WEB SOCKET + */ + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ContextHandlerHttpContext.java new file mode 100644 index 000000000..c0611329e --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ContextHandlerHttpContext.java @@ -0,0 +1,84 @@ +package org.argeo.cms.jetty.ee10; + +import java.util.Map; + +import jakarta.servlet.ServletContext; +import jakarta.websocket.DeploymentException; +import jakarta.websocket.server.ServerContainer; + +import org.argeo.cms.jetty.ContextHandlerAttributes; +import org.argeo.cms.jetty.AbstractJettyHttpContext; +import org.argeo.cms.jetty.JettyHttpServer; +import org.argeo.cms.servlet.httpserver.HttpContextServlet; +import org.argeo.cms.websocket.server.WebsocketEndpoints; +import org.eclipse.jetty.ee10.servlet.SessionHandler; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.Configurator; + +import com.sun.net.httpserver.HttpHandler; + +/** + * An @{HttpContext} implementation based on a Jetty + * {@link ServletContextHandler}. + */ +@Deprecated +public class ContextHandlerHttpContext extends AbstractJettyHttpContext { + private final ServletContextHandler servletContextHandler; + private final ContextHandlerAttributes attributes; + + public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) { + super(httpServer, path); + + // Jetty context handler + this.servletContextHandler = new ServletContextHandler(); + servletContextHandler.setContextPath(path); + HttpContextServlet servlet = new HttpContextServlet(this); + servletContextHandler.addServlet(new ServletHolder(servlet), "/*"); + SessionHandler sessionHandler = new SessionHandler(); + // FIXME find a better default + // FIXME find out how to have long-running sessions + // sessionHandler.setMaxInactiveInterval(-1); + servletContextHandler.setSessionHandler(sessionHandler); + + attributes = new ContextHandlerAttributes(servletContextHandler); + } + + @Override + public void setHandler(HttpHandler handler) { + super.setHandler(handler); + + // web socket + if (handler instanceof WebsocketEndpoints) { + JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { + serverContainer.addEndpoint(clss); + } + } + }); + } + + if (getJettyHttpServer().isStarted()) + try { + servletContextHandler.start(); + } catch (Exception e) { + throw new IllegalStateException("Cannot start context handler", e); + } + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getJettyHandler() { + return servletContextHandler; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ServletHttpContext.java new file mode 100644 index 000000000..b8d6cf882 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ServletHttpContext.java @@ -0,0 +1,66 @@ +package org.argeo.cms.jetty.ee10; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jetty.AbstractJettyHttpContext; +import org.argeo.cms.jetty.JettyHttpServer; +import org.argeo.cms.servlet.httpserver.HttpContextServlet; +import org.argeo.cms.websocket.server.WebsocketEndpoints; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; + +import com.sun.net.httpserver.HttpHandler; + +import jakarta.websocket.DeploymentException; +import jakarta.websocket.server.ServerContainer; + +/** + * A {@link AbstractJettyHttpContext} based on registering a servlet to the root handler + * of the {@link JettyHttpServer}, in order to integrate the sessions. It + * supports web sockets if the handler implements {@link WebsocketEndpoints}. + */ +public class ServletHttpContext extends AbstractJettyHttpContext { + private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class); + + private Map attributes = Collections.synchronizedMap(new HashMap<>()); + + public ServletHttpContext(JettyHttpServer httpServer, String path) { + super(httpServer, path); + + ServletContextHandler rootContextHandler = (ServletContextHandler) httpServer.getRootContextHandler(); + HttpContextServlet servlet = new HttpContextServlet(this); + rootContextHandler.addServlet(new ServletHolder(servlet), path + "*"); + } + + @Override + public void setHandler(HttpHandler handler) { + super.setHandler(handler); + + // web socket + if (handler instanceof WebsocketEndpoints) { + ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer(); + for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { + try { + serverContainer.addEndpoint(clss); + log.debug(() -> "Added web socket " + clss + " to " + getPath()); + } catch (DeploymentException e) { + log.error("Cannot deploy Web Socket " + clss, e); + } + } + } + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getJettyHandler() { + return (ServletContextHandler) getJettyHttpServer().getRootContextHandler(); + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee8/CmsJavaxJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee8/CmsJavaxJettyServer.java new file mode 100644 index 000000000..b518877e6 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee8/CmsJavaxJettyServer.java @@ -0,0 +1,85 @@ +package org.argeo.cms.jetty.ee8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +import javax.servlet.ServletContext; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; + +import org.argeo.cms.jetty.JettyHttpServer; +import org.eclipse.jetty.ee8.nested.SessionHandler; +import org.eclipse.jetty.ee8.servlet.ServletContextHandler; +import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; +import org.eclipse.jetty.server.Handler; + +/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */ +public class CmsJavaxJettyServer extends JettyHttpServer { + private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; + // Equinox compatibility + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + private Path tempDir; + + private CompletableFuture serverContainer = new CompletableFuture<>(); + + // FIXME + private ServletContextHandler rootContextHandler; + + protected void addServlets(ServletContextHandler servletContextHandler) { + } + + @Override + public void start() { + try { + tempDir = Files.createTempDirectory("jetty"); + } catch (IOException e) { + throw new IllegalStateException("Cannot create temp dir", e); + } + super.start(); + } + + @Override + protected Handler createRootContextHandler() { + ServletContextHandler servletContextHandler = new ServletContextHandler(); + servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, + Thread.currentThread().getContextClassLoader()); + servletContextHandler.setClassLoader(this.getClass().getClassLoader()); + servletContextHandler.setContextPath("/"); + + servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile()); + SessionHandler handler = new SessionHandler(); + // FIXME deal with long running session + // handler.setMaxInactiveInterval(-1); + servletContextHandler.setSessionHandler(handler); + + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + CmsJavaxJettyServer.this.serverContainer.complete(serverContainer); + } + }); + + rootContextHandler = servletContextHandler; + return servletContextHandler.get(); + } + +// @Override +// protected ServerContainer getRootServerContainer() { +// return serverContainer.join(); +// } + + @Override + protected void configureRootContextHandler(Handler servletContextHandler) { + addServlets(rootContextHandler); + } + + /* + * WEB SOCKET + */ + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyContextHandler.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyContextHandler.java new file mode 100644 index 000000000..5a768dd82 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyContextHandler.java @@ -0,0 +1,14 @@ +package org.argeo.cms.jetty.server; + +import org.eclipse.jetty.server.handler.ContextHandler; + +import com.sun.net.httpserver.HttpContext; + +/** A Jetty {@link ContextHandler} based on an {@link HttpContext}. */ +class HttpContextJettyContextHandler extends ContextHandler { + + public HttpContextJettyContextHandler(HttpContext httpContext) { + super(new HttpContextJettyHandler(httpContext), httpContext.getPath()); + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyHandler.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyHandler.java new file mode 100644 index 000000000..8c793f504 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyHandler.java @@ -0,0 +1,28 @@ +package org.argeo.cms.jetty.server; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Handler.Abstract; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +import com.sun.net.httpserver.HttpContext; + +/** A jetty {@link Handler} based on an {@link HttpContext}. */ +class HttpContextJettyHandler extends Abstract { + private final HttpContext httpContext; + + public HttpContextJettyHandler(HttpContext httpContext) { + this.httpContext = httpContext; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + JettyHttpExchange httpExchange = new JettyHttpExchange(httpContext, request, response); + // FIXME deal with authentication + httpContext.getHandler().handle(httpExchange); + callback.succeeded(); + return true; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpContext.java new file mode 100644 index 000000000..92411835e --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpContext.java @@ -0,0 +1,36 @@ +package org.argeo.cms.jetty.server; + +import java.util.Map; + +import org.argeo.cms.jetty.AbstractJettyHttpContext; +import org.argeo.cms.jetty.ContextHandlerAttributes; +import org.argeo.cms.jetty.JettyHttpServer; +import org.eclipse.jetty.server.handler.ContextHandler; + +import com.sun.net.httpserver.HttpContext; + +/** + * A {@link HttpContext} based on pure Jetty server components (no dependency to + * the jakarta/javax servlet APIs). + */ +public class JettyHttpContext extends AbstractJettyHttpContext { + private final ContextHandler contextHandler; + private final ContextHandlerAttributes attributes; + + public JettyHttpContext(JettyHttpServer httpServer, String path) { + super(httpServer, path); + contextHandler = new HttpContextJettyContextHandler(this); + attributes = new ContextHandlerAttributes(contextHandler); + } + + @Override + protected ContextHandler getJettyHandler() { + return contextHandler; + } + + @Override + public Map getAttributes() { + return attributes; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpExchange.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpExchange.java new file mode 100644 index 000000000..5a4a40230 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpExchange.java @@ -0,0 +1,193 @@ +package org.argeo.cms.jetty.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.System.Logger; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; + +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.io.ssl.SslConnection.SslEndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Fields; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +/** Integrates {@link HttpsExchange} in a servlet container. */ +class JettyHttpExchange extends HttpsExchange { + private final static Logger logger = System.getLogger(JettyHttpExchange.class.getName()); + // see + // https://github.com/jetty/jetty.project/blob/jetty-12.0.x/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java + // for mapping between the Servelt and Jetty APIs. + + private final HttpContext httpContext; + private final Request request; + private final Response response; + + private final Headers requestHeaders; + private final Headers responseHeaders; + + private InputStream filteredIn; + private OutputStream filteredOut; + + private HttpPrincipal principal; + + public JettyHttpExchange(HttpContext httpContext, Request jettyRequest, Response jettyResponse) { + this.httpContext = httpContext; + this.request = jettyRequest; + this.response = jettyResponse; + + // request headers + requestHeaders = new Headers(); + + Fields allParameters = Request.extractQueryParameters(request); + for (Fields.Field parameter : allParameters) { + requestHeaders.put(parameter.getName(), parameter.getValues()); + } + + responseHeaders = new Headers(); + } + + @Override + public SSLSession getSSLSession() { + SSLSession sslSession = null; + EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint(); + if (endPoint instanceof SslEndPoint sslEndPoint) { + SslConnection sslConnection = sslEndPoint.getSslConnection(); + SSLEngine sslEngine = sslConnection.getSSLEngine(); + sslSession = sslEngine.getSession(); + } + if (sslSession == null) + throw new IllegalStateException("SSL session not found"); + return sslSession; + } + + @Override + public Headers getRequestHeaders() { + return requestHeaders; + } + + @Override + public Headers getResponseHeaders() { + return responseHeaders; + } + + @Override + public URI getRequestURI() { + return request.getHttpURI().toURI(); + } + + @Override + public String getRequestMethod() { + return request.getMethod(); + } + + @Override + public HttpContext getHttpContext() { + return httpContext; + } + + @Override + public void close() { + + try { + Content.Source.asInputStream(request).close(); + } catch (IOException e) { + logger.log(System.Logger.Level.WARNING, "Cannot close stream of request " + request, e); + } + try { + Content.Sink.asOutputStream(response).close(); + } catch (IOException e) { + logger.log(System.Logger.Level.WARNING, "Cannot close stream of response " + response, e); + } + + } + + @Override + public InputStream getRequestBody() { + if (filteredIn != null) + return filteredIn; + else + return Content.Source.asInputStream(request); + } + + @Override + public OutputStream getResponseBody() { + if (filteredOut != null) + return filteredOut; + else + return Content.Sink.asOutputStream(response); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + for (String headerName : responseHeaders.keySet()) { + for (String headerValue : responseHeaders.get(headerName)) { + response.getHeaders().put(headerName, headerValue); + } + } + // TODO deal with content length etc. + response.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() { + // TODO support non IP socket address? (e.g. UNIX sockets) + return (InetSocketAddress) request.getConnectionMetaData().getRemoteSocketAddress(); + } + + @Override + public int getResponseCode() { + return response.getStatus(); + } + + @Override + public InetSocketAddress getLocalAddress() { + // TODO support non IP socket address? (e.g. UNIX sockets) + return (InetSocketAddress) request.getConnectionMetaData().getLocalSocketAddress(); + } + + @Override + public String getProtocol() { + return request.getConnectionMetaData().getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return request.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + request.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + if (i != null) + filteredIn = i; + if (o != null) + filteredOut = o; + + } + + @Override + public HttpPrincipal getPrincipal() { + return principal; + } + + void setPrincipal(HttpPrincipal principal) { + this.principal = principal; + } + +} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java index bbc5b4d99..a1b4c4ece 100644 --- a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java @@ -15,7 +15,7 @@ import jakarta.servlet.http.HttpSessionIdListener; import jakarta.servlet.http.HttpSessionListener; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jetty.CmsJettyServer; +import org.argeo.cms.jetty.ee10.CmsJettyServer; import org.eclipse.rap.http.servlet.HttpServiceServlet; import org.eclipse.jetty.ee10.servlet.SessionHandler; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; @@ -28,17 +28,21 @@ public class EquinoxJettyServer extends CmsJettyServer { private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; @Override - protected void addServlets(ServletContextHandler rootContextHandler) throws ServletException { - ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet()); - holder.setInitOrder(0); - holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$ - holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$ - - rootContextHandler.addServlet(holder, "/*"); - - // post-start - SessionHandler sessionManager = rootContextHandler.getSessionHandler(); - sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet()); + protected void addServlets(ServletContextHandler rootContextHandler) { + try { + ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet()); + holder.setInitOrder(0); + holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$ + holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$ + + rootContextHandler.addServlet(holder, "/*"); + + // post-start + SessionHandler sessionManager = rootContextHandler.getSessionHandler(); + sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet()); + } catch (ServletException e) { + throw new RuntimeException("Cannot add servlets", e); + } } public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet { diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/JavaxJettyServer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/JavaxJettyServer.java new file mode 100644 index 000000000..afa61dec7 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/JavaxJettyServer.java @@ -0,0 +1,162 @@ +package org.argeo.cms.equinox.http.jetty; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jetty.ee10.CmsJettyServer; +import org.argeo.cms.jetty.ee8.CmsJavaxJettyServer; +import org.eclipse.equinox.http.servlet.HttpServiceServlet; +import org.eclipse.jetty.ee8.nested.SessionHandler; +import org.eclipse.jetty.ee8.servlet.ServletContextHandler; +import org.eclipse.jetty.ee8.servlet.ServletHolder; +import org.osgi.framework.Constants; + +/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */ +public class JavaxJettyServer extends CmsJavaxJettyServer { + private final static CmsLog log = CmsLog.getLog(EquinoxJettyServer.class); + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + + @Override + protected void addServlets(ServletContextHandler rootContextHandler) { + try { + ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet()); + holder.setInitOrder(0); + holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$ + holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$ + + rootContextHandler.addServlet(holder, "/*"); + + // post-start + SessionHandler sessionManager = rootContextHandler.getSessionHandler(); + sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet()); + } catch (ServletException e) { + throw new RuntimeException("Cannot add servlets", e); + } + } + + public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet { + private final Servlet httpServiceServlet = new HttpServiceServlet(); + private ClassLoader contextLoader; + private final Method sessionDestroyed; + private final Method sessionIdChanged; + + public InternalHttpServiceServlet() { + Class clazz = httpServiceServlet.getClass(); + + try { + sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + try { + sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public void init(ServletConfig config) throws ServletException { + ServletContext context = config.getServletContext(); + contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER); + + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.init(config); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void destroy() { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.destroy(); + } finally { + thread.setContextClassLoader(current); + } + contextLoader = null; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.service(req, res); + } catch (IllegalStateException e) { + // context is probably in shutdown + if (log.isTraceEnabled()) + log.error("Cannot process request", e); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public ServletConfig getServletConfig() { + return httpServiceServlet.getServletConfig(); + } + + @Override + public String getServletInfo() { + return httpServiceServlet.getServletInfo(); + } + + @Override + public void sessionCreated(HttpSessionEvent event) { + // Nothing to do. + } + + @Override + public void sessionDestroyed(HttpSessionEvent event) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId()); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionIdChanged.invoke(httpServiceServlet, oldSessionId); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + } + +}