Joakim,

Thank you, as always, for your help.

Your solution looks interesting, and I will have to re-read it later with a clear mind, but going over it, I still need to register JsrManualEndpointWithServletContext as a Listener, meaning two things:

1) It has to be in a jar on the classpath, so a separate jar from my bundle

2) It has to be registered as a Listener (I know that it can be done via annotations but many users disable the jar scanning to improve startup time), so an edit to web.xml.

I like the fact that your solution doesn't create HttpSession objects unnecessarily (Lucee implements its own Session Management), but if the above statement is true then I will stick for now with my current working implementation which uses a simple Servlet Filter to initialize the HttpSession, which is then used during the Handshake to retrieve the ServletContext.

Also,

ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(MyEndpoint.class, "/ws/*")
.configurator(new MyEndpointConfigurator(context))
                .build();

That would not work for me as the endpoint path "/ws/*" is not known at compile time. I used it as an example of the HttpSessionInitializerFilter to show users how to set it up for specific URIs rather than every URI, because of the added overhead in creating an HttpSession object.

The Lucee developer may set any endpoint path in his code, and it is only known to the servlet at execution time. Unless we can use "/*"? I'm not sure of the overhead that is added, as I can not find any documentation about using a wildcard in the endpoint path.

Thanks again,


Igal Sapir
Lucee Core Developer
Lucee.org <http://lucee.org/>

On 7/18/2017 10:28 AM, Joakim Erdfelt wrote:
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

_______________________________________________
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