Spring 4 + Websockets: how to close the session? - java

I am using Spring 4 + Websockets + Stomp JS library.
I could not find any way to setup websocket ping/pong mechanism (heartbeat).
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...">
<websocket:message-broker>
<websocket:stomp-endpoint path="/cors/auth/clientEndpoint">
<websocket:handshake-handler ref="myHandshakeHandler" />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/queue, /topic" />
<websocket:client-inbound-channel>
<websocket:interceptors>
<bean class="com.mycompany.myproject.utils.messaging.MyInboundChannelInterception"></bean>
</websocket:interceptors>
</websocket:client-inbound-channel>
</websocket:message-broker>
<bean id="myHandshakeHandler" class="com.mycompany.myproject.utils.security.MyHandshakeHandler" />
<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
<property name="maxSessionIdleTimeout" value="120000" />
</bean>
As result, I am implementing my own mechanism of ping/pong messages.
One of the tasks here - to implement server side closure of the websocket in case if no ping message during more than 10s from client.
And no way to do this using Spring Websockets!
Maybe somebody can tell me how to access Session object of the user or to close those Session via Spring Websockets?
Seems Spring is very limited here.

I'm surprised spring doc doesn't mention how to config server ping...It seems that spring expects us to read code instead of read doc..
after some time searching on net and reading source code, I realize it's already supported, but not documented at a noticeable place like spring websocket doc.
I'm using spring 4.3.3, and here is how to config server ping without using sockJS:
#Configuration
#EnableWebSocketMessageBroker
public class StompOverWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
ThreadPoolTaskScheduler pingScheduler = new ThreadPoolTaskScheduler();
pingScheduler.initialize();
registry.enableSimpleBroker("/topic")
.setHeartbeatValue(new long[]{20000, 0}).setTaskScheduler(pingScheduler);
}
....
}
and should make sure that you correctly set web socket session timeout, it should be greater than ping interval, like this:
<bean id="servletServerContainerFactoryBean" class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
<property name="maxSessionIdleTimeout" value="30000"/>
</bean>

In this case, configuring SockJS in your app could go a long way:
<websocket:stomp-endpoint path="/cors/auth/clientEndpoint">
<websocket:handshake-handler ref="myHandshakeHandler" />
<websocket:sockjs/>
</websocket:stomp-endpoint>
This will give you:
better HTTP clients support
heartbeat management
If you want to actually close a session from STOMP endpoints, then I suggest you to vote/follow the SPR-12288 JIRA issue.

To access websocket session you can use the following approach:
https://stackoverflow.com/a/32270216/2982835

Related

Spring AmqpTemplate nested autowiring

I have a service that is responsible for placing a message on a RabbitMQ queue. I have set up the AMQP config in such a way that I can autowire the AmqpTemplate class into the service. This config works when I move the logic into the body of a JUnit test.
However, When I create a test with the service autowired in and call the method to trigger the AmqpTemplates convertAndSend method nothing happens. Using wireshark I have seen that it still handshakes with the RabbitMQ server but no exchange is created and no messages appear in any queue even when I am making use of RabbitMQ's firehose trace options.
The code is as follows:
<!-- AMQP messaging configurations starts here -->
<!-- Spring AMQP connection factory -->
<rabbit:connection-factory id="connectionFactory"
host="localhost"
port="5672"
username="guest"
password="guest"
channel-cache-size="25"/>
<!-- Queues -->
<rabbit:queue name="test.queue"/>
<!-- Exchanges with their queue bindings -->
<rabbit:topic-exchange name="test.exchange">
<rabbit:bindings>
<rabbit:binding queue="test.queue" pattern="test.*"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- Spring AMQP template - Creates a bean which can send a message to the topicExchange-->
<rabbit:template id="testTemplate"
connection-factory="connectionFactory"
exchange="test.exchange"/>
<!-- Spring AMQP Admin -->
<rabbit:admin connection-factory="connectionFactory"/>
The above code segment appears in my application context that is used for the JUnit tests.
#Service
public class AsyncQueueServiceImplementation implements AsyncQueueService
{
#Autowired
private AmqpTemplate template;
#Override
#Async
public void publish()
{
template.convertAndSend("test.debug", "test payload");
}
}
The above code segment is the service that is responsible for actually sending an object to the AmqpTemplate. Please not that the AmqpTemplate is autowired in here.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:application-context-unitTests.xml" })
public class AsyncTraceServiceImplementationTest
{
#Autowired
AsyncTraceService traceService;
#Test
public void testPublishAtDebugLevel()
{
traceService.publish();
}
}
The above segment is the JUnit test. It uses the application context that contains all the rabbit mq configuration. It then autowires in the service and calls the message.
When I place simple System.out.println's around, I can see that in both the service the AmqpTemplate is instantiated but doesn't seem to do what is expected.
Could this perhaps be an issue with the context not being passed on to the service for some reason.
I have tried using ReflectionTestUtils to set the template field in the service from the Junit test however I was unable to do so.
I managed to fix this issue.
The issue was in the RabbitMQ topics that I was using. The exchange received an unknown topic and as a result did nothing with it.
Correcting this resulted in the messages appearing in the correct queue as the exchange was then able to route the messages correctly.

