Extracting Remote endpoint Object from Spring websocket session - java

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"

Related

Routing websocket destination in Spring-boot

Having raw websocket implementation:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MessageHandler(), "/websocket")
.setAllowedOrigins("*")
.addInterceptors();;
}
}
Handler:
public class MessageHandler extends TextWebSocketHandler {
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// The WebSocket has been closed
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String auth = (String) session.getAttributes().get("auth");
System.out.println(auth);
session.sendMessage(new TextMessage("You are now connected to the server. This is the first message."));
}
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage) throws Exception {
// A message has been received
}
}
The websocket client connect to server ( handshake etc. ) with /websocket url e.g ws://localhost:8080/websocket
However, now that connection is estabilished is there a way how to route messages? Lets say i have app that provides chat and some pop-up functionality ( for simplicity lets say the user sends pop-up message and some pop-up window shows to all of his friends in app ).
Ofcourse i would like to route chat messages to /chat and popup to /popup.
One way how to achieve this is to send json message to server and parse it there e.g:
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage) throws Exception {
String path = getRouteFromJsonMessage(textMessage);
if( ! "".equals(path) && path.equals("chat")
....
if( ! "".equals(path) && path.equals("popup")
....
}
But this seems too slow, parsing json on every message. Is there some other, better way how to achieve routing?
Thanks for help!
Why don't you just register two different MessageHandlers
public class WebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatMessageHandler(), "/chat")
.setAllowedOrigins("*")
.addInterceptors()
.addHandler(new PopUpHandler(), "/popup") //etc;
}
}

Websocket ServerEndpoint instance by subprotocol

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();
}

How to disable ErrorPageFilter in Spring Boot?

