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]

On Tue, Jul 18, 2017 at 9:50 AM, Igal @ Lucee.org <[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]> 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 -- 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/HttpSessio
>> nInitializer.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/HttpSess
>> ionInitializerFilter.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]> 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.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/ques
>>> tions/45083982/register-servletrequestlistener-from-osgi-bundle
>>>
>>> Igal Sapir
>>> Lucee Core Developer
>>> Lucee.org <http://lucee.org/>
>>>
>>> _______________________________________________
>>> 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
>>>
>>
>>
>>
>> --
>> Jan Bartel <[email protected]>
>> www.webtide.com
>> *Expert assistance from the creators of Jetty and CometD*
>>
>>
>>
>> _______________________________________________
>> jetty-users mailing [email protected]
>> To change your delivery options, retrieve your password, or unsubscribe from 
>> this list, visithttps://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
>>
>
>
>
> --
> Jan Bartel <[email protected]>
> www.webtide.com
> *Expert assistance from the creators of Jetty and CometD*
>
>
>
> _______________________________________________
> jetty-users mailing [email protected]
> To change your delivery options, retrieve your password, or unsubscribe from 
> this list, visithttps://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
>
_______________________________________________
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

Reply via email to