Spring AMQP: Queue with machine name

I'm working with Spring AMQP to create queues in RabbitMQ. I'd like to have a queue whose name includes the name of the machine that the app is running on. So the queue name might be "fooQueue.host1" or "fooQueue.host2" depending on where you run the app.
I've figured out a way to do this (detailed below) but it seems a little complicated. Is there an easier/better/Spring-ier way to accomplish this?
My Solution
First make a bean to fetch the machine name:
public class MachineNamePropertyBean {
public String GetMachineName() throws UnknownHostException {
InetAddress localMachine = InetAddress.getLocalHost();
return localMachine.getHostName();
}
}
Then register the bean in your Spring config:
<bean id="machineNameBean" class="com.example.myapp.MachineNamePropertyBean" />
then use it in your Spring AMQP config like this:
<rabbit:queue id="fooQueue"
name="fooQueue.#{ machineNameBean.GetMachineName() }"
durable="false"
auto-delete="false"
exclusive="false" />
There is no other solution unless using SpEL:
<bean id="machineName" class="java.lang.String">
<constructor-arg value="#{T(java.net.InetAddress).localHost.hostName}"/>
</bean>
<rabbit:queue id="fooQueue"
name="fooQueue.#{ machineName }"
durable="false"
auto-delete="false"
exclusive="false" />
The same as you are doing, but without new class and via SpEL features.

Why do we use <cxf:rsServer> as opposed to a plain <jaxrs:server> when using the CXF-RS component?

