Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tomcat WebSocketServlet and Google Guice

I have a webapp which requires the usage of Tomcat 7 web sockets.

In this webapp all standard Servlets (those extending javax.servlet.http.HttpServlet) work nicely (and correctly) with Google Guice. To make my Servlet work with Guice handlers I simply:

  1. decorate the servlet with @Singleton
  2. declare private Provider for MyHandler instance & generate a setter which is marked for injection
  3. decorate the Servlet's constructor with @Inject

Example to demonstrate points above:

@Singleton
public class MyServlet extends HttpServlet {

    private Provider<MyHandler> myHandler;

    @Inject
    MyServlet() {
    }

    @Override
    protected void service(..) throws ServletException { ... }

    @Inject
    public void setMyHandler(Provider<MyHandler> myHandler) {
        this.myHandler = myHandler;
    }

    ...
}

How can one call the same Guice handler, above called myHandler from a WebSocketServlet?

I can't adopt the same style as in the standard servlet use case because, rather than having a Singleton servlet as in the case of the standard servlets, each WebSocket communication results in an instance extending MessageInbound; then the appropriate method that would call MyHandler is called from a method (e.g. onOpen or onClose) within the MessageInbound instance; not from a method within an HttpServlet instance as MyServlet above.

What did I try? I did try some (conceptually wrong) solutions such as calling the websocket-servlet's handlers from within the MessageInbound instance; that of course results in scoping problems lower down the Guice stack trace. What is the conceptually correct way of doing this?

like image 459
Joseph Victor Zammit Avatar asked Dec 19 '12 17:12

Joseph Victor Zammit


1 Answers

Update after looking at GitHub example:

How you use Guice is just fine. Since there is just one particular usage of MessageInbound any sugar like with the AbstractGuiceWebSocketServlet is unnecessary. Provider chatLogHdlr and then doing manual construction is OK. But you lose AOP support. If that is needed you might want to do Assisted Inject. But for now this is fine.

On a side note, use construction injection instead of setter injection.

I saw immediately what is the problem. It is not Guice but rather how you use Guice-Persist. I didn't used GP a lot and still use the venerable Warp-persist. But I see 2 problems with how you use Guice-persist in your code:

  1. You need to inject the PersistService to start Guice-Persist. It is explained in the WIKI e.g.

    public class PocWebApp extends GuiceServletContextListener {
    
    @Inject
    PersistService ps;
    
    @Override
    protected Injector getInjector() {
        Injector injector = Guice.createInjector(new ServletModule() {
    
            @Override
            protected void configureServlets() {
                install(new JpaPersistModule("DesktopPU"));
                serve("/socket/main/").with(MainSocket.class);
            }
    
        });
        injector.injectMembers(this);
        return injector;
    }
    
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        super.contextInitialized(servletContextEvent);
        ps.start();
    }
    }
    
  2. The PersistFilter is useless as only the first time WebSocket will go trough filter but all subsequent communication will not go trough the filter. Using the txn just around @Transactional (Session-per-transaction) is the way to go.

Off-topic:

How many users do you intend to support? If this is going to be a hardcore chat server I'd use Netty instead but it is somewhat more involved. Googling found this:

http://comoyo.github.com/blog/2012/07/30/integrating-websockets-in-netty/

Original answer:

So this is a question about style?

WebSockets != Servlets. There is nothing wrong if they require a slightly different style. I'd even prefer to be reminded I am not dealing with vanilla servlets.

Some observations:

WebSocketServlet is nothing special. You can easily use it with Guice-Servlet. E.g.:

 @Singleton
 public class FooGuiceWebSocketServlet extends WebSocketServlet {...}

And then refernce it as

 serve("/foo").with(FooGuiceWebSocketServlet.class);

Now, MessageInbound that is special as is all handled by Tomcat as you explained. The MessageInbound is WebSocket scoped. Now Guice has no idea about this scope and it might make sense to leave it that way.

For starters I'd make sure MessageInbound is created by Guice. Something along this lines:

@Singleton
public class ExampleWebSocketServlet extends AbstractGuiceWebSocketServlet {

    @Override
    public Class<? extends StreamInbound> serveWith() {
        return Foo.class;
    }

    public static class Foo extends MessageInbound {

    @Inject GuiceCreatedAndInjected bar;

    @Override
    protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
        // do nothing
    }

    @Override
    protected void onTextMessage(CharBuffer charBuffer) throws IOException {
        // this getSwOutbonds() is not very friendly to testing
        getWsOutbound().writeTextMessage(bar.echo(charBuffer));
    }

   }
}

Where

public abstract class AbstractGuiceWebSocketServlet extends WebSocketServlet {

    @Inject Injector injector;

    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
        return injector.getInstance(serveWith());
    }

    public abstract Class<? extends StreamInbound> serveWith();

}

You can go from here to higher abstractions and/or scopings as needed. I don't particularly like #getWsOutbound() as it hinders testing.

Just keep on improving the style until you are satisfied. Say if you need more help (will modify answer).

like image 149
Alen Vrečko Avatar answered Oct 11 '22 17:10

Alen Vrečko