RCP: Registering Jetty websocket servlets via HttpServiceImpl is broken after Jetty upgrade 9.4-> [message #1850207] |
Mon, 21 February 2022 17:20 |
Balint Pamer Messages: 1 Registered: February 2022 |
Junior Member |
|
|
Hello!
After upgrading from Jetty 9.4->10.0.5, In my RCP-project, which registers servlets (both regular- and WebSocket-ones) in Jetty via the Equinox whiteboard HTTP service:
(i.e.: HttpServiceImpl.registerServlet())
I've noticed that the JettyWebSocketServlet's init()-calls started throwing exceptions, saying:
"WebSocketComponents has not been created"
Despite having written a JettyCustomizer, invoking:
JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
(This gets executed during HttpServerManager.updated(), so before the Jetty Server.start())
After lengthy debugging, the problem seems to be the Whiteboard HTTP service's ServletContextAdaptor creating a ProxyContext, which does *not* fall back onto the parent Jetty context, which has the necessary servlet attributes (the WebSocketComponents above)
After looking (and hacking) around, I've also noticed that the ServletContextAdaptor creates a dynamic proxy, which at the end of the day will throw an exception if I try to manually upgrade the WebSocket request, because under the hood, Jetty explicitly casts the to-be-upgraded ServletContext to its internal one (ContextHandler.Context):
https://git.eclipse.org/r/plugins/gitiles/equinox/rt.equinox.bundles/+/f58c0735df08bd1449282ab0e1c60caa12e9e570/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ServletContextAdaptor.java#85
public ServletContext createServletContext() {
Class<?> clazz = getClass();
ClassLoader curClassLoader = clazz.getClassLoader();
Class<?>[] interfaces = new Class[] {ServletContext.class};
return (ServletContext)Proxy.newProxyInstance(
curClassLoader, interfaces, new AdaptorInvocationHandler());
}
https://github.com/eclipse/jetty.project/blob/jetty-10.0.x/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/CreatorNegotiator.java#L63
((ContextHandler.Context)servletContext).getContextHandler().handle(() ->
result.set(creator.createWebSocket(upgradeRequest, upgradeResponse)));
So my question is: how are Jetty 10 WebSockets supposed to work in Eclipse:
Should one directly use Jetty, and forget the whiteboard HTTP service, or is there some code that can be added to add the Jetty-specific WebSocket components?
Thanks:
Balint
|
|
|
|
|
Re: RCP: Registering Jetty websocket servlets via HttpServiceImpl is broken after Jetty upgrade 9.4- [message #1852503 is a reply to message #1851817] |
Thu, 19 May 2022 06:57 |
Todor Dimitrov Messages: 2 Registered: April 2022 |
Junior Member |
|
|
We came back to this problem recently. Here is a servlet implementation, which seems to work:
class WebSocketServletImpl extends JettyWebSocketServlet {
private static final long serialVersionUID = -8292051630200394714L;
/** The websocket creator */
private final JettyWebSocketCreator creator;
/** The underlying Jetty context */
private ServletContextHandler context;
/** Used to get attributes from the Jetty context */
private ServletContext proxiedContext;
/**
* @param creator the non-null creator
*/
WebSocketServletImpl(JettyWebSocketCreator creator) {
if (creator == null)
throw new IllegalArgumentException("creator is null");
this.creator = creator;
}
@Override
public void init() throws ServletException {
final ServletContext osgiServletContext = super.getServletContext();
context = ServletContextHandler.getServletContextHandler(osgiServletContext, "WebSockets");
final JettyWebSocketServerContainer serverContainer = JettyWebSocketServerContainer
.getContainer(osgiServletContext);
if (serverContainer == null) {
final Context jettyServletContext = context.getServletContext();
WebSocketServerComponents.ensureWebSocketComponents(context.getServer(), jettyServletContext);
WebSocketUpgradeFilter.ensureFilter(jettyServletContext);
WebSocketMappings.ensureMappings(jettyServletContext);
JettyServerFrameHandlerFactory.getFactory(jettyServletContext);
JettyWebSocketServerContainer.ensureContainer(jettyServletContext);
}
super.init();
}
@Override
public synchronized ServletContext getServletContext() {
if (proxiedContext == null) {
proxiedContext = (ServletContext) Proxy.newProxyInstance(WebSocketServletImpl.class.getClassLoader(),
new Class[] { ServletContext.class }, (proxy, method, methodArgs) -> {
final ServletContext osgiServletContext = super.getServletContext();
if (!"getAttribute".equals(method.getName())) {
return method.invoke(osgiServletContext, methodArgs);
}
final String name = (String) methodArgs[0];
Object value = osgiServletContext.getAttribute(name);
if (value == null && context != null) {
final Context jettyServletContext = context.getServletContext();
value = jettyServletContext.getAttribute(name);
}
return value;
});
}
return proxiedContext;
}
@Override
public void configure(JettyWebSocketServletFactory factory) {
factory.setMaxBinaryMessageSize(1024 * 1024);
factory.setMaxTextMessageSize(128 * 1024);
// factory.setAsyncWriteTimeout(5000);
factory.setCreator(creator);
getServletContext().setAttribute(DecoratedObjectFactory.ATTR, new DecoratedObjectFactory());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final HttpServletRequest proxiedReq = (HttpServletRequest) Proxy.newProxyInstance(
WebSocketServletImpl.class.getClassLoader(), new Class[] { HttpServletRequest.class },
(proxy, method, methodArgs) -> {
if (!"getServletContext".equals(method.getName())) {
return method.invoke(req, methodArgs);
}
if (context != null)
return context.getServletContext();
return req.getServletContext();
});
super.service(proxiedReq, resp);
}
}
It is not pretty but gets the job done :-)
|
|
|
Powered by
FUDForum. Page generated in 0.03603 seconds