diff --git a/http/README.md b/http/README.md
index 8f3741b859..ed71f528d2 100644
--- a/http/README.md
+++ b/http/README.md
@@ -442,6 +442,7 @@ properties can be used (some legacy property names still exist but are not docum
| `org.apache.felix.jetty.websocket.enable` | Enables Jetty websocket support. Default is false. |
| `org.apache.felix.http.jetty.threadpool.max` | The maximum number of threads in the Jetty thread pool. Default is unlimited. Works for both platform threads and virtual threads (Jetty 12 only). |
| `org.apache.felix.http.jetty.virtualthreads.enable` | Enables using virtual threads in Jetty 12 (JDK 21 required). Default is false. When enabled, `org.apache.felix.http.jetty.threadpool.max` is used for a bounded virtual thread pool. |
+
### Multiple Servers
It is possible to configure several Http Services, each running on a different port. The first service can be configured as outlined above using the service PID for `"org.apache.felix.http"`. Additional servers can be configured through OSGi factory configurations using `"org.apache.felix.http"` as the factory PID. The properties for the configuration are outlined as above.
diff --git a/http/base/pom.xml b/http/base/pom.xml
index bd6eeea198..1ae70a29af 100644
--- a/http/base/pom.xml
+++ b/http/base/pom.xml
@@ -157,5 +157,11 @@
5.7.0
test
+
+ org.eclipse.jetty.ee10.websocket
+ jetty-ee10-websocket-jetty-server
+ 12.0.16
+ test
+
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
index 96439f5b1a..e8658faa84 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
@@ -145,6 +145,7 @@ public void register(@NotNull final ServletContext containerContext, @NotNull fi
*/
public void setAttributeSharedServletContext(String key, Object value) {
this.whiteboardManager.setAttributeSharedServletContext(key, value);
+ this.httpServiceFactory.setAttributeSharedServletContext(key, value);
}
/**
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceServletHandler.java
index 255ad55551..d84a0e120e 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceServletHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceServletHandler.java
@@ -26,7 +26,7 @@
/**
* Servlet handler for servlets registered through the http service.
*/
-public final class HttpServiceServletHandler extends ServletHandler
+public class HttpServiceServletHandler extends ServletHandler
{
/**
* New handler
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceWebSocketServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceWebSocketServletHandler.java
new file mode 100644
index 0000000000..8d99962538
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpServiceWebSocketServletHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.handler;
+
+import java.io.IOException;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.runtime.ServletInfo;
+
+/**
+ * Servlet handler for servlets extending JettyWebSocketServlet registered through the http service.
+ */
+public final class HttpServiceWebSocketServletHandler extends HttpServiceServletHandler
+{
+ private final WebSocketHandler webSocketHandler;
+
+ public HttpServiceWebSocketServletHandler(final ExtServletContext context,
+ final ServletInfo servletInfo,
+ final javax.servlet.Servlet servlet)
+ {
+ super(context, servletInfo, servlet);
+ this.webSocketHandler = new WebSocketHandler(this);
+ }
+
+ @Override
+ public int init() {
+ if (webSocketHandler.shouldInit()) {
+ return super.init();
+ }
+ // do nothing, delay init until first service call
+ return -1;
+ }
+
+ @Override
+ public void handle(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+ this.webSocketHandler.lazyInit();
+ super.handle(req, res);
+ }
+
+ @Override
+ public boolean destroy() {
+ if (webSocketHandler.shouldDestroy()) {
+ return super.destroy();
+ }
+ return false;
+ }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
index 5a9c97a0ac..4254ce6a93 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
@@ -180,7 +180,7 @@ public int init()
catch (final Exception e)
{
SystemLogger.LOGGER.error(SystemLogger.formatMessage(this.getServletInfo().getServiceReference(),
- "Error during calling init() on servlet ".concat(this.servletInfo.getClassName(this.servlet))),
+ "Error during calling init() on servlet ".concat(this.servletInfo.getClassName(this.servlet))),
e);
return DTOConstants.FAILURE_REASON_EXCEPTION_ON_INIT;
}
@@ -188,7 +188,6 @@ public int init()
return -1;
}
-
public boolean destroy()
{
if (this.servlet == null)
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WebSocketHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WebSocketHandler.java
new file mode 100644
index 0000000000..89b58d7c3f
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WebSocketHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.http.base.internal.handler;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+
+/**
+ * Class that handles initialization for servlets extending JettyWebSocketServlet.
+ */
+public final class WebSocketHandler {
+ // The Jetty class used for Jetty WebSocket servlets
+ private static final String JETTY_WEB_SOCKET_SERVLET_CLASS = "JettyWebSocketServlet";
+
+ private final AtomicBoolean lazyFirstInitCall = new AtomicBoolean(true);
+ private final CountDownLatch initBarrier = new CountDownLatch(1);
+ private final ServletHandler servletHandler;
+
+ public WebSocketHandler(ServletHandler servletHandler) {
+ this.servletHandler = servletHandler;
+ }
+
+ /*
+ * Lazy initialization of the servlet.
+ * Will only be called once for each servlet instance and is thread-safe.
+ */
+ public void lazyInit() {
+ if (lazyFirstInitCall.compareAndSet(true, false)) {
+ try {
+ this.servletHandler.init();
+ } catch (final Exception e) {
+ SystemLogger.LOGGER.error(SystemLogger.formatMessage(
+ this.servletHandler.getServletInfo().getServiceReference(),
+ "Error calling init() lazy on servlet ".concat(
+ this.servletHandler.getServletInfo().getClassName(this.servletHandler.getServlet()))), e);
+ } finally {
+ initBarrier.countDown();
+ }
+ } else {
+ // already initialized, await the first initialization
+ try {
+ initBarrier.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the servlet should be initialized, false otherwise.
+ * @return true if the servlet should be initialized, false otherwise
+ */
+ public boolean shouldInit() {
+ return !lazyFirstInitCall.get() && initBarrier.getCount() > 0;
+ }
+
+ /**
+ * Returns true if the servlet was initialized earlier, false otherwise.
+ * @return true if the servlet should be destroyed, false otherwise
+ */
+ public boolean shouldDestroy() {
+ if (!lazyFirstInitCall.get()){
+ try {
+ initBarrier.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if the servlet is a JettyWebSocketServlet.
+ * JettyWebSocket classes are handled differently due to FELIX-6746.
+ * @param servlet the servlet to check
+ * @return true if the servlet is a JettyWebSocketServlet, false otherwise
+ */
+ public static boolean isJettyWebSocketServlet(Object servlet) {
+ final Class> superClass = servlet.getClass().getSuperclass();
+ SystemLogger.LOGGER.debug("Checking if the servlet is a JettyWebSocketServlet: '" + superClass.getSimpleName() + "'");
+
+ // Now check if the servlet class extends 'JettyWebSocketServlet'
+ boolean isJettyWebSocketServlet = superClass.getSimpleName().endsWith(JETTY_WEB_SOCKET_SERVLET_CLASS);
+ if (!isJettyWebSocketServlet) {
+ // Recurse through the wrapped servlets, in case of double-wrapping
+ if (servlet instanceof org.apache.felix.http.jakartawrappers.ServletWrapper) {
+ final javax.servlet.Servlet wrappedServlet = ((org.apache.felix.http.jakartawrappers.ServletWrapper) servlet).getServlet();
+ return isJettyWebSocketServlet(wrappedServlet);
+ } else if (servlet instanceof org.apache.felix.http.javaxwrappers.ServletWrapper) {
+ final jakarta.servlet.Servlet wrappedServlet = ((org.apache.felix.http.javaxwrappers.ServletWrapper) servlet).getServlet();
+ return isJettyWebSocketServlet(wrappedServlet);
+ }
+ }
+ return isJettyWebSocketServlet;
+ }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardServletHandler.java
index 5fae620ab5..8427da5535 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardServletHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardServletHandler.java
@@ -29,7 +29,7 @@
/**
* Servlet handler for servlets registered through the http whiteboard.
*/
-public final class WhiteboardServletHandler extends ServletHandler
+public class WhiteboardServletHandler extends ServletHandler
{
private final BundleContext bundleContext;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardWebSocketServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardWebSocketServletHandler.java
new file mode 100644
index 0000000000..dba9de191e
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/WhiteboardWebSocketServletHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.handler;
+
+import java.io.FilePermission;
+import java.io.IOException;
+
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.apache.felix.http.base.internal.runtime.ServletInfo;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.servlet.runtime.dto.DTOConstants;
+
+/**
+ * Servlet handler for servlets extending JettyWebSocketServlet registered through the http whiteboard.
+ */
+public final class WhiteboardWebSocketServletHandler extends WhiteboardServletHandler
+{
+ private final WebSocketHandler webSocketHandler;
+
+ public WhiteboardWebSocketServletHandler(final long contextServiceId,
+ final ExtServletContext context,
+ final ServletInfo servletInfo,
+ final BundleContext contextBundleContext,
+ final Bundle registeringBundle,
+ final Bundle httpWhiteboardBundle,
+ final Object servlet)
+ {
+ super(contextServiceId, context, servletInfo, contextBundleContext, registeringBundle, httpWhiteboardBundle);
+ this.webSocketHandler = new WebSocketHandler(this);
+ this.setServlet((Servlet) servlet);
+ }
+
+ @Override
+ public int init() {
+ if (webSocketHandler.shouldInit()) {
+ return super.init();
+ }
+ // do nothing, delay init until first service call
+ return -1;
+ }
+
+ @Override
+ public void handle(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+ this.webSocketHandler.lazyInit();
+ super.handle(req, res);
+ }
+
+ @Override
+ public boolean destroy() {
+ if (webSocketHandler.shouldDestroy()) {
+ return super.destroy();
+ }
+ return false;
+ }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceFactory.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceFactory.java
index 7ff37e0309..c5f7b1d8a3 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceFactory.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceFactory.java
@@ -16,10 +16,14 @@
*/
package org.apache.felix.http.base.internal.service;
+import java.util.HashMap;
import java.util.Hashtable;
+import java.util.Map;
+import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.apache.felix.http.base.internal.registry.HandlerRegistry;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
@@ -76,6 +80,7 @@ public final class HttpServiceFactory
private final HandlerRegistry handlerRegistry;
private volatile SharedHttpServiceImpl sharedHttpService;
+ private volatile Map attributesForSharedContext = new HashMap<>();
public HttpServiceFactory(final BundleContext bundleContext,
final HandlerRegistry handlerRegistry)
@@ -101,6 +106,7 @@ public void start(final ServletContext context,
this.context = context;
this.sharedHttpService = new SharedHttpServiceImpl(handlerRegistry);
+ this.sharedHttpService.setSharedContextAttributes(attributesForSharedContext);
this.active = true;
this.httpServiceReg = bundleContext.registerService(HttpService.class, this, this.httpServiceProps);
@@ -120,6 +126,7 @@ public void stop()
this.sharedHttpService = null;
this.httpServiceProps.clear();
+ this.attributesForSharedContext.clear();
}
@Override
@@ -160,6 +167,11 @@ public long getHttpServiceServiceId()
return (Long) this.httpServiceReg.getReference().getProperty(Constants.SERVICE_ID);
}
+ public void setAttributeSharedServletContext(String key, Object value) {
+ SystemLogger.LOGGER.info("HttpServiceFactory: Storing attribute for shared servlet context. Key '{}', value: '{}'", key, value);
+ this.attributesForSharedContext.put(key, value);
+ }
+
private boolean getBoolean(final String property)
{
String prop = this.bundleContext.getProperty(property);
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
index d96c656375..cb7ed32ead 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
@@ -16,13 +16,17 @@
*/
package org.apache.felix.http.base.internal.service;
+import static org.apache.felix.http.base.internal.handler.WebSocketHandler.isJettyWebSocketServlet;
+
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.felix.http.base.internal.context.ExtServletContext;
import org.apache.felix.http.base.internal.handler.HttpServiceServletHandler;
+import org.apache.felix.http.base.internal.handler.HttpServiceWebSocketServletHandler;
import org.apache.felix.http.base.internal.handler.ServletHandler;
+import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.apache.felix.http.base.internal.registry.HandlerRegistry;
import org.apache.felix.http.base.internal.runtime.ServletInfo;
import org.apache.felix.http.jakartawrappers.ServletWrapper;
@@ -38,6 +42,7 @@ public final class SharedHttpServiceImpl
private final HandlerRegistry handlerRegistry;
private final Map aliasMap = new HashMap<>();
+ private Map attributesForSharedContext;
/**
* Create a new implementation
@@ -67,7 +72,10 @@ public void registerServlet(@NotNull final String alias,
@NotNull final javax.servlet.Servlet servlet,
@NotNull final ServletInfo servletInfo) throws javax.servlet.ServletException, NamespaceException
{
- final ServletHandler handler = new HttpServiceServletHandler(httpContext, servletInfo, servlet);
+ final ServletHandler handler = getServletHandler(httpContext, servlet, servletInfo);
+
+ // Track properties from shared context
+ setAttributes(httpContext);
synchronized (this.aliasMap)
{
@@ -81,6 +89,19 @@ public void registerServlet(@NotNull final String alias,
}
}
+ @NotNull
+ private static HttpServiceServletHandler getServletHandler(
+ @NotNull ExtServletContext httpContext,
+ @NotNull javax.servlet.Servlet servlet,
+ @NotNull ServletInfo servletInfo)
+ {
+ if (isJettyWebSocketServlet(servlet))
+ {
+ return new HttpServiceWebSocketServletHandler(httpContext, servletInfo, servlet);
+ }
+ return new HttpServiceServletHandler(httpContext, servletInfo, servlet);
+ }
+
/**
* Unregister a servlet
* @param alias The alias
@@ -155,4 +176,23 @@ public void unregisterServlet(final javax.servlet.Servlet servlet)
{
return this.handlerRegistry;
}
+
+ public void setSharedContextAttributes(Map attributesForSharedContext) {
+ this.attributesForSharedContext = attributesForSharedContext;
+ }
+
+ /**
+ * Set the stored attributes on the servlet context.
+ * @param context the servlet context
+ */
+ private void setAttributes(ExtServletContext context) {
+ if (context != null && attributesForSharedContext != null) {
+ attributesForSharedContext.forEach((key, value) -> {
+ if (key != null && value != null) {
+ SystemLogger.LOGGER.info("SharedHttpServiceImpl: Shared context found, setting stored attribute key: '{}', value: '{}'", key, value);
+ context.setAttribute(key, value);
+ }
+ });
+ }
+ }
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
index cb48cb9a53..7f1cb5f4b6 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
@@ -16,6 +16,7 @@
*/
package org.apache.felix.http.base.internal.whiteboard;
+import static org.apache.felix.http.base.internal.handler.WebSocketHandler.isJettyWebSocketServlet;
import static org.osgi.service.servlet.runtime.dto.DTOConstants.FAILURE_REASON_NO_SERVLET_CONTEXT_MATCHING;
import static org.osgi.service.servlet.runtime.dto.DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE;
import static org.osgi.service.servlet.runtime.dto.DTOConstants.FAILURE_REASON_UNKNOWN;
@@ -39,7 +40,9 @@
import org.apache.felix.http.base.internal.handler.ListenerHandler;
import org.apache.felix.http.base.internal.handler.PreprocessorHandler;
import org.apache.felix.http.base.internal.handler.ServletHandler;
+import org.apache.felix.http.base.internal.handler.WebSocketHandler;
import org.apache.felix.http.base.internal.handler.WhiteboardServletHandler;
+import org.apache.felix.http.base.internal.handler.WhiteboardWebSocketServletHandler;
import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.apache.felix.http.base.internal.registry.EventListenerRegistry;
import org.apache.felix.http.base.internal.registry.HandlerRegistry;
@@ -416,7 +419,7 @@ private void setAttributes(@Nullable ServletContext context) {
if (context != null) {
attributesForSharedContext.forEach((key, value) -> {
if (key != null && value != null) {
- SystemLogger.LOGGER.info("Shared context found, setting stored attribute key: '{}', value: '{}'", key, value);
+ SystemLogger.LOGGER.info("WhiteboardManager: Shared context found, setting stored attribute key: '{}', value: '{}'", key, value);
context.setAttribute(key, value);
}
});
@@ -726,13 +729,7 @@ private void registerWhiteboardService(final WhiteboardContextHandler handler, f
}
else
{
- final ServletHandler servletHandler = new WhiteboardServletHandler(
- handler.getContextInfo().getServiceId(),
- servletContext,
- (ServletInfo)info,
- handler.getBundleContext(),
- info.getServiceReference().getBundle(),
- this.httpBundleContext.getBundle());
+ final ServletHandler servletHandler = getServletHandler(handler, info, servletContext);
handler.getRegistry().registerServlet(servletHandler);
}
}
@@ -805,6 +802,31 @@ else if ( info instanceof ListenerInfo )
}
}
+ @NotNull
+ private WhiteboardServletHandler getServletHandler(WhiteboardContextHandler handler,
+ WhiteboardServiceInfo> info,
+ ExtServletContext servletContext)
+ {
+ Object servlet = info.getService(handler.getBundleContext());
+ if (isJettyWebSocketServlet(servlet))
+ {
+ return new WhiteboardWebSocketServletHandler(
+ handler.getContextInfo().getServiceId(),
+ servletContext,
+ (ServletInfo) info,
+ handler.getBundleContext(),
+ info.getServiceReference().getBundle(),
+ this.httpBundleContext.getBundle(), servlet);
+ }
+ return new WhiteboardServletHandler(
+ handler.getContextInfo().getServiceId(),
+ servletContext,
+ (ServletInfo) info,
+ handler.getBundleContext(),
+ info.getServiceReference().getBundle(),
+ this.httpBundleContext.getBundle());
+ }
+
/**
* Unregister whiteboard service from the http service
* @param handler Context handler
@@ -981,7 +1003,7 @@ private void updateRuntimeChangeCount()
* @param value attribute value
*/
public void setAttributeSharedServletContext(String key, Object value) {
- SystemLogger.LOGGER.info("Storing attribute for shared servlet context. Key '{}', value: '{}'", key, value);
+ SystemLogger.LOGGER.info("WhiteboardManager: Storing attribute for shared servlet context. Key '{}', value: '{}'", key, value);
this.attributesForSharedContext.put(key, value);
}
}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/WebSocketHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/WebSocketHandlerTest.java
new file mode 100644
index 0000000000..e635a50f1d
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/WebSocketHandlerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.handler;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.apache.felix.http.javaxwrappers.ServletWrapper;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
+import org.junit.Before;
+import org.junit.Test;
+
+public class WebSocketHandlerTest {
+ private javax.servlet.Servlet javaxServlet;
+ private jakarta.servlet.Servlet jakartaServlet;
+ private JettyWebSocketServlet jakartaWebSocketServlet;
+
+ @Before
+ public void setUp()
+ {
+ this.javaxServlet = mock(javax.servlet.Servlet.class);
+ this.jakartaServlet = mock(jakarta.servlet.Servlet.class);
+ this.jakartaWebSocketServlet = mock(JettyWebSocketServlet.class);
+ }
+
+ @Test
+ public void isJettyWebSocketServlet(){
+ assertFalse(WebSocketHandler.isJettyWebSocketServlet(this.javaxServlet));
+ assertFalse(WebSocketHandler.isJettyWebSocketServlet(this.jakartaServlet));
+
+ // See test scope dependency in pom.xml
+ assertTrue(WebSocketHandler.isJettyWebSocketServlet(this.jakartaWebSocketServlet));
+
+ // Also works with the wrapper
+ assertTrue(WebSocketHandler.isJettyWebSocketServlet(new ServletWrapper(this.jakartaWebSocketServlet)));
+ }
+}
diff --git a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySpecificWebsocketIT.java b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySpecificWebsocketIT.java
index 63503f6e2b..27b33bc9d0 100644
--- a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySpecificWebsocketIT.java
+++ b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySpecificWebsocketIT.java
@@ -20,10 +20,12 @@
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+import static org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
+import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -35,10 +37,13 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
+import org.apache.felix.http.javaxwrappers.ServletWrapper;
import org.awaitility.Awaitility;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
@@ -54,7 +59,6 @@
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.osgi.framework.BundleContext;
import org.osgi.service.http.HttpService;
-import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants;
/**
*
@@ -66,6 +70,9 @@ public class JettySpecificWebsocketIT extends AbstractJettyTestSupport {
@Inject
protected BundleContext bundleContext;
+ @Inject
+ protected HttpService httpService;
+
@Override
protected Option[] additionalOptions() throws IOException {
String jettyVersion = System.getProperty("jetty.version", JETTY_VERSION);
@@ -100,14 +107,46 @@ protected Option felixHttpConfig(int httpPort) {
.asOption();
}
-
@Test
public void testWebSocketConversation() throws Exception {
assertNotNull(bundleContext);
bundleContext.registerService(Servlet.class, new MyWebSocketInitServlet(), new Hashtable<>(Map.of(
- HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/mywebsocket1"
+ HTTP_WHITEBOARD_SERVLET_PATTERN, "/mywebsocket1"
)));
+ assertWebSocketResponse("mywebsocket1");
+ }
+
+ @Test
+ public void testWebSocketServletWhiteboard() throws Exception {
+ final JettyWebSocketServlet webSocketServlet = new JettyWebSocketServlet() {
+ @Override
+ protected void configure(JettyWebSocketServletFactory jettyWebSocketServletFactory) {
+ jettyWebSocketServletFactory.register(MyServerWebSocket.class);
+ }
+ };
+ final Dictionary props = new Hashtable<>();
+ props.put(HTTP_WHITEBOARD_SERVLET_PATTERN, "/websocketservletwhiteboard");
+ bundleContext.registerService(Servlet.class, webSocketServlet, props);
+
+ assertWebSocketResponse("websocketservletwhiteboard");
+ }
+
+ @Test
+ public void testWebSocketServletHttpService() throws Exception {
+ final JettyWebSocketServlet webSocketServlet = new JettyWebSocketServlet() {
+ @Override
+ protected void configure(JettyWebSocketServletFactory jettyWebSocketServletFactory) {
+ jettyWebSocketServletFactory.register(MyServerWebSocket.class);
+ }
+ };
+
+ httpService.registerServlet("/websocketservlethttpservice", new ServletWrapper(webSocketServlet), null, null);
+
+ assertWebSocketResponse("websocketservlethttpservice");
+ }
+
+ private void assertWebSocketResponse(String servletPath) throws Exception {
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP();
HttpClient httpClient = new org.eclipse.jetty.client.HttpClient(transport);
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
@@ -115,7 +154,7 @@ public void testWebSocketConversation() throws Exception {
Object value = bundleContext.getServiceReference(HttpService.class).getProperty("org.osgi.service.http.port");
int httpPort = Integer.parseInt((String)value);
- URI destUri = new URI(String.format("ws://localhost:%d/mywebsocket1", httpPort));
+ URI destUri = new URI(String.format("ws://localhost:%d/%s", httpPort, servletPath));
MyClientWebSocket clientWebSocket = new MyClientWebSocket();
ClientUpgradeRequest request = new ClientUpgradeRequest();
diff --git a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
deleted file mode 100644
index 55af0f1863..0000000000
--- a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.felix.http.samples.whiteboard;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-
-import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
-
-/**
- * Abstract class that hides all Jetty Websocket specifics and provides a way for the developer to focus on the actual WebSocket implementation.
- */
-public abstract class FelixJettyWebSocketServlet extends JettyWebSocketServlet {
- private final AtomicBoolean myFirstInitCall = new AtomicBoolean(true);
- private final CountDownLatch myInitBarrier = new CountDownLatch(1);
-
- public final void init() {
- // nothing, see delayed init below in service method
- // this is a workaround as stated in https://issues.apache.org/jira/browse/FELIX-5310
- }
-
- @Override
- public void service(final ServletRequest req, final ServletResponse res) throws ServletException, IOException {
- if (myFirstInitCall.compareAndSet(true, false)) {
- try {
- super.init();
- } finally {
- myInitBarrier.countDown();
- }
- } else {
- try {
- myInitBarrier.await();
- } catch (final InterruptedException e) {
- throw new ServletException("Timed out waiting for initialisation", e);
- }
- }
-
- super.service(req, res);
- }
-
- /**
- * Cleanup method.
- */
- @Override
- public final void destroy() {
- // only call destroy when the servlet has been initialized
- if (!myFirstInitCall.get()) {
- // This is required because WebSocketServlet needs to have it's destroy() method called as well
- // Causes NPE otherwise when calling an WS endpoint
- super.destroy();
- }
- }
-}
diff --git a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
index e916a9a3ff..133fc1b4fc 100644
--- a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
+++ b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
@@ -19,6 +19,7 @@
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
@@ -30,10 +31,10 @@
/**
* Example of a WebSocket servlet that uses the Jetty WebSocket API, and is registered by extending JettyWebSocketServlet.
- * It does respect the path this servlet is registered to, but requires a further workaround. See FelixJettyWebSocketServlet.
+ * It does respect the path this servlet is registered to.
* Requires setting `org.apache.felix.jetty.websocket.enable=true`.
*/
-public class TestWebSocketServletAlternative extends FelixJettyWebSocketServlet {
+public class TestWebSocketServletAlternative extends JettyWebSocketServlet {
private final String name;
public TestWebSocketServletAlternative(String name) {