Tomcat: using servlet and websocket (jsr356) in same web app

2.2k views Asked by At

I create a sample webapp using Guice-servlets and websocket in tomcat, now once guice filter is used websocket stop working

Basic information:

In my web.xml, i initialized the Guiceservlet using GuiceBasedListener

<web-app>
     <listener>
        <listener-class>test.GuiceBasedListener</listener-class>
    </listener>          
    <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

GuieBasedListener Code which binds all request /* to MyDispatcher

public class GuiceBasedListener extends GuiceServletContextListener {
    protected Injector getInjector() {
        return Guice.createInjector( new ServletModule() {
            @Override
            protected void configureServlets() {
                bind(MyDispatcher.class).asEagerSingleton();
                serve("/*").with(MyDispatcher.class);//////IMPORTANT LINE//
            }
        });}}

MyDispatcher code which just respond with a String

public class MyDispatcher extends HttpServlet {    
    @Inject private Injector injector;
    public MyDispatcher() {}    
    public void service(ServletRequest req, ServletResponse resp) throws IOException, ServletException {
        resp.getOutputStream().print("SUCCESS:" + req);
    }
}

Also i have a @ServerEndPoint for Websocket

@ServerEndpoint(value = "/websocket/chat2")
public class WebSocket{
....
    @OnOpen
    public void start(Session session) {        
        System.out.println("Staring:"+this);
   }
....
}

Observations:

  1. Now if i run the app and hit http://app:8080/test it returns SUCCESS
  2. But if i try to connect to websocket using ws://app:8080/websocket/chat2 it fails
  3. Now if i comment serve("/*").with(MyDispatcher.class); basically if we switch off guice routing the websocket starts to work

  4. If i switch off guice-servlet but add a servlet mapping in web.xml like below websocket still works

    < servlet-mapping > < servlet-name > HelloWorld< / servlet-name > < url-pattern > /* < / url-pattern > < / servlet-mapping >

What am i missing or doing wrongly?


EDIT:

Observation-conti:

  1. What i did was defined a simple filter which just respond with FILTER .

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { response.getOutputStream().print("FILTER"); }

and changed my web.xml to

<web-app>           
    <filter>
        <filter-name>myFilter</filter-name>
        <filter-class>test.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>myFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>       
</web-app>

Now hitting the http://localhost:8080/app/x return FILTER as expected. But trying to connect with websocket fail as the request shows something like this. I also noticed that as i change the String MyFilter return the content length in response changes , which meaning the request reached MyFilter before tomcat handled it for websocket.

enter image description here

  1. I changed the web.xml to below and guice and websocket are working fine now.. so i think Guice is not honoring the WsFilter that registered after the GuiceFilter

    <filter>
    <filter-name>myFilter</filter-name>
    <filter-class>org.apache.tomcat.websocket.server.WsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>myFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping> 
    
     <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

TOMCAT 8.0, Window 7, Java 1.7 , Guice 4.0, Guice-servlet-4.0

1

There are 1 answers

0
Hugues M. On BEST ANSWER

That also looks like a Guice issue to me (as already mentioned in comments). Using servlets and WebSockets in same application should not be a problem, even with a servlet mapping that covers /*.

2 relevant things about servlets and filters:

  • as already mentioned, when getting a websocket upgrade request (a very special kind of http request), normally Tomcat should not let it go through the filters. It looks like Guice breaks this somehow.
  • Filters are applied in the order their mappings are defined in web.xml.

So, if WsFilter is first, it will intercept the request first, then check if it's a WebSocket upgrade request.
If it's indeed a WebSocket connection, the filter will not pass it on to the rest of the chain.
If it's another type of request (GET, POST...), it will pass it on, and then Guice will do its thing.

(so you found a first solution here)

If Guice filter is first, AND you use serve("/*")..., then it breaks your WS.

If you comment out serve("/*")..., then it does not matter if Guice filter is first or not, WsFilter can even be absent: your WS can be reached (which establishes GuiceFilter alone is OK).

So Guice has its own "layer of interception" above servlet mappings, and I think that's what breaks WebSockets. I don't know if there is a bug or anything to fix in Guice (I mean probably, but don't know what exactly), but you can specify exceptions to Guice (unlike servlet mappings in web.xml).
Replace this:

serve("/*").with(TestServlet.class);

with this:

serveRegex("/(?!websocket/).*").with(TestServlet.class);
// Regex that accepts /.* but excludes /websocket/.*

With that, you can keep the servlet mapping on /*, and remove WsFilter as it is not needed. (Tested, works for me)

So that's a second solution, that has the advantage that it also allows to specify exceptions for non-WebSocket stuff.