I'm using this tutorial and I'm trying to figure out how to get the number of current sessions.
My WebSocketConfig looks like this (copy and paste from the tutorial) :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
I'd like to know the number of sessions inside of this class (again copy and paste):
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
Is there an easy way to get the number of current sessions(users, connections) to the websocket?
Edit:
Here is my solution:
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
#EventListener
private void onSessionConnectedEvent(SessionConnectedEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.add(sha.getSessionId());
}
#EventListener
private void onSessionDisconnectEvent(SessionDisconnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.remove(sha.getSessionId());
}
I can now get the number of Sessions with mySet.size() .
You can use SimpUserRegistry and its getUserCount() method instead of handling connections manually.
Example:
#Autowired
private SimpUserRegistry simpUserRegistry;
public int getNumberOfSessions() {
return simpUserRegistry.getUserCount();
}
You can use ApplicationContext events. Every connection, subscription or other action will fire a special event: SessionConnectEvent, SessionConnectedEvent, SessionSubscribeEvent and so on.
Full doc is here. When one of these events fires, you can handle it with your own logic.
Sample code for reference:
#EventListener(SessionConnectEvent.class)
public void handleWebsocketConnectListner(SessionConnectEvent event) {
logger.info("Received a new web socket connection : " + now());
}
#EventListener(SessionDisconnectEvent.class)
public void handleWebsocketDisconnectListner(SessionDisconnectEvent event) {
logger.info("session closed : " + now());
}
Related
I implemented a Spring Boot Application with WebSocket. This application show a list of entities (BaseStatistica) in real time with summary info (count of answered, no-answered and busy calls).
I would like to extend this application so it is possibly to click on the name of entity,move to other page and watch a more detail (ExtendedStatistica) info about calls counts grouped by gateways and trunks for this entity, also in real time.
My code :
WebSocketConfiguration.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
MonitoringController.java
#PostMapping("/queue_info/")
public void getQueueInfo() throws JSONException {
List<QueueInfo> queueInfoList = new ArrayList<>();
template.convertAndSend("/topic/queue_info", queueInfoList);
}
BaseStatistica.java
public class BaseStatistica {
private String queueNum;
private int gotAnsweredCalls;
private int gotNoAnswerCalls;
private int gotBusyCalls;
}
ExtendedStatistica.java
public class ExtendedStatistica extends BaseStatistica {
private String gatewayName;
private String trunkName;
}
and client side - Angular 7
app.component.ts
export class AppComponent {
queueInfoList: String[]=[];
constructor(private webSocketService: WebSocketService) {
let stompClientQueueInfo = this.webSocketService.connect();
stompClientQueueInfo.connect({}, frame => {
stompClientQueueInfo.subscribe('/topic/queue_info', response => {
this.queueInfoList = JSON.parse(response.body);
})
});
}
}
websocket.service.ts
export class WebSocketService {
constructor() { }
connect() {
let socket = new SockJs(`http://localhost:8081/websocket-backend/socket`);
let stompClient = Stomp.over(socket);
return stompClient;
}
}
I guess that here it is possible somehow change the structure of entity classes, but I'm interested in the ability to view detailed information on the fly. Is it possible to create Websocket endpoints dynamically?
I have implemented a console application using Spring and WebSockets. The application works fine if one or more participants are connected to the base method which is anotated like this.
#MessageMapping("/chat")
#SendTo("/topic/messages")
I will copy the configuration and the implementation which i have made o be more clear.
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
#Controller
public class ChatController {
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(#Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
#MessageMapping("/chat/{room}")
#SendTo("/topic/messages/{room}")
public OutputMessage enableChatRooms(#DestinationVariable String room, #Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
}
#Service
public class SessionHandlerService extends StompSessionHandlerAdapter {
private String nickName;
public SessionHandlerService() {
this.nickName = "user";
}
private void sendJsonMessage(StompSession session) {
ClientMessage msg = new ClientMessage(nickName, " new user has logged in.");
session.send("/app/chat", msg);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return ServerMessage.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.err.println(payload.toString());
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/messages", new SessionHandlerService());
sendJsonMessage(session);
}
}
The problem which i face is that when i subscribe to /topic/messages and session.send("/app/chat", msg);everything works fine. But if i choose something like session.send("/app/chat/room1", msg); and /topic/messages/room1 the participans can not see each other messages like they are in different chat rooms.
In my JIRA plug-in I have created a WebListener which add a websocket endpoint to the SeverContainer.
The problem is, when I make changes to my plugin and upload it in JIRA, the new code is not executed.
This is because the endpoint is not being deployed again. I get the following exception: Multiple Endpoints may not be deployed to the same path
My weblistener:
#WebListener
public class MyListener implements ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(MyListener.class);
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
final ServerContainer serverContainer = (ServerContainer) servletContextEvent.getServletContext()
.getAttribute("javax.websocket.server.ServerContainer");
try {
serverContainer.addEndpoint(MyWebsocket.class); //Generates exception when the plug-in is updated
} catch (DeploymentException e) {
LOG.error("Error adding endpoint to the servercontainer: " + e.getMessage());
}
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
LOG.error("CONTEXT DESTROYED!");
}
}
My websocket:
#ServerEndpoint(value = "/websocket/{myPathParam}")
public class MyWebsocket {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
#OnOpen
public void onOpen(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnOpen");
}
#OnClose
public void onClose(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnClose");
}
#OnMessage
public void onMessage(String message, Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnMessage: " + message);
}
}
Is there a way to remove the endpoint from the servercontainer, so it will be deployed again?
In the specification, I did not find any way to deactivate a websocket. The same applies for ServletContextListeners. They can only be added. So you need a workaround.
I suggest, that you do not replace the MyEndpoint.class, but make it a proxy that will call an implementation. Thus, the endpoint will not be required to re-register and the new proxy class calls the new code, when it is deployed.
So, you can just safely ignore the DeploymentException in your code, because you change the MyWebsocket.class as follows:
#ServerEndpoint(value = "/websocket/{myPathParam}")
public class MyWebsocket {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
#OnOpen
public void onOpen(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnOpen");
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onOpen(session, myPathParam);
}
#OnClose
public void onClose(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnClose");
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onClose(session, myPathParam);
}
#OnMessage
public void onMessage(String message, Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnMessage: " + message);
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onMessage(message, session, myPathParam);
}
}
I understand, that this will not really answer your question, but it is a solution how to work around the missing remove options.
One problem is there with this workaround: It will fix your interface, you cannot add parameters in a new version of the plugin.
To enable this, you add another websocket by adding another context listener (V7 has been choosen at random):
#WebListener
public class MyListener_V7 implements ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(MyListener_V7.class);
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
final ServerContainer serverContainer = (ServerContainer) servletContextEvent.getServletContext()
.getAttribute("javax.websocket.server.ServerContainer");
try {
serverContainer.addEndpoint(MyWebsocket_V7.class); //Generates exception when the plug-in is updated
} catch (DeploymentException e) {
LOG.error("Error adding endpoint to the servercontainer: " + e.getMessage());
}
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
LOG.error("CONTEXT DESTROYED!");
}
}
#ServerEndpoint(value = "/websocket_V7/{myPathParam}")
public class MyWebsocket_V7 {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
This will litter the JIRA instance with endpoints until a restart happens. But if you mix the two suggestions, you will only add a new endpoint every once in a while.
I have Spring service, which is actually actor, it is received info, but I cant pass it to another Spring service, because injection fails.
#Service("mailContainer")
#Scope("prototype")
#Component
public class MailContainer extends UntypedActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private Mail value;
private List<Mail> mailList = new ArrayList<Mail>();
private Integer size;
#Autowired
#Qualifier("springService")
private SpringService springService;
//#Autowired
public void setSpringService(SpringService springService) {
this.springService = springService;
}
public MailContainer(Mail value) {
this.value = value;
}
#Override
public void onReceive(Object message) throws Exception {
// LOG.debug("+ MailContainer message: {} ", message);
if (message instanceof Mail) {
value = (Mail) message;
System.out.println("MailContainer get message with id " + value.getId());
System.out.println("With time " + value.getDateSend());
//getSender().tell(value, getSelf()); //heta uxarkum
//this.saveIt(value);
springService.add(value);
}
}
and second service
#Service("springService")
//#Component
#Scope("session")
public class SpringService {
private List<Mail> mailList = new ArrayList<Mail>();
public void add(Mail mail) {
System.out.println("Saving mail from Spring " +mail.getId());
mailList.add(mail);
}
public List<Mail> getMailList() {
return mailList;
}
}
Spring config, this is from akka spring example
#Configuration
//#EnableScheduling
//EnableAsync
#ComponentScan(basePackages = {"com"}, excludeFilters = {
#ComponentScan.Filter(Configuration.class)})
//#ImportResource("classpath:META-INF/spring/spring-data-context.xml")
//#EnableTransactionManagement
//#EnableMBeanExport
//#EnableWebMvc
public class CommonCoreConfig {
// the application context is needed to initialize the Akka Spring Extension
#Autowired
private ApplicationContext applicationContext;
/**
* Actor system singleton for this application.
*/
#Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("AkkaJavaSpring");
// initialize the application context in the Akka Spring Extension
SpringExtProvider.get(system).initialize(applicationContext);
return system;
}
}
So, how I can inject just another Spring service?????????
Based on our discussions, I think it is due to the way you create the MailContainer actor. You aren't using the SpringExtProvider and instead are using Props.create directly. This means that Spring doesn't get the opportunity to perform dependency injection on your new actor.
Try changing this code:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(Props.create(MailContainer.class, result), "one");
}
to use the the SpringExtProvider like this:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("mailContainer"), "one");
}
This way you are asking the Spring extension to create the new actor and inject any required dependecnies.
I am using spring 4.0.0.rc1 websocket endpoint and topic.
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
#ComponentScan(basePackages="org.springframework.samples")
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocketendpoint").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerConfigurer configurer) {
configurer.enableSimpleBroker("/queue/", "/topic/");
}
}
But JMS listener can not post a message on websocket-topic. In this case I am using camel-consume annotation to listen to queue and post message to topic.
#Service
public class WebsocketTopicService implements ApplicationListener<BrokerAvailabilityEvent>
{
private static final Logger log = Logger.getLogger(MessageChannelService.class);
private final MessageSendingOperations<String> messagingTemplate;
private AtomicBoolean brokerAvailable = new AtomicBoolean();
#Autowired
public MessageChannelService(MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#Override
public void onApplicationEvent(BrokerAvailabilityEvent event) {
this.brokerAvailable.set(event.isBrokerAvailable());
}
//#Scheduled(fixedDelay=2000)
#Consume(uri="activemq:queue.out")
public void processMessage(String msg){
if (this.brokerAvailable.get()) {
this.messagingTemplate.convertAndSend("/topic/message.status", msg);
log.info("Sending quote " + msg);
}
}
Any idea why ??