I'm trying to figure out a good way to organize a javax.websocket multiplayer card game I'm working on.
I want to split up my code into multiple classes which are each a ServerEndpoint. My problem is that I need an effective way of sharing session data between them.
I have an index at "/index", which is where I'm currently creating Player objects for clients. I'm setting these like so:
#ServerEndpoint("/index")
public class IndexEndpoint {
#OnOpen
public void openConnection(Session session) {
session.getUserProperties().put("player", new Player());
}
}
And that works; I can access the Player objects elsewhere throughout IndexEndpoint.
But when I try to access the user properties from another endpoint (after having established a connection with IndexEndpoint in JavaScript, waiting 5 seconds and then opening up an additional connection to LobbyEndpoint on the same page), I get null.
#ServerEndpoint("/lobby")
public class LobbyEndpoint {
#OnOpen
public void openConnection(Session session) {
System.out.println(session.getUserProperties().get("player")); // prints null
}
}
Which leads me to imply that session data is unfortunately not shared across endpoints.
Is there any good way for me to share websocket session data across multiple classes?
I guess one solution would be to just have one über endpoint for all users to connect to, and which can handle any message type. Then that endpoint delegates message data to other parts of my application.
However I feel like this design would be very messy and restrictive. I'd like to have endpoints dedicated to specific rooms, using annotations like #ServerEndpoint("/rooms/{room-id}") and #PathParam("room-id"). So if I was using a monolithic Endpoint then I couldn't do that; I wouldn't be able to know if a connecting user is a user who access to the room.
what about a singleton EJB containing all player and game data?
#Stateless
#ServerEndpoint("/lobby/{sessionKey}")
public class IndexEndpoint {
#EJB
GameData data;
#OnOpen
public void onOpen(Session session, EndpointConfig config,
#PathParam("sessionKey") int id) {
Player player = data.getPlayer(id);
session.getUserProperties().put("player", player);
}
}
The first message from the IndexEndpoint could provide the client with its id and then the client could provide its id to the LobbyEndpoint in the ws URL (or in a header with a Configurator), that is how I plan to do it.
Related
Could anyone tell me if the server-side implementation is using stomp WebSocket, is the client also expected to implement stomp?
I am trying to implement a spring boot application and I am confused if I should go with or without stomp implementation. From my research, I understand, if you want to scale the application, it is better to use stomp and embedded broker( RabbitMQ for eg.) as it will handle the sessions, heartbeat etc. instead of an in-memory broker.
The examples available online just shows implementations with and without stomp.
I am basically trying to get different datasets from the table upon client request and write to a WebSocket continuously.
Could anyone please confirm if my understanding so far is correct?
What are the essential things I will have to take care of if I go with stomp + websocket?
Updating the usecase below:
The mobile client would be displaying charts upon user login. There would be links in the left panel for eg. Sales, Discounts etc. which upon clicking, the request will reach server through websocket channel. Server will check the datatype in the request, generate the model using data from DB and write the data to the websocket.
Updating code - v1
MyWebSocketHandler:
#Component
public class MyWebSocketHandler extends TextWebSocketHandler {
Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
DashboardUtil dashboardutil;
#Resource(name = "socketSessionsMap")
private Map<String, WebSocketSession> socketSessionsMap;
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws InterruptedException, IOException {
try {
//Gets the socket session from map and writes a json to that socket - did for testing purpose.
socketSessionsMap.put("session", session);
//String payload = message.getPayload();
String jsonString = dashboardutil.getDataInJSON(); // gets hardcoded json model
session.sendMessage(new TextMessage(jsonString));
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
WebSecurityConfig:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
private MyWebSocketHandler myWebSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWebSocketHandler, "/socketHandler").setAllowedOrigins("*").withSockJS();
}
}
Could anyone tell me if the server-side implementation is using stomp
WebSocket, is the client also expected to implement stomp?
You can register multiple handlers in your web socket configuration. So in theory you can provide a handler for STOMP and another one for plain web socket. If you only provide a STOMP handler then the handshake from a standard web socket client will fail.
From my research, I understand, if you want to scale the application,
it is better to use stomp and embedded broker( RabbitMQ for eg.) as it
will handle the sessions, heartbeat etc. instead of an in-memory
broker.
That's correct. STOMP also offers a few more nice features especially the subscription to certain endpoints.
I am basically trying to get different datasets from the table upon
client request and write to a WebSocket continuously.
That's a really simple description ...
You should think about if you need to share sessions across multiple instances or if you need to send messages between web socket sessions.
From your description it sounds like you just accept a web socket connection and continuously push data to the client. If you want to scale this application you can just put a load balancer in front of your instances and you are good to go.
I have a web application written in Java using the Spring framework.
I would like to store the users activities like, page visits, actions, interactions etc.
I read that usually this is done by creating a table for each tracked aspect. I was wondering if there is a better way to do it using Spring framework, like a way to intercept all the requests and trigger some actions.
What kind of technology do you recommend to store all these information? Right know I’m using a MySql database interacting with it through JPA. But, since I’m really really new to these kind of things I don’t know if I should go with a NoSql database or stay with my already existing MySql database. This wonder comes from the idea that this kind of data flow will be much bigger than a normal data flow coming from more traditional actions such as signin, creation, deletion etc.
Hope to have explained myself... if not just tell me and I’ll try to add more details.
[EDIT 1]
The web app is an e-commerce. So far it does not have So many users but it will (in the order of thousands).
The goal of the user tracking it’s just to profile them in order to give them a better and more custom service. For instance, if a see that a user is taking a look to a lot of items of a precise category I can show him more items of that kind.
I do no care that much about the performance, I mean, it does not have to be so fast.
Right know I have just one database and everything is stored inside it. I don’t know if charging it with this kind of data flow would slow down its performance.
The application is running on AWS ElasticBeanstalk and the database is on AWS RDS.
In general its a very broad topic.
The following considerations come to my mind:
How many requests pass to the microservice per some period of time? If its a small number of users (which translates to the number of records to the database) - then its ok to go with the MySQL approach - the table won't be large. Note however, that sometimes it should be cleaned anyway
Is the latency important? Sometimes requests have to be served very quickly, adding a hop to the database to save the user preference can be problematic
How do you want to consume this kind of information? Are you planning to use dashboards (in this case micrometer + Prometheus / InfluxDB and Grafana can be a good solution). Are you planning to actually charge the users per number of requests with an ability to send the monthly bill to their email in PDF or provide a web access to such an information (like AWS does for example)?
How about Rate limiter? Are you planning to deny some requests if they're frequent and coming from the same user?
How many instance will "add" this kind of information? What if you have thousands of microservices that now will have to write to MySQL - it might not survive such a load (in addition to the regular load its set up for)?
The range of solutions can vary.
You can Batch the requests per user in memory and send once in while a message into Kafka and then use kafka streams to provide aggregations on it. With this approach you'll minimize the impact of the added processing on the existing solution and will deploy some other service that will be able to process this pretty large amount of data.
Another option: maybe you can create an asynchronously populated log file and store the information there. Then you might want to add some "agent" / side-car container like logstash and stream the data into some storage. Yet Another project that might be relevant in this field is Apache Flume which will allow you to construct a pipeline.
For billing you might use specialized systems AFAIK spring doesn't have anything like this usually these are ready products that you can integrate with.
For Rate Limiting you might consider: Resilience4j or solve it with redis
Yeah , That's possible , Here below are the three approaches with some sample snippets which would help you in the implementation , Moreover it depends on the data you store when you log the activity and when do you consider the activity data as obsolete and there are many factors which can decides your data store.
Approach 1: You can keep track of the login user using Spring-Security
You can write a HTTPSessionBindingListener and track the actions something like this
#Component
public class LoggedUser implements HttpSessionBindingListener {
private String username;
private ActiveUserStore activeUserStore;
public LoggedUser(String username, ActiveUserStore activeUserStore) {
this.username = username;
this.activeUserStore = activeUserStore;
}
public LoggedUser() {}
#Override
public void valueBound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (!users.contains(user.getUsername())) {
users.add(user.getUsername());
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (users.contains(user.getUsername())) {
users.remove(user.getUsername());
}
}
// standard getter and setter
}
and for login and logout you can track using AuthenticationSuccessHandler
#Component("myAuthenticationSuccessHandler")
public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired
ActiveUserStore activeUserStore;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
HttpSession session = request.getSession(false);
if (session != null) {
LoggedUser user = new LoggedUser(authentication.getName(), activeUserStore);
session.setAttribute("user", user);
}
}
}
Approach 2 : The other method if you want to make it very simple is that you can write a OncePerRequestFilter
#Component
#Ordered(Ordered.LOWEST_PRECEDENCE)
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Log the info you need
// ...
filterChain.doFilter(request, response);
}
}
Approach 3 : Implement using Spring AOP.
#Aspect
#Component
public class WebMethodAuditor {
protected final Log logger = LogFactory.getLog(getClass());
public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
#Autowired
AuditRecordDAO auditRecordDAO;
#Before("execution(* com.mycontrollers.*.*(..))")
public void beforeWebMethodExecution(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Timestamp timestamp = new Timestamp(new java.util.Date().getTime());
// only log those methods called by an end user
if(principal.getUsername() != null) {
for(Object o : args) {
Boolean doInspect = true;
if(o instanceof ServletRequestDataBinder) doInspect = false;
if(o instanceof ExtendedModelMap) doInspect = false;
if(doInspect) {
if(o instanceof BaseForm ) {
// only show form objects
AuditRecord ar = new AuditRecord();
ar.setUsername(principal.getUsername());
ar.setClazz(o.getClass().getCanonicalName());
ar.setMethod(methodName);
ar.setAsString(o.toString());
ar.setAudit_timestamp(timestamp);
auditRecordDAO.save(ar);
}
}
}
}
}
}
Source and More details :
https://www.baeldung.com/spring-security-track-logged-in-users
Spring / AOP: Best way to implement an activities log in the database
What is the best way to log Activity in Spring Boot with Thymeleaf?
I have chat which mapping to static URL. I need get the opportunity creating rooms for user.
How to inject variable in annotation #ServerEndpoint("/myVariable") when app already running?
class SomeClass{
public void createRoom(User user) {
String name = user.getName();
//...Somehow inject name to annotation #ServerEndpoint("/name")...
}
}
#ServerEndpoint("/chat") //May be replace to #ServerEndpoint(someGetteUserName())
public class ChatEndpoint {
#OnMessage
public void message(String message, Session client)
throws IOException, EncodeException {
for (Session peer : client.getOpenSessions()) {
peer.getBasicRemote().sendText(message);
}
}
}
I don't use Spring this is clear websocket and Glassfish.
Help me create implementation variable injection to annotation. Thank You.
I think that you don't need any injection if you only want to create and handle chat rooms. You just need to handle this by java code independently from your endpoint.
I recommend you to:
Create one websocket server endpoint: #ServerEndpoint("/chat"/{client_id}). This client id pathParam is may serve as a session id.
In ChatEndpoint class, initialize a list of rooms (this list should be static <=> common between all threads).
Create your business methods to handle clients and rooms(create/delete user, create/delete room, subscribe to a room...etc).
Finally, in your chat message try to specify the room destination. This can be very simple if you use JSON format.
message = { ... ,"room": "room1", ... }
I have a design problem as follows: I want to execute several soap webservices, where each response depends on the former.
When all responses are obtained, I want to validate all obtained data, then build some output based on it, and also issue from DB update.
Therefore I created a TemplateFacade method that wrapps all webservices that are to be executed. Problem: I obviously have to persist the responses between the method calls. Which will be problematic as autowired services should by definition be stateless and are singletons.
So how can I use injection with services that have to maintain some kind of state (at least until the Executor.execute() has terminated)?
Could you recommend a better design approach?
#Component
class Executor {
#Autowired
TemplateFacade template;
public void execute() {
template.run();
template.validate();
template.buildOutput();
template.updateDatabase();
}
}
#Service
class TemplateFacade {
//service classes wrapping webservice soap execution logic
#Autowired
PersonSoap personSoap;
#Autowired
CarSsoap carSoap;
#Autowired
ServiceDao dao;
private WebserviceRsp personRsp, carRsp;
void run() {
personRsp = personSoap.invoke();
//process response and prepare CarSoapXML accordingly, then send
carRsp = carSoap.invoke();
}
//the following methods will all
void validate() {
//validate both responses
}
void buildOutput() {
//create system out based on responses
}
void updateDatabase() {
dao.update(..);
}
}
To share state between multiple web services, you could keep track using a PersonState in the session which is tied to the user. I recommend encryption or hashing to secure the information.
When the validate completes, you could keep a PersonState in the session. When the buildOutput starts, you could get the PersonState object and continue with your logic and so on.
It is important that, you keep the PersonState to have a smaller memory footprint. Incase of a lot of data, you could just create a stateObject that will have the necessary state for the next step. e.g. at the end of validate you could create, BuildState object and put it in the session. build will get the object from the session and continue.
But I am not sure if it is really necessary to keep track of state and do it in 2 web services calls. The better solution would be to move all the logic part to another layer, and use the web services as just a window to your business/process layer.
Edit:
One more solution that could work you, is that the response of each step could contain the necessary state that is required for the next step. e.g. validateResponse contains personState and that could somehow be passed for the build.
I'm starting to develop for Web and I'm using Spring MVC as my Server Framework. Now I'm wondering about creating variables in Controller class. I had to do it to manage some data in server, but now I'm concerned about the following case: If I have more than one user sending information to the same page, at the same time, would one user interfere on another user variable?
Here's some code example:
#Controller
public Class myController {
int number;
#RequestMapping("/userInformation")
public String getInformation(int info) {
number = info;
}
public void doSomethingWithIt() {
number = number + 1;
}
}
In this case, If I have more than one user sending data to /userInformation at the same time, would Spring MVC create one Controller for each user? This way I wouldn't have problem, I guess. But if not, I have to rethink this implementation, don't I?
You are right. Controllers are singletons and must be stateless. Server side state belongs in session or in a data store. You can also use a request scoped object (look at bean scopes in spring).
The Spring container will create one instance of your Controller. So all users will share that instance.
If you have data that is private to a user, you have several options:
store it in the HTTP session (not recommended if it's a lot of data, as your memory usage might explode)
store it in a database and retrieve it upon each request, based on some property identifying the user
store it in a memory cache and retrieve it upon each request, based on some property identifying the user
Option 3 is the most simple one of them, you can even implement it as a Map<User, UserData> instance variable on your Controller if you like. It's not the cleanest, most beautiful or most secure option, just the most simple.
You should not use any instance variables in the Spring Controller that represent state of the controller class. It should not have state since its single instance. Instead you could have references to the injected managed beans.