Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication

One exciting new feature of Spring 4 is the support for WebSocket, SockJS and STOMP messaging. This allows two way communication between the server and its clients in a Spring MVC web application using the standard point-to-point and publish-subscribe messaging protocols. In this post, I will demonstrate how to set up a basic boilerplate project to start using this new feature. It is in part based on this article.

Maven Setup

First we need to add the Spring messaging modules in the POM file:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-messaging</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-websocket</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>

Spring MVC Configuration

Next, we need to add the message broker config to the Spring MVC config XML file.

<beans
 ...
 xmlns:websocket="http://www.springframework.org/schema/websocket"
 xsi:schemaLocation="
 ...
 http://www.springframework.org/schema/websocket
 http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
            <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
<!-- Other MVC config omitted here-->

The main thing here is the set up of the message broker for handling the message exchange between the server and its clients. This is done via the <message-broker> and its child tags. The tag <websocket:simple-broker> indicates we are using in-memory message broker.

It is easy to understand together with the server and client codes so I will include them below first before attempting to give a bit more explanations by cross-referencing the client and server codes.

Spring MVC Controller

Below is my Spring MVC Controller

 @Controller
 public class MessageController {
      @MessageMapping("/hello")
      @SendTo("/topic/greetings")
      public Greeting greeting(HelloMessage message) throws Exception {
           return new Greeting("Hello, " + message.getName() + "!");
     }
}

The method argument HelloMessage and output Greeting are just POJOs representing the body of the messages being sent and returned.

public class Greeting {
    private String content;
    public Greeting(String content) {
           this.content = content;
    }
    public String getContent() {
      return content;
    }
}
public class HelloMessage {
    private String name;
    public String getName() {
        return name;
    }
}

Client sockJS and STOMP codes

On the client side, I use the sockJS protocol fallback option as outlined in the Spring documentation. The javascript codes are included below

// Create stomp client over sockJS protocol (see Note 1)
 var socket = new SockJS("/hello");
 var stompClient = Stomp.over(socket);

 // callback function to be called when stomp client is connected to server (see Note 2)
 var connectCallback = function() {
      alert("connected!");
      stompClient.subscribe('/topic/greetings', function(greeting){
           alert(JSON.parse(greeting.body).content);
      });
 }; 

 // callback function to be called when stomp client could not connect to server (see Note 3)
 var errorCallback = function(error) {
      // display the error's message header:
      alert(error.headers.message);
 };

 // Connect as guest (Note 4)
 stompClient.connect("guest", "guest", connectCallback, errorCallback);

Note

  1. The client starts by create a sockJS by specifying the endpoint (ie. /hello) to connect to and then a stomp client is created over the socket. The endpoint here should match that defined in the Spring MVC configuration in the lines. Note also the 2nd line referring to sockJS.

    <websocket:stomp-endpoint path=”/hello”>
    <websocket:sockjs/>
    </websocket:stomp-endpoint>

  2. Then a callback function is created and assigned to a variable connectCallback. This is called when a successful connection is made by the stomp client. This allows us to start making subscriptions to messages (as in codes, repeated below) and sending messages. Note the subscription is for the topic “/topic/greetings”

    stompClient.subscribe(‘/topic/greetings’, function(greeting){
    alert(JSON.parse(greeting.body).content);
    });

  3. A error callback function is defined if stomp client fails to connect to server.
  4. This line makes the connection registering the callback functions.

Now we are ready to send messages from the client, e.g. using the following javascript function

// function to send message
 function fnSayHi() {
       stompClient.send("/app/hello", {}, JSON.stringify({ 'name': 'Joe' }));
 }

The message will be sent to the Spring MVC message handler method greeting() as defined via the annotation @MessageMapping(“/hello”).

 <websocket:message-broker application-destination-prefix=”/app”>

Note the prefix “/app” is defined in the Spring config as  application-destination-prefix attribute of the message broker: Note also, the use of @SendTo annotation to direct the message to a given destination. I repeat the controller method below

 @MessageMapping("/hello")
 @SendTo("/topic/greetings")
 public Greeting greeting(HelloMessage message) throws Exception {
      return new Greeting("Hello, " + message.getName() + "!");
 }

