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

1

6

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

Gerrygerrymander answered 27/5, 2017 at 12:21 Comment(5)
"How can i tell tomcat to first consider serverEndPoint url for routing the request before matching the filter url patterns ?" Frankly, Tomcat already does that. You just misinterpreted the cause of your actual problem. I recommend to rewrite the question to tell in detail about the observed symptoms of the actual problem instead of asking how to solve a misassumed possible cause. I gather that the GuiceFilter has just a bug and needs a simple bugfix, but it isn't clear what exactly as long as you don't elaborate about the actually observed symptoms.Olnee
@Olnee you are right ... i have rewritten the issueGerrygerrymander
"But if i try to connect to websocket using ws://app:8080/websocket/chat2 it fails" How exactly does it fail? What exactly happens and what exactly happens not and what exactly happens instead?Olnee
@Olnee Added an interesting observationGerrygerrymander
@Olnee I think the Filters in web.xml are registered before the WsFilter of tomcat. Just a speculation?Gerrygerrymander
S
3

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.

Slumber answered 31/5, 2017 at 20:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.