Sticking with JSR-356, you basically have the
javax.websocket.server.ServerEndpointConfig.Configurator as the means
to create a WebSocket instance with the knowledge you need from the
javax.servlet.ServletContext
(The easy way would be to use auto-wiring in Spring, Guice, or CDI to
accomplish this. But I'll assume you are not using those technologies.)
There's really only 1 reliable place to do this with JSR-356 that I
can think of, and that's during Servlet context init, using a manual
addition of the WebSocket endpoint to the
javax.websocket.server.ServerContainer.
This is how it works ...
You have a javax.servlet.ServletContextListener
or a javax.servlet.ServletContainerInitializer
That obtains the reference to the
javax.websocket.server.ServerContainer and then adds manually adds an
Endpoint with a ServerEndpointConfig that has a built-up
ServerEndpointConfig.Configurator with a reference to the
javax.servlet.ServletContext (Obtained at the context init timeframe).
Note: Do not use annotated websocket endpoints, as the annotations
will be auto discovered and auto deployed (along with the annotation
provided configurator)
Something like this ...
package jetty.websocket;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
public class JsrManualEndpointWithServletContext implements
ServletContextListener
{
public static class MyEndpoint extends Endpoint
{
private final ServletContext context;
public MyEndpoint(ServletContext context)
{
this.context = context;
}
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(new MessageHandler.Whole<String>()
{
@Override
public void onMessage(String message)
{
session.getAsyncRemote().sendText("Context Path: " +
context.getContextPath());
}
});
}
}
public static class MyEndpointConfigurator extends
ServerEndpointConfig.Configurator
{
private final ServletContext context;
public MyEndpointConfigurator(ServletContext context)
{
this.context = context;
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass)
throws InstantiationException
{
return (T) new MyEndpoint(context);
}
}
public void contextInitialized(ServletContextEvent sce)
{
ServletContext context = sce.getServletContext();
ServerEndpointConfig endpointConfig =
ServerEndpointConfig.Builder.create(MyEndpoint.class, "/ws/*")
.configurator(new MyEndpointConfigurator(context))
.build();
ServerContainer wsContainer = (ServerContainer)
context.getAttribute(ServerContainer.class.getName());
try
{
wsContainer.addEndpoint(endpointConfig);
}
catch (DeploymentException e)
{
// Let container know that something seriously wrong occurred.
throw new RuntimeException("Unable to deploy websocket", e);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
}
Joakim Erdfelt / [email protected] <mailto:[email protected]>
On Tue, Jul 18, 2017 at 9:50 AM, Igal @ Lucee.org <[email protected]
<mailto:[email protected]>> wrote:
Jan,
Thank you for the sanity check! I kept (in a way still keep)
thinking that I missed something simple. I guess it's possible
that the "Expert Group" missed it though, and not me.
I hope that Joakim would have a better solution. I must say here
though, that much of my JSR-356 knowledge comes from Joakim's old
posts in mailing list archives and on StackOverflow, so thank you,
Joakim, for that!
Best,
Igal Sapir
Lucee Core Developer
Lucee.org <http://lucee.org/>
On 7/18/2017 2:05 AM, Jan Bartel wrote:
Igal,
Thanks for that information, that makes things much clearer!
I think you've hit on a reasonable, if clunky, solution given
that the jsr-356 interface does not seem to explicitly pass the
ServletContext anywhere.
Joakim, this is more your area of expertise - got any other
suggestion on how to solve the problem of using websocket as a
service for many different ServletContexts?
good luck,
Jan
On 14 July 2017 at 19:54, Igal @ Lucee.org <[email protected]
<mailto:[email protected]>> wrote:
Jan,
Thank you for replying. Here is the scenario:
We have a FOSS project named Lucee, which is a Servlet based
on JSP, which allows for rapid development of web
applications because it's much simpler to use and easier to
learn than JSP/JSF, for example. The project is used by many
developers and organizations all over the world.
Lucee allows for extensions (or plugins), which are loaded
via OSGi, so that they can be loaded/unloaded/updated on
demand without restarting the Servlet container, as well as
use different versions of the same 3rd party libraries side
by side.
I wrote an open source extension for Lucee which provides
WebSocket support: https://github.com/isapir/lucee-websocket
<https://github.com/isapir/lucee-websocket> -- The extension
has the following requirements:
1) Be packaged as an OSGi bundle so that it can be
installed as a Lucee extension.
2) Be JSR-356 compliant because Lucee could run on any
Servlet container, so I can not use Jetty- or Tomcat-
specific APIs.
3) Allow for WebSocket configuration to be set at
runtime, as we do not know in advance what endpoint etc. the
developer will want to use.
4) Be able to communicate with the Lucee servlet for
Application and Session states.
In order to be able to retrieve the correct state from Lucee
(requirement 4) my code needs to know the root directory of
the ServletContext, e.g. servletContext.getRealPath("/"), at
the Handshake phase. This is required so that multiple
contexts can maintain their separate states without "mixing"
up state between the contexts, so that if there are two
contexts, ServletContext1 and ServletContext2, a user that
connects to ServletContext2 he will get the state from that
context, and not the one from ServletContext1.
The only way that I found to retrieve the ServletContext
through the JSR-356 spec (requirement 2) is via the
HttpSession in
ServerEndpointConfig.Configurator.modifyHandshake(), i.e.
handshakeRequest.getHttpSession().getServletContext().getRealPath("/").
The problem is, that according to the JSR, the Session should
not be initialized in handshakeRequest.getHttpSession() if
one does not exist, so by default,
handshakeRequest.getHttpSession() always returns null.
The "recommended" way to initialize the Session is with a
ServletRequestListener, where a simple call to
httpServletRequest.getHttpSession() does initialize the
HttpSession object. I have provided a class to do that --
https://github.com/isapir/lucee-websocket/blob/master/src/main/java/net/twentyonesolutions/servlet/listener/HttpSessionInitializer.java#L40
<https://github.com/isapir/lucee-websocket/blob/master/src/main/java/net/twentyonesolutions/servlet/listener/HttpSessionInitializer.java#L40>
-- but because this is an OSGi bundle (requirement 1), this
jar is not on the classpath -- and can not be specified as-is
in the web descriptor.
The solution that I came up with, is to add a tiny jar file
to the classpath with a Servlet Filter that initializes the
HttpSession --
https://github.com/isapir/servlet-filter-utils/blob/master/src/main/java/net/twentyonesolutions/servlet/filter/HttpSessionInitializerFilter.java#L70
<https://github.com/isapir/servlet-filter-utils/blob/master/src/main/java/net/twentyonesolutions/servlet/filter/HttpSessionInitializerFilter.java#L70>
-- Then the user adds the tiny jar to the classpath, and
configures the Filter to intercept WebSocket URIs.
But there has to be a simpler method than that, no? Again,
my ultimate goal here is to get the ServletContext's root
directory, so if there is another way to do that then I would
not need to jump through all those hoops. If not, I am
looking to register the ServletRequestListener so that the
user will not need to add another jar and/or modify web.xml.
Thank you,
Igal Sapir
Lucee Core Developer
Lucee.org <http://lucee.org/>
On 7/14/2017 12:23 AM, Jan Bartel wrote:
Igal,
Some more description of exactly what your setup is would be
good: do you have a bundle that is a traditional war? And
the websocket code bundle is external to the main webapp
bundle? What bundle is the "servlet" in? What does the
websocket bundle do? Without knowing more it is difficult to
give a cogent answer.
Assuming you have another bundle that contains a web.xml,
you could either put that listener definition in there (so
long as you manually get the manifest import statements
correct on the web bundle to refer to your websocket
bundle); or you could define a context listener in there
whose job it is to register the request listener
programmatically - that way tools like bnd will generate
correct manifest import statements for you. I don't know if
you want to be able to selectively enable this listener or
not, or based on what configuration ....
If you provide more info, then maybe I can suggest something
else.
cheers
Jan
On 13 July 2017 at 19:21, Igal @ Lucee.org <[email protected]
<mailto:[email protected]>> wrote:
Hello,
I have a JSR-356 (WebSocket API) code that is packaged
in an OSGi bundle. The servlet loads the code via Apache
Felix if needed.
I want to register a ServletRequestListener, which I
would normally put in the web descriptor:
<listener>
<listener-class>path.to.my
<http://path.to.my>.RequestListener</listener-class>
</listener>
but since the bundle is not in the classpath that
wouldn't work.
How can I register the ServletRequestListener? I am
thinking that maybe there would be a way with scanning
for annotations but am not sure how to set that up. My
other concern is that many users disable the scanning to
improve startup time.
The listener's job is to initialize HttpSession so that
I can retrieve the ServletContext in the WebSocket
handshake. If there's a way to achieve that without the
listener then that will work for me even better.
Any ideas? Thanks!
p.s. This is a crosspost with
https://stackoverflow.com/questions/45083982/register-servletrequestlistener-from-osgi-bundle
<https://stackoverflow.com/questions/45083982/register-servletrequestlistener-from-osgi-bundle>
Igal Sapir
Lucee Core Developer
Lucee.org <http://lucee.org/>
_______________________________________________
jetty-users mailing list
[email protected] <mailto:[email protected]>
To change your delivery options, retrieve your password,
or unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users
<https://dev.eclipse.org/mailman/listinfo/jetty-users>
--
Jan Bartel <[email protected] <mailto:[email protected]>>
www.webtide.com <http://www.webtide.com>
/Expert assistance from the creators of Jetty and CometD/
_______________________________________________
jetty-users mailing list
[email protected] <mailto:[email protected]>
To change your delivery options, retrieve your password, or unsubscribe
from this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users
<https://dev.eclipse.org/mailman/listinfo/jetty-users>
_______________________________________________
jetty-users mailing list
[email protected] <mailto:[email protected]>
To change your delivery options, retrieve your password, or
unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users
<https://dev.eclipse.org/mailman/listinfo/jetty-users>
--
Jan Bartel <[email protected] <mailto:[email protected]>>
www.webtide.com <http://www.webtide.com>
/Expert assistance from the creators of Jetty and CometD/
_______________________________________________
jetty-users mailing list
[email protected] <mailto:[email protected]>
To change your delivery options, retrieve your password, or unsubscribe
from this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users
<https://dev.eclipse.org/mailman/listinfo/jetty-users>
_______________________________________________
jetty-users mailing list
[email protected] <mailto:[email protected]>
To change your delivery options, retrieve your password, or
unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users
<https://dev.eclipse.org/mailman/listinfo/jetty-users>
_______________________________________________
jetty-users mailing list
[email protected]
To change your delivery options, retrieve your password, or unsubscribe from
this list, visit
https://dev.eclipse.org/mailman/listinfo/jetty-users