That’s it for now.

Advertisements

Unit test Spring RestTemplate webservice client

This blog demonstrates how to setup and test webservice client codes written in Spring RestTemplate.

Obtaining the jar

I am using Spring 3.1 here and have to include the jar file from the spring-mvc-test project into the classpath. The project has since been incorporated into Spring 3.2. See the Spring documentation for details. The codes here are tested against Spring 3.1 and spring-mvc-test. The APIs may be somewhat different in Spring 3.2.

Restful webservice client

My webservice client communicates with the server via XML so I setup RestTemplate with JaxB marshaller and unmarshaller as follows:

    <bean id="exampleRestClient" >
        <property name="template" ref="exampleRestTemplate"></property>
        <property name="baseUrl" value="<server url>"></property>
    </bean>

    <!-- RESTful template and related beans -->
    <bean id="exampleRestTemplate">
        <property name="messageConverters">
            <list>
                   <ref bean="marshallingHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="marshallingHttpMessageConverter">
      <property name="marshaller" ref="jaxb2Marshaller" />
      <property name="unmarshaller" ref="jaxb2Marshaller" />
    </bean>

    <bean id="jaxb2Marshaller">
        <property name="contextPath">
                <value>com.blog.resttemplatetest.jaxb</value>
        </property>
        <property name="schema" value="classpath:myschema.xsd"/>
    </bean>

The webservice client implements the normal CRUD functions by delegating to its RestTemplate bean. I will focus on the put function here and demonstrate how to write unit test for it in next section.

@Component(“exampleRestClient”)
public class ExampleRestClient {

private RestTemplate template;
private String baseUrl; // base url

public ExampleResponse add(final ExampleData resource, final ExampleResponse response) {
return put(baseUrl, resource, response);
}

private ExampleResponse put(final String url, final ExampleData resource, final ExampleResponse response) {
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
final HttpEntity<ExampleData> requestEntity = new HttpEntity<ExampleData>(resource, headers);
final ResponseEntity<ExampleResponse> reponseEntity = template.exchange(url, HttpMethod.PUT, requestEntity,
response.getClass());
return reponseEntity.getBody();
}

// getters and setters

Note that instead of using the put() method in RestTemplate, I use its exchange() method in order to get the return response from the web service, as the RestTemplate#put method does not return anything. Both input and output classes ExampleData and ExampleResponse are JAXB objects generated via xjc from the example XML schema myschema.xsd.

Writing unit test for webservice client

spring-mvc-test provides a mock server class MockRestServiceServer to support client testing. For example, to test the put add method in our ExampleRestClient:

...
import static org.springframework.test.web.client.RequestMatchers.method;
import static org.springframework.test.web.client.RequestMatchers.requestTo;
import static org.springframework.test.web.client.ResponseCreators.withSuccess;
...

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:example-test.xml" })
public class StorefrontRestClientTest {
    private MockRestServiceServer mockServer;

    @Autowired
    private ExampleRestClient service;

    @Before
    public void setUp() {
        mockServer = MockRestServiceServer.createServer(service.getTemplate()); // (1)
    }

    private String loadXmlFile(final String filename) throws IOException {
        // load xml file into String ...
    }

    @Test
    public void testAddReturnCorrectResponse() throws Exception {

        final ObjectFactory factory = new ObjectFactory();
        final String responseXml = loadXmlFile("/test/expectedResponse.xml");

        mockServer.expect(requestTo(service.getBaseUrl()))
                  .andExpect(method(HttpMethod.PUT))
                  .andRespond(withSuccess(responseXml, MediaType.APPLICATION_XML)); // (2)

        final ExampleData exampleData= factory.createExampleData();
        final ExampleResponse response = (ExampleResponse) service.add(exampleData, new ExampleResponse());
        assertEquals(200, response.getStatus()); // (3)
        assertEquals(12345, response.getID()); // (3)
        mockServer.verify(); // (4)
    }
    ...
// file: expectedResponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<tns:exampleResponse xmlns:tns="http://" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.example.com/example example.xsd ">
  <tns:status>200</tns:status>
  <tns:ID>12345</tns:ID>
</tns:exampleResponse>

Note:

