14

What do I want to have:

  • Client sends GET / HTTP/1.1(without Connection: upgrade) - this request should be handled by RequestMappingHandlerMapping
  • Client sends Connection: upgrade along with GET request - this request should be handled by ServletWebSocketHandlerRegistry

My Java configuration:

@Configuration
@EnableWebSocket
public class WebsocketConfiguration extends WebMvcConfigurationSupport 
                                    implements WebSocketConfigurer {
    @Bean
    WebsocketComponent wsHandler() {
        return new WebsocketComponent();
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(wsHandler(), "/").setAllowedOrigins("*");
    }
}

My webmvc controller:

@Controller
public class Status {
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String status() {
        return "OK";
    }
}

The problem is - when MVC controller taking precedence, it always respond with HTTP 200, WebSocket handler never reached. When WebSocket handler have precedence - it works with WebSocket client, but when I try http client(browser) it responds with Can "Upgrade" only to "WebSocket". Is it possible to replace somehow this error page with fallback to my MVC mapping? Any other configurations to make what I described first?

vitalyster
  • 4,980
  • 3
  • 19
  • 27
  • Are sure of the fact that Spring MVC's request mappings take lower precedence over WebSocket mappings? For example, can you open a web socket connection on `ws://localhost:8080/status`? – Ali Dehghani Jul 18 '16 at 21:21
  • @AliDehghani sure, websocket connection works, plain http connection didn't work with: 00:30:20 ERROR Handshake failed due to invalid Upgrade header: null – vitalyster Jul 18 '16 at 21:32
  • Apparently, your `WebSocketHandlerMapping` has more precedence over your `RequestMappingHandlerMapping`. It's usually the other way around, please post more about your configurations – Ali Dehghani Jul 18 '16 at 22:07
  • @AliDehghani when `RequestMappingHandlerMapping` have precedence there are no place to fallback at all, see updated question – vitalyster Jul 19 '16 at 12:18
  • 2
    Out of curiosity: Why do you want both on the same url? Why not to give some different uri for websockets? – Aleksandr M Jul 19 '16 at 12:23
  • 1
    @AleksandrM 1) that is the way how my legacy application work, I want to use spring-websockets instead of handwritten websocket server and remain compatible with clients 2) it's better to have more descriptive message with instructions than cryptic `can upgrade only to websocket` – vitalyster Jul 19 '16 at 12:29

1 Answers1

14

The problem is - when MVC controller taking precedence, it always respond with HTTP 200, WebSocket handler never reached

When RequestMappingHandlerMapping takes precedence over WebSocketHandlerMapping, for a request to an endpoint that both of them can handle (If you just consider the URL), DispatcherServlet would dispatch the request to @RequestMapping methods, not the WebSocket handler. In order to solve this problem, restrict @RequestMapping method to just serve the request without Connection:Upgrade header:

@Controller
public class Status {
    @RequestMapping(value = "/", method = GET, headers = "Connection!=Upgrade")
    public String status() {
        return "OK";
    }
}

This way, when DispatcherServlet searches for a handler for that common endpoint, it would consider the presence or absence of the Connection:Upgrade header to determine the right handler to fulfill the request.

Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
  • Great explanation! In my case I had to double the test: `headers = {"Connection!=upgrade", "Connection!=Upgrade" }` – etienne-sf Mar 14 '21 at 15:56