]> git.argeo.org Git - lgpl/argeo-commons.git/commitdiff
Refactor Jetty support in order to clearly distinguish between the
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 22 Sep 2024 06:04:48 +0000 (08:04 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 22 Sep 2024 06:04:48 +0000 (08:04 +0200)
various APIs being used.

23 files changed:
org.argeo.cms.ee/OSGI-INF/pkgServlet.xml
org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml
org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java
org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/javax/JavaxServletHttpResponse.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/AbstractJettyHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java [deleted file]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java [deleted file]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java [deleted file]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java [deleted file]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/CmsJettyServer.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ContextHandlerHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee10/ServletHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ee8/CmsJavaxJettyServer.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyContextHandler.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/HttpContextJettyHandler.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/server/JettyHttpExchange.java [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/JavaxJettyServer.java [new file with mode: 0644]

index 00fcaff99032147021a0271555bc70fa1d87dc6b..05978e669f3945f313d8b814942ada794112239b 100644 (file)
@@ -2,7 +2,7 @@
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
    <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
    <service>
-      <provide interface="javax.servlet.Servlet"/>
+      <provide interface="jakarta.servlet.Servlet"/>
    </service>
    <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
    <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
index 7540a2cdb130ed715b0049c0047f75831a7048b8..6ddac16e3d5095a3360389e72857ba685c8d4106 100644 (file)
@@ -2,7 +2,7 @@
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
    <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
    <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+      <provide interface="org.eclipse.rap.service.http.HttpContext"/>
    </service>
    <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
    <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
index 3827a6d5900adedc3cc0ce278ab54b3abd2c30f6..60e809d68d6f6a2928f32c1636e5ec10204e1281 100644 (file)
@@ -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;
+       }
+
+       
 }
index ca4b6f74d70f9c27603fb7aaa9cefcc60f29bfde..07e3ccca07d8425c76a0454177f74620c9af4ce6 100644 (file)
@@ -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 (file)
index 0000000..6ffc154
--- /dev/null
@@ -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<String, String> 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<Void>() {
+//
+//                     @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 (file)
index 0000000..0f29aef
--- /dev/null
@@ -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 (file)
index 0000000..76336b1
--- /dev/null
@@ -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<Filter> 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<Filter> 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 (file)
index 6bbc799..0000000
+++ /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> 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
-        */
-
-}
index 105aa294556c093a0f8e8e8abd53216e107650ca..8d19af3bad3c51798bcb09b41c800edc94aa947f 100644 (file)
@@ -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<String, Object> {
+public class ContextHandlerAttributes extends AbstractMap<String, Object> {
        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 (file)
index 5e0cb4c..0000000
+++ /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<String, Object> 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 (file)
index 3aae466..0000000
+++ /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<Filter> 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<Filter> getFilters() {
-               return filters;
-       }
-
-       @Override
-       public Authenticator setAuthenticator(Authenticator auth) {
-               Authenticator previousAuthenticator = authenticator;
-               this.authenticator = auth;
-               return previousAuthenticator;
-       }
-
-       @Override
-       public Authenticator getAuthenticator() {
-               return authenticator;
-       }
-
-}
index dbdbdc7ccf1d8cc653a613c760462b9b0a8f4463..b3ffe3ddb2874fb9c8f28a822c330822393e0268 100644 (file)
@@ -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<String, JettyHttpContext> contexts = new TreeMap<>();
+       private final Map<String, AbstractJettyHttpContext> 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 (file)
index 987e5df..0000000
+++ /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<String, Object> 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<String, Object> 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 (file)
index 0000000..aa37314
--- /dev/null
@@ -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> 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 (file)
index 0000000..c061132
--- /dev/null
@@ -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<String, Object> 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 (file)
index 0000000..b8d6cf8
--- /dev/null
@@ -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<String, Object> 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<String, Object> 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 (file)
index 0000000..b518877
--- /dev/null
@@ -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> 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 (file)
index 0000000..5a768dd
--- /dev/null
@@ -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 (file)
index 0000000..8c793f5
--- /dev/null
@@ -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 (file)
index 0000000..9241183
--- /dev/null
@@ -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<String, Object> 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 (file)
index 0000000..5a4a402
--- /dev/null
@@ -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;
+       }
+
+}
index bbc5b4d99fb19a673cba7503171ebe9c371152e0..a1b4c4ece019162a1bfc27d3c035ef37315b81cf 100644 (file)
@@ -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 (file)
index 0000000..afa61de
--- /dev/null
@@ -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);
+                       }
+               }
+       }
+
+}