  1. Instantiate mock server with the RestTemplate to be tested
  2. This sets the expectations on the request and mocks the response to be returned, in this case the Xml string stored in file expectedResponse.xml (above).  Note the static imports required at the top of the codes for requestTo, addExpect and andResponse.
  3. Junit assertions as would normally included.
  4. Call verify() method to ensure that expectations are being met.

Final words

Client side unit testing is useful and important. It allows you to verify that correct requests are generated by the client and responses received from server are handled properly, without the need of a running server. It is handy when the server and client are being developed concurrently.

Using Spring RestTemplate to consume restful webservice

Introduction

I recently have to write a client to consume a restful webservice using Spring RestTemplate class. The task turns out to be non trivial as there are a few gotchas needed to be handled because of the way the webservice is implemented. I managed to put together a solution after searching through some useful articles online. I summarise what I have done in this article.

Problem 1: SSL

The webservice I am consuming is implemented in another web application but has to be accessed via https. As a result, calling the getObject() method in RestTemplate with the url will return the following exception:

org.springframework.web.client.ResourceAccessException: I/O error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)
… 32 more

I decide to bypass the certificate validation by adding the following codes:

    public static void trustSelfSignedSSL()
{
try
{
final SSLContext ctx = SSLContext.getInstance(“TLS”);
final X509TrustManager tm = new X509TrustManager()
{

public void checkClientTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public void checkServerTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public X509Certificate[] getAcceptedIssuers()
{
return null;
}
};
ctx.init(null, new TrustManager[]
{ tm }, null);
SSLContext.setDefault(ctx);
}
catch (final Exception ex)
{
ex.printStackTrace();
}
}

Note the method is static and needs to be called once before calling the webservice. Now calling RestTemplate.getObject will return something about hostname wrong:

org.springframework.web.client.ResourceAccessException: I/O error: HTTPS hostname wrong:  should be <some url>; nested exception is java.io.IOException: HTTPS hostname wrong:  should be <some url>
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)

… 32 more

The above error can be removed by providing a custom http client factory by extending Spring class SimpleClientHttpRequestFactory:

public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory
{

private final HostnameVerifier verifier;

public MySimpleClientHttpRequestFactory(final HostnameVerifier verifier)
{
this.verifier = verifier;
}

@Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod) throws IOException
{
if (connection instanceof HttpsURLConnection)
{
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
}
super.prepareConnection(connection, httpMethod);
}
}

Note we also need to inject a dummy hostname verifier for the above class to bypass the hostname verification:

public class NullHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(final String hostname, final SSLSession session)
{
return true;
}

}

All then left to do is the inject the client http request factory to the resttemplate:

RestTemplate template = new RestTemplate();

final MySimpleClientHttpRequestFactory factory = new MySimpleClientHttpRequestFactory(verifier);
template.setRequestFactory(factory);

Problem 2 : List Generic / Type Erasure

My second problem with accessing the webservice is that it returns an array of JSON object in the form

[{“no”:”00028″,”l”:”201″,”t”:0,”i”:10,”a”:20},…,{“no”:”00028″,”l”:”201″,”t”:5,”i”:15,”a”:25}]

Because of Java type erasure, calling RestTemplate.getObject(url, List.class) will return a list of LinkedHashMap objects. To marshall the returned json objects into domain objects, the default json message converter MappingJacksonHttpMessageConverter has to be extended as follows:

public class MyJsonMessageConverter extends MappingJacksonHttpMessageConverter
{

@Override
protected JavaType getJavaType(final Class<?> clazz)
{
if (List.class.isAssignableFrom(clazz))
{
final TypeFactory typeFactory = getObjectMapper().getTypeFactory();
return typeFactory.constructCollectionType(ArrayList.class, StockInventory.class);
}
else
{
return super.getJavaType(clazz);
}
}

Note the use of TypeFactory.constructCollectionType method to map list type into my domain object type StockInventory.

Problem 3: Custom deserialiser

The last problem I need to solve is to implement a custom deserialiser in my message converter to handle the coded json object. I do this by extending the getObjectMapper method:

