Reputation: 3139
I'm working with a Spring 4
application (not Spring Boot
, unfortunately) and I'm unable to get web sockets to work. There's a simple TextWebSocketHandler
implementation that I register at /ws
path, and I do get a confirmation log:
Mapped URL path [/ws] onto handler of type [class org.springframework.web.socket.server.support.WebSocketHttpRequestHandler]
...but I cannot connect with the address in the web browser. I've tried this annotation-based configuration:
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
public static final String WEB_SOCKET_PATH = "/ws";
@Autowired
private WebSocketService handler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
log.info("Registering web socket handler {} at '{}'.", handler, WEB_SOCKET_PATH);
registry.addHandler(handler, WEB_SOCKET_PATH);
}
}
I can confirm that the WebSocketService handler
is not null and actually implements the WebSocketHandler
interface. While the configurer clearly gets triggered, connecting with "ws://localhost:8081/ws"
results in errors:
WebSocket connection to 'ws://localhost:8081/ws' failed: Error during WebSocket handshake: Unexpected response code: 404
error { target: WebSocket, isTrusted: true, currentTarget: WebSocket, eventPhase: 2, bubbles: false, cancelable: false, defaultPrevented: false, timeStamp: 1469616712980247, originalTarget: WebSocket, explicitOriginalTarget: WebSocket, NONE: 0 }
The client code that I'm using to test the web sockets mechanism is pretty straightforward:
var ws = new WebSocket("ws://localhost:8081/ws");
ws.onerror = function(e) { console.log(e); }
ws.onopen = function() { console.log("Opened"); }
I even tried registering the handler via XML configuration, but while I'm getting pretty much the same logs, it still does not work. Note that I'm running the application using pretty much default config of Jetty 9 (via Gretty Gradle plugin).
When going through Spring debug logs, I can only guess that Spring fails at promoting the HTTP request to web socket connection:
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 1 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@4190548c: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 2 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 3 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 4 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Checking match of request : '/ws'; against '/logout'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Request 'GET /ws' doesn't match 'POST /securityCheck
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 7 of 13 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
13:14:02.322 [qtp1305935114-14] DEBUG - Checking match of request : '/ws'; against '/login*'
13:14:02.322 [qtp1305935114-14] DEBUG - Secure object: FilterInvocation: URL: /ws; Attributes: [ROLE_USER]
13:14:02.322 [qtp1305935114-14] DEBUG - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER
13:14:02.322 [qtp1305935114-14] DEBUG - Voter: org.springframework.security.access.vote.RoleVoter@b5de58f, returned: 1
13:14:02.322 [qtp1305935114-14] DEBUG - Authorization successful
13:14:02.322 [qtp1305935114-14] DEBUG - RunAsManager did not change Authentication object
13:14:02.322 [qtp1305935114-14] DEBUG - /ws reached end of additional filter chain; proceeding with original chain
13:14:02.323 [qtp1305935114-14] DEBUG - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@425f619f
13:14:02.324 [qtp1305935114-14] DEBUG - Chain processed normally
13:14:02.324 [qtp1305935114-14] DEBUG - SecurityContextHolder now cleared, as request processing completed
Having looked through the official documentation and samples, it seems that the web sockets configuration is pretty standard. Is there anything I might have missed or any configuration that I might have to change to enable the web sockets in Spring?
Upvotes: 0
Views: 1930
Reputation: 3139
Turns out web sockets worked as expected, but Spring servlet mapping was not set at /*
in web.xml
:
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>
It's silly, really - all I had to do is change the address to ws://localhost:8081/spring/ws
. What took me off guard was the fact that some Spring filters were mapped to /*
and I was under the impression that Spring servlet shared this mapping - after all, calls to ws://localhost:8081/ws
triggered some Spring stuff (as seen in the logs).
Upvotes: 1
Reputation: 821
I think you should add @Controller annotation to the WebSocketConfigurer class, if not all your http request will be handled by the Spring's DispatcherServlet. Related: Spring websocket getting 404 not found
Upvotes: 0