I'm trying to add web sockets to my AngularJS application.
I set up the web socket using Spring 4 on the server and Stomp SockJS on the client.
I access the page at http://localhost:9000/#/project/1/bts
after a grunt serve
command.
But I get a 500 response when doing the web socket handshake:
WebSocket connection to 'ws://localhost:8080/nitro-project-rest/api/socket/bts/405/bmtcztq4/websocket' failed: Error during WebSocket handshake: Unexpected response code: 500
The server has this to say upon each failed websocket request:
SEVERE: Servlet.service() for servlet [NITRo] in context with path [/nitro-project-rest] threw exception [Request processing failed; nested exception is org.springframework.web.socket.sockjs.SockJsException: Uncaught failure in SockJS request, uri=http://localhost:8080/nitro-project-rest/api/socket/bts/970/2xoe6kls/websocket; nested exception is org.springframework.web.socket.sockjs.SockJsTransportFailureException: WebSocket handshake failure; nested exception is java.lang.RuntimeException: Cannot load platform configurator] with root cause
java.lang.RuntimeException: Cannot load platform configurator
More on the error:
2017-02-06 10:36:04,241 DEBUG [http-bio-8080-exec-10] o.s.w.s.h.LoggingWebSocketHandlerDecorator Transport error in WebSocketServerSockJsSession[id=nqj286ob]
java.lang.RuntimeException: Cannot load platform configurator
at javax.websocket.server.ServerEndpointConfig$Configurator.fetchContainerDefaultConfigurator(ServerEndpointConfig.java:123)
at javax.websocket.server.ServerEndpointConfig$Configurator.getContainerDefaultConfigurator(ServerEndpointConfig.java:128)
at javax.websocket.server.ServerEndpointConfig$Configurator.checkOrigin(ServerEndpointConfig.java:192)
The funny thing is the push notification works fine. I can see the webpage being updated live when the expected event occurs. So my websocket works fine, in spite of this exception. This exception occurs at page load time.
When I manually type the url:
http://localhost:8080/nitro-project-rest/api/socket/bts
in the Chromium web browser it displays: Welcome to SockJS!
Here is my web socket connection service:
function StompService(endpoint) {
this.stompClient = Stomp.client(ENV.NITRO_PROJECT_WS_URL + '/socket/' + endpoint);
}
StompService.prototype.connect = function(onConnect, onError) {
this.stompClient.connect({}, function(frame) {
$rootScope.$apply(function() {
onConnect.apply(stompClient, frame);
});
}, function(frame) {
$rootScope.$apply(function() {
onError.apply(stompClient, frame);
});
}, '/');
};
And the controller:
var stomp = new SocketService(BTS_WS_ENDPOINT);
stomp.connect(function() {
$scope.client.subscribe('status', function(message) {
$scope.messages.push(message.body);
});
}, function() {
});
With the server side configuration and controller:
@Configuration
@EnableWebSocketMessageBroker
@ComponentScan(basePackages = "com.nsn.nitro.project.rest.socket")
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// Prefix for destinations towards the server
config.setApplicationDestinationPrefixes("/app");
// Prefix for destinations towards the client
config.enableSimpleBroker("/topic");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// All controllers need to be added to the endpoint registry
registry.addEndpoint("/socket/bts").withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.taskExecutor().corePoolSize(4).maxPoolSize(10);
}
}
@Controller
public class BTSSocketController {
private static Logger logger = LoggerFactory.getLogger(BTSSocketController.class);
@MessageMapping("/socket/bts")
@SendTo("/topic/status")
public BTSMessage status(BTSMessage message) throws Exception {
logger.debug("==========>> Received the message " + message.getBtsId());
message.setStatus(BTSStatus.ONAIR);
return message;
}
}
I'm running Spring Security with Basic Authentication and the following configuration:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(simpleCORSFilter, ChannelProcessingFilter.class);
http
.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic().authenticationEntryPoint(restAuthenticationEntryPoint)
.and().authorizeRequests().antMatchers("/socket/**").permitAll()
.and().authorizeRequests().antMatchers("/resources/**").permitAll()
.and().authorizeRequests().antMatchers(RESTConstants.SLASH + RESTConstants.ADMINS + RESTConstants.SLASH + RESTConstants.LOGIN).permitAll()
.and().authorizeRequests().antMatchers("/**").hasRole("ADMIN").anyRequest().authenticated();
}
@Component
public class SimpleCORSFilter implements Filter {
private static final String ORIGIN = "Origin";
private static final String OPTIONS = "OPTIONS";
private static final String OK = "OK";
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
if (httpServletRequest.getHeader(ORIGIN) != null) {
String origin = httpServletRequest.getHeader(ORIGIN);
httpServletResponse.setHeader("Access-Control-Allow-Origin", origin);
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Accept-Language,Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-Filename,Content-Disposition,Content-Length");
// Allow more than the 6 default headers to be returned, as the content length is required for a download file request to get the file size
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Accept-Language,Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-Filename,Content-Disposition,Content-Length");
}
if (httpServletRequest.getMethod().equals(OPTIONS)) {
try {
httpServletResponse.getWriter().print(OK);
httpServletResponse.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
}
My Java dependencies are:
[INFO] +- javax.websocket:javax.websocket-api:jar:1.1:provided
[INFO] +- org.springframework:spring-web:jar:4.3.6.RELEASE:compile
[INFO] +- org.springframework:spring-webmvc:jar:4.3.6.RELEASE:compile
[INFO] +- org.springframework:spring-test:jar:4.3.6.RELEASE:compile
[INFO] +- org.springframework:spring-messaging:jar:4.3.6.RELEASE:compile
[INFO] +- org.springframework:spring-websocket:jar:4.3.6.RELEASE:compile
My Js dependencies are:
"sockjs": "~0.3.4",
"stomp-websocket": "~2.3.4",