Spring Boot websocket handshake hanging intermittently - java

Connecting to my web socket end point seems to (randomly) hang more often than not.
Here is the code that registers the endpoint -
#Configuration
#EnableWebSocket
public class WebSocketEndpointConfigurer implements WebSocketConfigurer {
#Autowired
private WebSocketConfig webSocketConfig;
#Autowired
private WebSocketEndpoint webSocketEndpoint;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry
.addHandler(webSocketEndpoint, webSocketConfig.getEndpoint()).setAllowedOrigins("*");
}
}
And here is my websocket end point -
#Component
public class WebSocketEndpoint extends TextWebSocketHandler {
private static Logger logger = LoggerFactory.getNoAppLogger(WebSocketEndpoint.class);
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("Websocket connection created");
//do more things
}
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
//do things
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//do things
}
}
I'd say 50% of the time when i try to initiate the connection it is successful. The other 50% of the time it reaches the afterConnectionEstablished() method and I see my log message but it never returns to the browser and Chrome's debug tool says 'pending'.
If I restart my Spring Boot server sometimes it fixes it sometimes it doesn't, it is totally random. Am I missing something here?
Thanks in advance, this is really bugging me.
EDIT: I have tried this with multiple web socket testing clients and have the same results for all.

Figured it out...
Our corporate anti-virus introduced a bug that affects web sockets....

Related

spring websocket cannot establish connection

cannot establish connection to my websocket server by browser client.
configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting");
}
}
And controller:
#MessageMapping("/message")
#SendToUser("/queue/reply")
public String processMessageFromClient(#Payload String message, Principal principal) throws Exception {
String name = new Gson().fromJson(message, Map.class).get("name").toString();
System.out.println(name);
//messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name);
return name;
}
I start server, and then open index.html in browser then make connect to ws://localhost:8080/greeting
and after that sending message to /app/message
but actually happens nothing. Browser inspector shows 404. What's wronng i do?
Here is the way that use to implement WebSocket in Spring. First, you should configure the Web socket message broker and register the stomp endpoint as below. Here I use setAllowedOrigins("*").withSockJS() to access this endpoint to any host.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/topic", "/queue/");
}
}
Then I create the controller as below.
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
#Autowired
WebSocketController(SimpMessagingTemplate template){
this.template = template;
}
#MessageMapping("/queue/reply")
public void sendMessage(String message){
System.out.println(message);
this.template.convertAndSend("/topic", message);
}
}
Use #MessageMapping("/queue/reply") instead of #SendToUser("/queue/reply") as above.
From that Simple Messaging Template, I used convertAndSend() method to
asynchronous data communication with that message broker. If there is
any data comes to that message broker it will automatically send that
data using the above configured endpoint called /socket with SockJS
and Stomp.
You can refer this article to learn more about the Spring web socket.

Shutdown spring application from ApplicationListener

Is it possible to close the context from an ApplicationListener?
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// on some certain condition we want to shutdown spring ie close the context
((ConfigurableApplicationContext)event.getApplicationContext()).close();
}
}
The problem is that Spring still wants to finish the startup process here:
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
And therefore throws an IllegalStateException:
java.lang.IllegalStateException:
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#dcb84c98
has been closed already
It seems likely that the question you actually wanted to ask is "How do I interrupt Spring-Boot startup".
Throw an exception from your onApplicationEvent method.
Its seems that you want to interrupt Spring on startup.
Simple you will not able to do that with out getting error.
If you will still like to kill your application while startup just use:
System.exit(0);
But if you want to close the context after the initial you can listen to a different Spring event called ApplicationReadyEvent
#Component
public class MyListener {
#EventListener
public void onApplicationEvent(ApplicationReadyEvent event) throws Exception {
event.getApplicationContext().close();
}
}
ApplicationReadyEvent - Event published as late as conceivably possible to indicate that the application is
* ready to service requests

How to implement a custom WebSocket subprotocol with Spring