I'm creating a SOAP service that should be running on Tomcat.
I'm using Spring Boot for my application, similar to:
#Configuration
#EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
public class AppConfig {
}
My webservice (example):
#Component
#WebService
public class MyWebservice {
#WebMethod
#WebResult
public String test() {
throw new MyException();
}
}
#WebFault
public class MyException extends Exception {
}
Problem:
Whenever I throw an exception within the webservice class, the following message is logged on the server:
ErrorPageFilter: Cannot forward to error page for request
[/services/MyWebservice] as the response has already been committed.
As a result, the response may have the wrong status code. If your
application is running on WebSphere Application Server you may be able
to resolve this problem by setting
com.ibm.ws.webcontainer.invokeFlushAfterService to false
Question:
How can I prevent this?
To disable the ErrorPageFilter in Spring Boot (tested with 1.3.0.RELEASE), add the following beans to your Spring configuration:
#Bean
public ErrorPageFilter errorPageFilter() {
return new ErrorPageFilter();
}
#Bean
public FilterRegistrationBean disableSpringBootErrorFilter(ErrorPageFilter filter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
The simpliest way to disable ErrorPageFilter is:
#SpringBootApplication
public class App extends SpringBootServletInitializer {
public App() {
super();
setRegisterErrorPageFilter(false); // <- this one
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(App.class);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
//set register error pagefilter false
setRegisterErrorPageFilter(false);
builder.sources(MyApplication.class);
return builder;
}
}
The best way is to tell the WebSphere container to stop ErrorPageFiltering. To achieve this we have to define a property in the server.xml file.
<webContainer throwExceptionWhenUnableToCompleteOrDispatch="false" invokeFlushAfterService="false"></webContainer>
Alternatively, you also can disable it in the spring application.properties file
logging.level.org.springframework.boot.context.web.ErrorPageFilter=off
I prefer the first way.Hope this helps.
I found in the sources that the ErrorPageFilter.java has the following code:
private void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
ErrorWrapperResponse wrapped = new ErrorWrapperResponse(response);
try {
chain.doFilter(request, wrapped);
int status = wrapped.getStatus();
if (status >= 400) {
handleErrorStatus(request, response, status, wrapped.getMessage());
response.flushBuffer();
}
else if (!request.isAsyncStarted() && !response.isCommitted()) {
response.flushBuffer();
}
}
catch (Throwable ex) {
handleException(request, response, wrapped, ex);
response.flushBuffer();
}
}
As you can see when you throw an exception and return a response code >= 400 it will do some code. there should be some additional check if the response was already committed or not.
The way to remove the ErrorPageFilter is like this
protected WebApplicationContext run(SpringApplication application) {
application.getSources().remove(ErrorPageFilter.class);
return super.run(application);
}
Chris
public class Application extends SpringBootServletInitializer
{
private static final Logger logger = LogManager.getLogger(Application.class);
public Application()
{
super();
setRegisterErrorPageFilter(false);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}

Spring Boot, Websockets cannot obtain user (i.e, java.security.Principal) from Session

Working with Spring Boot 1.2.1.RELEASE and Spring Websockets. Having a deployment runtime issue where when running embedded Jetty 9, I cannot fake a user (java.security.Principal) successfully when app deployed anywhere else but localhost.
I have consulted
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-authentication
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-runtime-configuration
The config below (I believe) already "upgrades" a request
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// see http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-handle-broker-relay
// requires an external broker like AMQP or RabbitMQ
//registry.enableStompBrokerRelay("/queue/", "/topic/");
// XXX This might wind up being the impl we actually deploy; but be aware it has certain constraints
// see http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/cards").setHandshakeHandler(new UserHandler()).withSockJS();
}
// cheat; ensure that we have a Principal w/o relying on authentication
class UserHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
return new TestPrincipal("bogus");
}
}
And here's the principal...
public class TestPrincipal implements Principal {
private final String name;
public TestPrincipal(String name) {
this.name = name;
}
#Override
public String getName() {
return this.name;
}
}
But this is the exception I'm receiving...
Logger=org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler Type=ERROR Message=Unhandled exception
org.springframework.messaging.simp.annotation.support.MissingSessionUserException: No "user" header in message
at org.springframework.messaging.simp.annotation.support.PrincipalMethodArgumentResolver.resolveArgument(PrincipalMethodArgumentResolver.java:42) ~[spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77) ~[spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:127) ~[spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:100) ~[spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:451) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:443) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:82) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:412) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:350) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at org.springframework.messaging.support.ExecutorSubscribableChannel$SendTask.run(ExecutorSubscribableChannel.java:135) [spring-messaging-4.1.4.RELEASE.jar!/:4.1.4.RELEASE]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_05]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_05]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_05]
What else should I consider here?
UPDATE
I can reproduce this reliably in a localhost deployment now.
Interestingly, using STS 3.6.3 in debug mode, when I set a breakpoint at line 91 of JettyRequestUpgradeStrategy
public JettyRequestUpgradeStrategy(WebSocketServerFactory factory) {
Assert.notNull(factory, "WebSocketServerFactory must not be null");
this.factory = factory;
this.factory.setCreator(new WebSocketCreator() {
#Override
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response) {
// Cast to avoid infinite recursion
return createWebSocket((UpgradeRequest) request, (UpgradeResponse) response);
}
Then continue on to another breakpoint set at line 41 of PrincipalMethodArgumentResolver
#Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Principal user = SimpMessageHeaderAccessor.getUser(message.getHeaders());
if (user == null) {
throw new MissingSessionUserException(message);
}
return user;
}
the user is null. Is there a race condition? E.g., is there some time limit within which the socket must acquire the user from request?

CometD publish a message back to a client

I am having a problem in sending back a message to a client. Below is my code
JavaScript
dojox.cometd.publish('/service/getservice', {
userid : _USERID,
});
dojox.cometd.subscribe('/service/getservice', function(
message) {
alert("abc");
alert(message.data.test);
});
Configuration Servlet
bayeux.createIfAbsent("/service/getservice", new ConfigurableServerChannel.Initializer() {
#Override
public void configureChannel(ConfigurableServerChannel channel) {
channel.setPersistent(true);
GetListener channelListner = new GetListener();
channel.addListener(channelListner);
}
});
GetListener class
public class GetListener implements MessageListener {
public boolean onMessage(ServerSession ss, ServerChannel sc) {
SomeClassFunction fun = new SomeClassFunction;
}
}
SomeClassFunction
class SomeClassFunction(){
}
here i am creating a boolean variable
boolean success;
if it is true send a message to client which is in javascript. how to send a message back to client. i have tried this line also.
remote.deliver(getServerSession(), "/service/getservice",
message, null);
but it is giving me an error on remote object and getServerSession method.
In order to reach your goal, you don't need to implement listeners nor to configure channels. You may need to add some configuration at a later stage, for example in order to add authorizers.
This is the code for the ConfigurationServlet, taken from this link:
public class ConfigurationServlet extends GenericServlet
{
public void init() throws ServletException
{
// Grab the Bayeux object
BayeuxServer bayeux = (BayeuxServer)getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);
new EchoService(bayeux);
// Create other services here
// This is also the place where you can configure the Bayeux object
// by adding extensions or specifying a SecurityPolicy
}
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
throw new ServletException();
}
}
This is the code for EchoService class, taken fro this link:
public class EchoService extends AbstractService
{
public EchoService(BayeuxServer bayeuxServer)
{
super(bayeuxServer, "echo");
addService("/echo", "processEcho");
}
public void processEcho(ServerSession remote, Map<String, Object> data)
{
// if you want to echo the message to the client that sent the message
remote.deliver(getServerSession(), "/echo", data, null);
// if you want to send the message to all the subscribers of the "/myChannel" channel
getBayeux().createIfAbsent("/myChannel");
getBayeux().getChannel("/myChannel").publish(getServerSession(), data, null);
}
}

Categories

Resources