As a follow-up to this question, I'm still a bit confused about how to properly use the CXF-RS component.
I'm confused why we need the <cxf:rsServer> tag for specifying CXF-RS endpoints (or is there even such a concept?), when I can use the <jaxrs:server> tag perfectly fine.
Here's my configuration XML for both Camel and CXF:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<jaxrs:server id="userService" address="/users">
<jaxrs:serviceBeans>
<bean class="com.example.UserServiceNoop" />
</jaxrs:serviceBeans>
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
</jaxrs:providers>
</jaxrs:server>
<bean id="user" class="org.apache.camel.component.direct.DirectComponent" />
<camel:camelContext id="someCamelContext">
<camel:route id="userServiceRoute">
<camel:from uri="cxfrs:bean:userService" />
<camel:routingSlip>
<camel:simple>user:${header.operationName}</camel:simple>
</camel:routingSlip>
</camel:route>
<camel:route id="userServiceRetrieveUser">
<from uri="user:retrieveUser" />
<!-- Assume this is going to a useful Processor -->
</camel:route>
</camel:camelContext>
</beans>
UserService.java:
package com.example;
/* a bunch of imports... */
public interface UserService {
#GET
#Path(value="/{user.id}")
#Produces({MediaType.APPLICATION_JSON})
public User retrieveUser(
#PathParam("user.id") Integer id
);
}
UserServiceNoop.java
package com.example;
/* a bunch of imports ... */
public class UserServiceNoop implements UserService
{
#Override
public User retrieveUser(Integer id) {
throw new RuntimeException();
}
}
In this example, I'm not using any <cxf:rsServer> tag, yet it works fine. I know it goes through the CXF-RS component, because when I run the application, it doesn't throw any RuntimeExceptions, which is the expected behavior when using CXF-RS (the method implementation in the service class will not be called).
Am I missing something by not using this tag?
As the other answer says, the cxf:rsServer is mainly used to be processed by a Camel route as in the jaxrs:server the processing of the request is done by a classic controller.
For example:
Classic JAXRS server:
You will declare a classic Bean Rest (Controller) and inject a Service inside.
Sample of an XML config (extract):
<jaxrs:server id="deviceServiceSvcV1" address="/device/v1">
<jaxrs:serviceBeans>
<ref component-id="deviceServiceRest" />
</jaxrs:serviceBeans>
<!-- and other providers, interceptors, etc... here -->
</jaxrs:server>
<!-- Service bean -->
<bean id="deviceServiceRest" class="org.mycomp.device.rest.v1.ws.api.DeviceServiceRest">
<property name="deviceService" ref="deviceService" />
</bean>
The Controller class will process the request / response in a classic way (e.g. calling an injected service).
Camel route with cxf:rsServer
Sample of an XML config (extract):
<cxf:rsServer id="rsServer" address="/device/v1"
serviceClass="org.mycomp.device.rest.v1.ws.api.DeviceServiceRest">
<cxf:properties>
<!-- whatever here -->
</cxf:properties>
<!-- and other interceptors, etc... here -->
</cxf:rsServer>
and in the classes:
#Produces({ MediaType.APPLICATION_XML })
#Path("/")
public class DeviceServiceRest {
#GET
public Response listDevicess(
#QueryParam("model") String model,
#QueryParam("sid") String sid,
) {
return null; // never used
}
#GET
#Path("{id}")
public Response getDeviceById(
#PathParam("id") String id,
#QueryParam("model") String model,
#QueryParam("sid") String sid
){
return null; // never used
}
}
The REST Controller has empty methods (returning null) but I think the latest camel-cxf supports now an Interface which is more elegant than having methods returning null.
Now, the request processing can be implemented by a Camel Route like this:
from("cxfrs:bean:rsServer?synchronous=true")
.routeId("cxf-device-rest-v1")
.process( new CheckAuthenticationProcessor())
.choice()
.when(header("operationName").isEqualTo("listDevice"))
.setHeader("backenOperation").constant("list")
.setHeader("backendResource").constant("device")
.endChoice()
.when(header("operationName").isEqualTo("getDeviceById"))
.setHeader("backenOperation").constant("retrieve")
.setHeader("backendResource").constant("device")
.endChoice()
.end()
.bean("requestProcessor")
.to(InOut, backendEndpoint)
.process(checkResponseStatusCode())
.bean(new HttpResponseProcessor())
;
And you can also control the request / response processing as you want from the route.
These are two different kind of implementing a REST API (server side) but in my opinion this is a bit old school as modern framework like spring-boot does not need any of these.
I found the second way a bit too much overkill as I like Camel for integration purpose but using it for a REST API could be subject to discussion.
One use-case I can see is a HTTP REST Web-Service for asynchronous processing, the service responding 202 Accepted and the Camel Route making an integration of the request in asynchronous mode especially when a specific Camel Component can be easily used instead of a complex class (or any need of the EIP patterns).
You use cxf:reserver tag when you want to use CXF endpoint as the consumer of something. Say for example in a complex Apache Camel route or in Spring integration. is used when you are the provider of the endpoint serving requests.

Apache CXF + Spring: Generating a Simple Client