I have to add support for a custom WebSocket subprotocol (so not STOMP) in a Spring Boot application, but I'm having a very hard time understanding what I need to provide and what Spring already has.
This is how far I got:
#Configuration
#EnableWebSocket
public class WebSocketAutoConfiguration implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(this.webSocketHandler(), new String[]{endpointUrl});
}
#Bean
public WebSocketHandler webSocketHandler() {
ExecutorSubscribableChannel clientInboundChannel = new ExecutorSubscribableChannel();
ExecutorSubscribableChannel clientOutboundChannel = new ExecutorSubscribableChannel();
SubProtocolWebSocketHandler subProtocolWebSocketHandler = new SubProtocolWebSocketHandler(clientInboundChannel, clientOutboundChannel);
subProtocolWebSocketHandler.addProtocolHandler(new SubProtocolHandler() {
public List<String> getSupportedProtocols() {
return Collections.singletonList("custom-protocol");
}
public void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> message, MessageChannel outputChannel) throws Exception {
session.sendMessage(new TextMessage("some message"));
}
public void handleMessageToClient(WebSocketSession session, Message<?> message) throws Exception {
}
public String resolveSessionId(Message<?> message) {
return UUID.randomUUID().toString();
}
public void afterSessionStarted(WebSocketSession session, MessageChannel outputChannel) throws Exception {
System.out.println("SESSION STARTED");
}
public void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel) throws Exception {
session.close();
System.out.println("SESSION ENDED");
}
});
return subProtocolWebSocketHandler;
}
}
This works, in the sense that handleMessageFromClient does get triggered on a web socket message, but I fail to understand the purpose of MessageChannel outputChannel and handleMessageToClient.
Is it possible to get the PerConnectionWebSocketHandler semantics with SubProtocolWebSocketHandler?
The documentation around this is basically non-existent e.g. the docs for handleMessageToClient say:
Handle the given {#link Message} to the client associated with the given WebSocket session.
Well, fantastic. And the STOMP implementations are mind-boggling, so they're not very usable as a guideline.
Any example, broad steps or anything, really, would be much appreciated.
Turns out it is exceptionally easy. No need to mess with SubProtocolWebSocketHandler at all. The only requirement is that the provided WebSocketHandler implements SubProtocolCapable.
public class CustomHandler implements WebSocketHandler, SubProtocolCapable {
...
}
That's all. To make a PerConnectionWebSocketHandler, it's enough to simply extend it and implement SubProtocolCapable:
public class CustomHandler extends PerConnectionWebSocketHandler implements SubProtocolCapable {
...
}

Guice Injector keeps application running

I am using guice to inject my dependencies. My IDE is IntelliJ 2017.2.5. When I run the following code my main method keeps on running and is not stopped. When I remove the DI the process stops with Process finished with exit code 0.
The class with the main method:
public class Test {
#Inject
Provider<EntityManager> em;
public void test() {
if(em.get().isOpen())
System.out.println("EM open");
}
public static void main(String args[]) {
final Injector injector = createInjector(new DatabaseModule());
Test app = injector.getInstance(Test.class);
app.test();
System.out.println("Done");
}
}
The DatabaseModule:
public class DatabaseModule extends AbstractModule {
private static final String PU_NAME = "my_pu";
#Override
protected void configure() {
install(new JpaPersistModule(PU_NAME));
bind(JPAInitializer.class).asEagerSingleton();
}
#Singleton
private static class JPAInitializer {
#Inject
public JPAInitializer(final PersistService service) {
service.start();
}
}
}
If I execute Test.main all goes well, JPA is initialised properly and I see the following output:
EM open
Done
For some reason the application is still running after that. I have to terminate the application manually.
How do I fix this?
You're not releasing acquired resources (DB connections and non-daemon threads).
public class Test {
#Inject
Provider<EntityManager> em;
#Inject
UnitOfWork unitOfWork;
public void test() {
if(em.get().isOpen())
System.out.println("EM open");
unitOfWork.end(); // releases DB connection
}
public static void main(String args[]) {
final Injector injector = createInjector(new DatabaseModule());
Test app = injector.getInstance(Test.class);
app.test();
System.out.println("Done");
injector.get(PersistService.class).stop(); // releases resources acquired by the underlying EntityManagerFactory
}
}
Technically just stoping PersistService should be enough in this case, but in general you should close EntityManager when it's not needed anymore. The way you're supposed to do this with Guice JPA is either by using #Transactional annotation or manually ending corresponding unit of work (as in the example above).
You probably have some open DB connections/threads waiting to be explicitly closed
Since you're using Hibernate make sure you close() the SessionFactory at the end (if that's what you're using)
In IntelliJ you can get the thread dump to see which threads are still running/waiting to be closed
Check that to see what you missed

Injection to ContainerRequestFilter in a java REST API

I guess I am dealing with a bug in Glassfish 4; but I am not sure. Basically I am trying to inject a service into a ContainerRequestFilter; but I am getting an exception while trying. I can do injection in resources where I make rest calls, but I can't use injection for filters. There is a filed bug in glassfish jira: https://java.net/jira/browse/GLASSFISH-20597. I tried the workaround mentioned in there but it didn't solve my problem.
Here is the exception I get:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=Authenticator,parent=SecurityInterceptor,qualifiers
Here is the code that I am working with, do you have any idea what I am missing here?
#Provider
#PreMatching
public class SecurityInterceptor implements ContainerRequestFilter {
#Inject
private Authenticator authenticator; // can't inject this service here, but I can inject this to RequestScoped Resources
private static final Logger LOG = Logger.getLogger(SecurityInterceptor.class.getName());
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (!requestContext.getMethod().equals("OPTIONS")) {
String path = OyumBuFunctions.normalizeString(requestContext.getUriInfo().getPath());
String authToken = requestContext.getHeaderString(OyumbuRestHeaders.AUTH_TOKEN_HEADER);
if (!authenticator.isAuthTokenValid(authToken)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
else{
LOG.log(Level.INFO, "authenticated");
}
}
}
}
I ran into a similar issue and came across this posting while searching for a solution. Fortunately I found a solution and thought to post it here. Note that this only works in JAX-RS 2.0 as it makes use of the DynamicFeature class
#Provider
public class AuthenticatorFeature implements DynamicFeature {
#Inject
private Autheticator authenticator;
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
context.register(new SecurityInterceptor(authenticator);
}
}
Binding the authenticator to the ContainerRequestFilter.
public class SecurityInterceptor implements ContainerRequestFilter {
Authenticator authenticator;
public SecurityInterceptor(Authenticator authenticator){
this.authenticator = authenticator;
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
authenticator.doSomething(...);
}
}
Some hopefully helpful readings
creating resource filters with jersey and jaxrs 2.0
Securing REST Resources with JAX-RS 2.0
As mentioned in this answer https://stackoverflow.com/a/23996225/1045081, giving #Path annotation to the service to be injected saves the situation. Still didn't understand how to solve it properly, but just writing it here so that it could be useful for someone who encounters a similar problem.

Categories

Resources