Based on this question I'd like to create a server endpoint instance based on the negotiated subprotocol to handle various protocol messages differently. Unfortunately ServerEndpointConfig.Configurator.getEndpointInstance [docs] wouldn't let me access any relevant session data to get the negotiated subprotol so I could instantiate different classes.
public static class ServerEndpointConfigurator extends
ServerEndpointConfig.Configurator {
public ServerEndpointConfigurator()
{
}
#Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
// useful to work with session data in endpoint instance but not at getEndpointInstance
HttpSession httpSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
#Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
// TODO get negotiated subprotocol and instantiate endpoint using switch case or factory
return (T) new WebSocketControllerA();
// or return (T) new WebSocketControllerB();
// or return (T) new WebSocketControllerC();
// ...
}
}
Any idea how to solve this problem or are there any widely accepted practices how to handle different subprotocols? I am having a hard time finding example implementations or advanced documentation about subprotocol handling on the web.
Is this what you are looking for?
#ServerEndpoint("/ws")
public class MyWebSocket {
#OnOpen
public void onOpen(Session session) {
session.getNegotiatedSubprotocol();
}
Related
I am writing an application with spring messaging and stomp and rabbitmq. My application already sends messages from the browser to rabbitmq and back. But i dont want the predefined rabbitmq queue names based on the session id. I want to change the session id on connect. This is what i tried:
#Component
public class MyListener {
private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
#EventListener
public void x(SessionConnectEvent event) {
Map<String, Object> headers = event.getMessage().getHeaders();
String id = headers.get("simpSessionId").toString();
logger.info("My current session id is " + id);
headers.put("sessionId", "fred");
}
}
Error is: the map is immutable
You need to update the sessionId before the handshake is done between client <-> server, that is when the headers attributes are defined.
On the other hand, the listener SessionConnectEvent is executed only after the handshake is done.
public class HttpHandshakeInterceptor implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
attributes.put("sessionId", "mySessiond");
}
return true;
}
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
}
}
Also don't forget to register the interceptor on the specific endpoint
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting").addInterceptors(new HttpHandshakeInterceptor());
}
Changing the session ID was not the correct way. I used ServletFilter for Cookie and Security Checks and #SendTo for the correct use of rabbitmq queues.
You can change the session id by creating a Principal for each handshake and then you can
target each connected session with the provided username :
class CustomHandshake extends DefaultHandshakeHandler {
#Override
public Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
principal = new AnonymousPrincipal();
String uniqueName = UUID.randomUUID().toString();
((AnonymousPrincipal) principal).setName(uniqueName);
}
return principal;
}
}
Do not forget to register the handler as below :
.setHandshakeHandler(new CustomHandshake())
hope this is helpful
I have a Java WebSocket that currently caches sessions in a static field so other servlets can call a static method to send messages to any listeners (as push to notify of events). It's like:
#ServerEndpoint("/events")
public class Events {
// collection containing all the sessions
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
#OnOpen
public void onOpen(final Session session) {
// cache the new session
sessions.add(session);
...
}
#OnClose
public void onClose(final Session session) {
// remove the session
sessions.remove(session);
}
public static void notify(String message) {
synchronized(sessions) {
for (Session s : sessions) {
if (s.isOpen()) {
try {
// send the message
s.getBasicRemote().sendText(message);
} catch (IOException ex) { ... }
}
}
}
}
}
Then the servlet can do:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Events.notify( "some message" );
}
But is a static variable the best way? Is there no built-in annotation for caching either a session or a server endpoint for retrieval by other servlets (maybe via #Context variables or something else)?
A static variable just doesn't seem like the best way to handle this.
I'm looking for better architecture solution. Currently we have following end-point:
/**
* Endpoint for frontend to be sure we are logged in
*/
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
This end-point is covered by Spring Security and only authenticated users have access to it.
What is the best practice of making frontend aware of user authentication state?
It looks like you are using pooling to check the login status. Your controller method
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
will never return false. So in general there is no need to have a return value in this case.
#ResponseStatus(value = HttpStatus.OK)
#RequestMapping(value = "/is_auth")
public void ping() {
// log ?
}
I believe the best solution would be a websocket connection between client and server. If you then implement a SessionListener, you can very easy send a login status to corresponding client if his session get expired:
//
// pseudo code
//
#Component
public class SessionListener implements HttpSessionListener {
private static final Logger logger = LoggerFactory.getLogger(SessionListener.class);
#Autowired
private IWebsocketService websocketService; // you own service here
#Override
public void sessionCreated(HttpSessionEvent se) {
logger.debug("sessionCreated: {}", se.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
String sessionId = se.getSession().getId();
logger.debug("sessionDestroyed: {}", sessionId);
websocketService.sendLoginStatus(sessionId, false);
}
}
EDIT: here is a very good example how to implement websockets with spring and javascript: Using WebSocket to build an interactive web application
In javax websockets we can use something like the follows
Session.getAsyncRemote().sendText(String text)
Session.getBasicRemote().sendText();
How can we send an asynchronous messages using spring websocket.
From WebSocketSession of spring webscockets can we extract RemoteEndPoint and send an async messages
PS Note: I am using Basic Spring websockets...
The configuration and code is as follows:
#Configuration
#EnableWebMvc
#EnableAspectJAutoProxy
#EnableWebSocket
public class WebMVCConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
private static final String ENDPOINT_URL = "/echo";
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(socketHandler(), ENDPOINT_URL).setAllowedOrigins("*");
}
#Bean
public WebSocketHandler socketHandler() {
return new WebSocketTestHandler();
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
public class SpringMVCInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { ApplicationConfig.class, RabbitMQConfig.class, RabbitConnectionFactory.class,
WebPropertyPlaceHolderConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
}
#Configuration
public class WebSocketTestHandler extends TextWebSocketHandler {
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("Connection is established to Server....:: Session Open : {}", session.isOpen());
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
}
#Override
public void afterConnectionClosed(WebSocketSession curSession, CloseStatus status) throws Exception {
}
}
So inside handleTextMessage(WebSocketSession session,TextMessage message) {
Inside this method am creating multiple threads And sending same session Object and some other parameters..Inside each thread am not modifying any session object related parameters but am trying to execute
TextMessage socketMessage = new TextMessage(message);
session.sendMessage(socketMessage);
}
So each thread is trying to send messages using same session Object..But am facing the following error
java.lang.IllegalStateException: Blocking message pending 10000 for BLOCKING
at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.lockMsg(WebSocketRemoteEndpoint.java:130) ~[websocket-common-9.3.8.v20160314.jar:9.3.8.v20160314]
at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.sendString(WebSocketRemoteEndpoint.java:379) ~[websocket-common-9.3.8.v20160314.jar:9.3.8.v20160314]
at org.springframework.web.socket.adapter.jetty.JettyWebSocketSession.sendTextMessage(JettyWebSocketSession.java:188) ~[spring-websocket-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.web.socket.adapter.AbstractWebSocketSession.sendMessage(AbstractWebSocketSession.java:105) ~[spring-websocket-4.2.4.RELEASE.jar:4.2.4.RELEASE]
So is it possible to send asynchronous messages using spring websockets?
If yes please let me know what configuration changes are required in the above code..Or Can we extract the core AsyncRemoteEndPoint and BasicRemoteEndpoint from spring Websocket Session and can we send asynchronous messages..or if not both the above cases ..move the code to common place and put synchonized(sessionObject)
{
sendmessage
}..Sorry if the framing of question is not clear or already a duplicate question
Please note I am not using any Stomp client or anyother features over spring websocket..Am using plain spring websockets..And is it possible to do without using Future(java feature)(If yes..it would be better)?
I used ConcurrentWebSocketSessionDecorator on the session.
according to:
https://jira.spring.io/browse/SPR-13602
The decorator "enforces sending messages one at a time with a send buffer and send time limit per session. That helps quite a bit to limit the impact of slow clients"
I am trying to retrieve the Subject that is currently executing a Privileged Action under the JAAS framework, in order to be able to extract its principals. Basically, I need to verify at run-time that the Privileged Action is indeed executed by the principal that has the right to do so.
Or, to put it differently: is it possible to get the current LoginContext at run-time as some kind of system property (and not by creating a new one)? This would easily allow extracting the Subject.
Are you sure you need the LoginContext?
If you just need the Subject (with all attached Principals), you can do
Subject activeSubject = Subject.getSubject(AccessController.getContext());
I think you need to manage such a mechanism yourself. For instance if this is a web application where you authenticate once and then associate the authentication with a session. You store the LoginContext in the session. One trick to make it available in other parts of the code would be to make a thread local wrapper that you set/unset at the start/end of every thread invocation (such as a request).
public class LoginContextHolder {
private static ThreadLocal<LoginContext> ctx = new ThreadLocal<LoginContext>();
public static void set(LoginContext lc) {
ctx.set(lc);
}
public static LoginContext get() {
return ctx.get();
}
}
public class LoginContextFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
LoginContext ctx = null;
HttpSession sess = (HttpSession)((HttpRequest)request).getSession(false);
if (sess != null) {
ctx = (LoginContext)sess.getAttribute("ctx");
}
try {
LoginContextHolder.set(ctx);
chain.doFilter(request, response);
} finally {
LoginContextHolder.set(null);
}
}
}