I've started learning the Apache CXF with Spring. First of all, I've tried to create a simple client/server model.
The server-side is:
service.HelloWorld.java
#WebService
public interface HelloWorld {
String sayHi(String text);
}
service.HelloWorldImpl.java
#WebService(endpointInterface = "service.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
public String sayHi(String text) {
return "Hello, " + text;
}
}
The client-side is:
client.Client.java
public class Client {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"cxf-client-servlet.xml"});
HelloWorld client = (HelloWorld) context.getBean("client");
System.out.println(client.sayHi("Batman"));
}
}
cxf-client-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schema/jaxws.xsd">
<bean id="client" class="service.HelloWorld" factory-bean="clientFactory" factory-method="create"/>
<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="service.HelloWorld"/>
<property name="address" value="http://localhost:8080/services/HelloWorld"/>
</bean>
The problem is: to make the client work I've had to add service.HelloWorld (package + interface) to the clients's project. I've heard that before using a service I need to generate a stub. So it's confusing for me. So that, what is the correct approach and what is the best practice (may be it is better to use some contract-first approach or suchlike)? Later, I want to add WS-Security, so I need a strong background =)
Thanks in advance.
You can use a simple spring configuration like this for client side -
<jaxws:client id="mywebServiceClient"
serviceClass="com.saurzcode.TestService"
address="http://saurzcode.com:8088/mockTestService">
<jaxws:binding>
<soap:soapBinding version="1.2" mtomEnabled="true" />
</jaxws:binding>
</jaxws:client>
<cxf:bus>
<cxf:outInterceptors>
<bean class="com.saurzcode.ws.caller.SoapHeaderInterceptor" />
</cxf:outInterceptors>
</cxf:bus>
Ignore the Interceptor if you don't need it.
More details in this post.
If you are doing code-first WS development then it is fine to distribute the interface and give it to the client. I believe #WebService is not needed (?) on the interface (only implementation), so the client does not have dependencies on this annotation.
Even if you are doing code-first web-services, you may still download the WSDL document generated for you by Apache CXF and give it to the client instead. With this approach (which is considered more mature, not to mention it can be used on different platforms like .NET) the client has to generate the stubs (using tool like wsdl2java). This process will essentially create very similar client interface automatically.
That's one of the reasons why so many people prefer contract-first development - the same WSDL is used to generate client-side stubs and server-side WS implementation. This limits the scope of (accidental) incompatibilites.

How to receive what was sent by convertAndSend?

I'm reading Spring Framework reference, chapter about JMS integration. There are some examples for sending text messages and asynchronously receiving them (by listeners). And there is also an example for JmsTemplate function convertAndSend which converts given object to a message. The reference says:
By using the converter, you and your application code can focus on the business object that is being sent or received via JMS and not be concerned with the details of how it is represented as a JMS message.
But there is no example for receiving such messages. They mention function receiveAndConvert but, unfortunately, it receives synchronously.
So how am I to receive it asynchronously? Must I be aware that when I convertAndSend a Map, the resulting message will be a MapMessage, and just check in my listener for this type of message and handle it? But they promised I'm not to be concerned with the details of how it is represented as a JMS message.
So is there a better way?
I know it's been a while since this was asked, but I had the same problem, solved it and wanted to give an explicit code example here.
Here's my MessageListener. This implements the onMessage(Message) method to intercept messages asynchronously.
package com.package.amqp;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.support.converter.JsonMessageConverter;
import com.package.model.User;
public class TestListener implements MessageListener {
public void onMessage(Message message) {
JsonMessageConverter jmc = new JsonMessageConverter();
User u = (User)jmc.fromMessage(message);
System.out.println("received: " + u.getFirstName());
}
}
The messages are then converted using the standard JsonMessageConvertor in my case as this is the messageConvertor I plugged into my rabbitTemplate bean.
<bean id="rabbitConnectionFactory" class="org.springframework.amqp.rabbit.connection.SingleConnectionFactory">
<constructor-arg value="10.10.1.2"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
</bean>
<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="queueName" value="queue.helloWorld"/>
<property name="messageListener" ref="someListener"/>
</bean>
<bean id="someListener" class="com.package.amqp.TestListener"></bean>
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.JsonMessageConverter"/>
</property>
</bean>
Hope this helps someone!
Owen
While JmsTemplate provides basic synchronous receive methods, asynchronous reception is a whole lot more complicated, and is beyond the scope of JmsTemplate.
Asynchronous reception of JMS messages is done in Spring using Message Listener Containers, which asynchronously take messages from the JMS destination and pass them to your application. You can plug a MessageConverter in to your message listener container via a MessageListenerAdapter (plug the converter into the adapter, plug your application's listener into the adapter, then plug the adapter into the listener container).

Categories

Resources