 @Override
public void setObjectMapper(final ObjectMapper objectMapper)
{
// add custom json serialiser for ItemInventory response
final SimpleModule module = new SimpleModule(“StockInventoryModule”, new Version(1, 1, 1, null));
module.addDeserializer(StockInventory.class, new JsonDeserializer<StockInventory>()
{

@Override
public StockInventory deserialize(final JsonParser jp, final DeserializationContext cxt) throws IOException,
JsonProcessingException
{
final StockInventory stockInventory = new StockInventory();
//    final DeserializerProvider provider = cxt.getDeserializerProvider();
while (jp.nextToken() != JsonToken.END_OBJECT)
{
final String fieldName = jp.getCurrentName();
jp.nextToken();
if (“no”.equals(fieldName))
{
stockInventory.setNo(jp.getText());
}
else if (“l”.equals(fieldName))
{
stockInventory.setLocation(jp.getText());
}
… // handle other fields
else
{
throw new IllegalStateException(“unrecognized field [” + fieldName + “]”);
}
}
return stockInventory;
}
});
objectMapper.registerModule(module);
super.setObjectMapper(objectMapper);
}

Now the only thing left to do is to use the customer converter in the RestTemplate:

  final List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
final MappingJacksonHttpMessageConverter converter = new MyJsonMessageConverter();
converter.setObjectMapper(new ObjectMapper());
list.add(converter);
template.setMessageConverters(list);
List<StockInventory> stocks = template.getForObject(url, List.class);

That’s it!

Resources

Thanks for the contributors of the following links where I got the codes from:

  1. SSL – http://stackoverflow.com/questions/1725863/ssl-handshake-issue-using-spring-resttemplate
  2. Customer client HTTP request factory codes from http://www.jroller.com/jurberg/entry/using_a_hostnameverifier_with_spring
  3. JSON list – http://stackoverflow.com/questions/7040279/jackson-json-java-generics-get-linkedhashmap
  4. JSON list – http://stackoverflow.com/questions/6173182/spring-json-convert-a-typed-collection-like-listmypojo

Using Spring 3 to implement RESTful webservice

Introduction

In this post, I will illustrate how to use Spring framework v.3 to implement RESTful webservices for create, read, update and delete (CRUD) operations.

Relationship between CRUD and HTTP Verbs

The mapping between CRUD and HTTP methods is summarized as below:

  • Create – PUT
  • Read – GET
  • Update – POST
  • Delete – DELETE

Note that browser form element only supports GET and POST. To handle PUT and DELETE, the form will need to send a hidden parameter with name “_method” to the server. For example, to submit a PUT request require following html:

<form action=”put.action” method=”post”>

<input type=”hidden” name=”_method” value=”put”/>

// …

<input type=”submit” value=”Add”/>

</form>

Or you can use the form tag in Spring.

Also, the HiddenHttpMethodFilter filter needs to be setup in the web.xml file:

<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
</filter-mapping>

Note that you will need to get ordering of filters right if you are handling multi-part requests. See the API documentation for more details.

CRUD Operations

As an example, I am going to show the request mappings for implementing the REST web services for CRUD operations on a tag resource in a Spring 3 Controller class.

Create maps to a PUT request in the format http://<mydomain>/tag/add.action

@RequestMapping(value=”/tag/add.action”, method=RequestMethod.PUT)

public String put(@Valid @ModelAttribute(“tag”) TagDTO tagDTO, BindingResult result, Model model) {
// …
}

Read maps to a GET request in the format http://<mydomain>/tag/1/view.action

@RequestMapping(value=”/tag/{id}/view.action”, method=RequestMethod.GET)
public String view(@PathVariable(“id”) Long id, Model model) {

// …

}

Update maps to a post request in the format http://<mydomain>/tag/update.action:

@RequestMapping(value=”/tag/update.action”, method=RequestMethod.POST)
public String update(@Valid @ModelAttribute(“tag”) TagDTO tagDTO, BindingResult result, Model model) {
// …
}

Delete maps to a delete request in the format http://<mydomain>/tag/delete.action

@RequestMapping(value=”/tag/delete.action”, method=RequestMethod.DELETE)

public String delete(@RequestParam(“id”) Long id) {

// …

}

That’s it for now.