SSL enabled RESTful services are quite easier to develop and test using Jersey, Grizzly and RestTemplate.

Jersey (resource development)
Grizzly Web Server (resource configuration and deployment)
Spring 3 RestTemplate backed by Commons HTTP Client (resource access)

In a moment, you will notice how all these nicely fit the bill. Let us start with the POM for the maven fans.

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.web</artifactId>
            <version>3.0.0.M4</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.1.2-ea</version>
        </dependency>
        <dependency>
            <groupId>com.sun.grizzly</groupId>
            <artifactId>grizzly-servlet-webserver</artifactId>
            <version>1.9.18a</version>
        </dependency>
        <dependency>
            <groupId>org.apache.log4j</groupId>
            <artifactId>com.springsource.org.apache.log4j</artifactId>
            <version>1.2.15</version>
        </dependency>
    </dependencies>

Configuring Log4j is very useful as you could see the commons Client debug messages and http wire headers which are quite useful for debugging in case if you were lost in translation.

Putting together all these pieces working did not take much of my time. I did not have to do anything fancy here as I just reused most of the sample code from the Jersey HTTPS sample and Commons HTTP Client SSL sample.

Lets dive into the Spring Config, which does most of the wiring of HTTP Client and RestTemplate.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sslClient" class="spring3.restclient.RestSSLClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="credentials"/>
    </bean>

    <bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
        <property name="authenticationPreemptive" value="true"/>
        <property name="connectionManagerClass"
                  value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
    </bean>
    <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
        <constructor-arg ref="httpClientParams"/>
    </bean>
    <bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials">
        <constructor-arg value="admin"/>
        <constructor-arg value="adminadmin"/>
    </bean>
    <bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
        <constructor-arg ref="httpClient"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg ref="httpClientFactory"/>
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            </list>
        </property>
    </bean>
</beans>

The below code configures a Grizzly Server with SSL support for server side certificates, Basic Auth filter and Jersey resource servlet.

import com.sun.grizzly.SSLConfig;
import com.sun.grizzly.http.embed.GrizzlyWebServer;
import com.sun.grizzly.http.servlet.ServletAdapter;
import com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.samples.https_grizzly.auth.SecurityFilter;

import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;

public class GrizzlyServer {

    private static GrizzlyWebServer webServer;

    public static final URI BASE_URI = getBaseURI();

    private static URI getBaseURI() {
        return UriBuilder.fromUri("https://localhost/").port(getPort(4463)).build();
    }

    private static int getPort(int defaultPort) {
        String port = System.getenv("JERSEY_HTTP_PORT");
        if (null != port) {
            try {
                return Integer.parseInt(port);
            } catch (NumberFormatException e) {
            }
        }
        return defaultPort;
    }

    protected static void startServer() {

        webServer = new GrizzlyWebServer(getPort(4463), ".", true);

        // add Jersey resource servlet

        ServletAdapter jerseyAdapter = new ServletAdapter();
        jerseyAdapter.addInitParameter("com.sun.jersey.config.property.packages", "server.https.auth;server.https.resource");
        jerseyAdapter.setContextPath("/");
        jerseyAdapter.setServletInstance(new ServletContainer());

        // add security filter (which handles http basic authentication)
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, SecurityFilter.class.getName());
        // add authorization filter
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, RolesAllowedResourceFilterFactory.class.getName());

        webServer.addGrizzlyAdapter(jerseyAdapter, new String[]{"/"});


        // Grizzly ssl configuration
        SSLConfig sslConfig = new SSLConfig();

        sslConfig.setNeedClientAuth(true); // don't work - known grizzly bug, will be fixed in 2.0.0

        // set up security context
        String keystore_server = Thread.currentThread().getContextClassLoader().getResource("keystore_server").getFile();
        String truststore_server = Thread.currentThread().getContextClassLoader().getResource("truststore_server").getFile();

        sslConfig.setKeyStoreFile(keystore_server); // contains server keypair
        sslConfig.setKeyStorePass("secret");
        sslConfig.setTrustStoreFile(truststore_server); // contains client certificate
        sslConfig.setTrustStorePass("secret");

        webServer.setSSLConfig(sslConfig);

        // turn server side client certificate authentication on

//        ((SSLSelectorThread) webServer.getSelectorThread()).setNeedClientAuth(true);

        try {
            // start Grizzly embedded server //
            System.out.println(String.format("Jersey app started with WADL at %sapplication.wadl", BASE_URI));
            webServer.start();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    protected static void stopServer() {
        webServer.stop();
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        startServer();
        System.out.println("Hit return to stop...");
        System.in.read();
        stopServer();
    }
}

Here’s slightly modified version of the sample Jersey Security filter which would handle the HTTP basic authentication on the server. The auth helper classes (AuthenticationExceptionMapper, AuthenticationException) are found here.

package com.sun.jersey.samples.https_grizzly.auth;

import com.sun.jersey.api.container.MappableContainerException;
import com.sun.jersey.core.util.Base64;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import java.security.Principal;

public class SecurityFilter implements ContainerRequestFilter {

    @Context
    UriInfo uriInfo;
    private static final String REALM = "HTTPS Example authentication";

    public ContainerRequest filter(ContainerRequest request) {
        User user = authenticate(request);
        request.setSecurityContext(new Authorizer(user));
        return request;
    }

    private User authenticate(ContainerRequest request) {
        // Extract authentication credentials
        String authentication = request.getHeaderValue(ContainerRequest.AUTHORIZATION);
        if (authentication == null) {
            throw new MappableContainerException
                    (new AuthenticationException("Authentication credentials are required", REALM));
        }
        if (!authentication.startsWith("Basic ")) {
            return null;
            // additional checks should be done here
            // "Only HTTP Basic authentication is supported"
        }
        authentication = authentication.substring("Basic ".length());
        String[] values = new String(Base64.base64Decode(authentication)).split(":");
        if (values.length < 2) {
            throw new WebApplicationException(400);
            // "Invalid syntax for username and password"
        }
        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;
        if ((username == null) || (password == null)) {
            throw new WebApplicationException(400);
            // "Missing username or password"
        }

        // Validate the extracted credentials
        User user = null;

        if (username.equals("john") && password.equals("secret")) {
            user = new User("john", "user");
            System.out.println("USER 'John Doe' AUTHENTICATED");
        } else if (username.equals("jane") && password.equals("secret")) {
            user = new User("jane", "user");
            System.out.println("USER 'Jane Doe' AUTHENTICATED");
        } else if (username.equals("admin") && password.equals("adminadmin")) {
            user = new User("admin", "admin");
            System.out.println("ADMIN AUTHENTICATED");
        } else {
            System.out.println("USER NOT AUTHENTICATED");
            throw new MappableContainerException(new AuthenticationException("Invalid username or password\r\n", REALM));
        }
        return user;
    }

    public class Authorizer implements SecurityContext {

        private User user;
        private Principal principal;

        public Authorizer(final User user) {
            this.user = user;
            this.principal = new Principal() {

                public String getName() {
                    return user.username;
                }
            };
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public boolean isUserInRole(String role) {
            return (role.equals(user.role));
        }

        public boolean isSecure() {
            return "https".equals(uriInfo.getRequestUri().getScheme());
        }

        public String getAuthenticationScheme() {
            return SecurityContext.BASIC_AUTH;
        }
    }

    public class User {

        public String username;
        public String role;

        public User(String username, String role) {
            this.username = username;
            this.role = role;
        }
    }
}
&#91;/sourcecode&#93;

The resource class is very simple. The resource methods are access controlled using the JSR-250 annotation @RolesAllowed. The methods are self-explanatory and they are just coded for illustration, not a fool-proof implementation. In this sample, the Grizzly server would perform server-side certificate authentication and HTTP Basic authentication, in addition to basic authorization checks.

&#91;sourcecode language='java'&#93;
import com.sun.jersey.core.util.Base64;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/")
public class HttpsResource {

    @GET
    @RolesAllowed({"admin"})
    @Path("/locate/{username}")
    public Response getUserLocation(@Context HttpHeaders headers, @PathParam("username") String username) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Location for : " + username + " requested by " + getUser(headers));
        return Response.ok("Billings, Montana").type(MediaType.TEXT_PLAIN).build();
    }

    @GET
    @RolesAllowed({"admin", "user"})
    public Response getUserPin(@Context HttpHeaders headers) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Pin for: " + getUser(headers));
        return Response.ok("1234").type(MediaType.TEXT_PLAIN).build();
    }

    private String getUser(HttpHeaders headers) {
        String auth = headers.getRequestHeader("authorization").get(0);

        auth = auth.substring("Basic ".length());
        String&#91;&#93; values = new String(Base64.base64Decode(auth)).split(":");

        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;

        return username;
    }
}
&#91;/sourcecode&#93;

The following steps guide to create sample client and server certificates using the JDK keytool utility. The self-signed certificates are used for demonstration purposes only. In reality, this would be performed by a Certificate Authority (for ex: Verisign).

<strong><ul>generate client and server keys:</ul></strong>

keytool -genkey -keystore keystore_client -alias clientKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"
keytool -genkey -keystore keystore_server -alias serverKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"

<strong><ul>generate client and server certificates:</ul></strong>

keytool -export -alias clientKey -rfc -keystore keystore_client > client.cert
keytool -export -alias serverKey -rfc -keystore keystore_server > server.cert

<strong><ul>import certificates to corresponding truststores:</ul></strong>

keytool -import -alias clientCert -file client.cert -keystore truststore_server
keytool -import -alias serverCert -file server.cert -keystore truststore_client


SSL helper classes (AuthSSLProtocolSocketFactory, AuthSSLX509TrustManager, AuthSSLInitializationError) for the client-side are used from the Commons Client SSL <a href="http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/">contrib</a> samples.

RestTemplate is injected into the RestSSLClient which uses the Commons Client APIs to set the credentials and configures the keystore and truststore on the client-side.


import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.contrib.ssl.AuthSSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.HashMap;

public class RestSSLClient {
    private final RestTemplate restTemplate;
    private final HttpClient client;
    private Credentials credentials;
    private static final int HTTPS_PORT = 4463;
    private static final String HTTPS_GET = "https://localhost:4463/";
    private static final String HTTPS_GET_LOCATION = "https://localhost:4463/locate/{username}";
    private static final String HTTPS = "https";
    private static final String HTTPS_HOST = "localhost";

    public RestSSLClient(RestTemplate restTemplate, Credentials credentials) {
        this.restTemplate = restTemplate;
        this.credentials = credentials;
        CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) restTemplate.getRequestFactory();
        this.client = factory.getHttpClient();
        client.getState().setCredentials(AuthScope.ANY, credentials);
        try {
            URL keystore_client = Thread.currentThread().getContextClassLoader().getResource("keystore_client").toURI().toURL();
            URL truststore_client = Thread.currentThread().getContextClassLoader().getResource("truststore_client").toURI().toURL();
            ProtocolSocketFactory protocolSocketFactory = new AuthSSLProtocolSocketFactory(keystore_client, "secret",
                    truststore_client, "secret");
            Protocol authhttps = new Protocol(HTTPS, protocolSocketFactory, HTTPS_PORT);
            Protocol.registerProtocol(HTTPS, authhttps);
            client.getHostConfiguration().setHost(HTTPS_HOST, HTTPS_PORT, authhttps);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    public void setCredentials(String user, String pass) {
        this.credentials = new UsernamePasswordCredentials(user, pass);
        client.getState().setCredentials(AuthScope.ANY, credentials);
    }

    public String get() {
        return restTemplate.getForObject(HTTPS_GET, String.class);
    }

    public String getLocation(String user) {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("username", user);
        return restTemplate.getForObject(HTTPS_GET_LOCATION, String.class, vars);
    }
}

The test code which invokes the SSL configured resource is shown below.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Spring3RestSSLClient {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-ssl.xml");
        RestSSLClient client = applicationContext.getBean("sslClient", RestSSLClient.class);
        System.out.println("John's Location : " + client.getLocation("john"));
        System.out.println("Jane's Location : " + client.getLocation("jane"));
        client.setCredentials("john", "secret");
        System.out.println("John Doe's Pin : " + client.get());
        client.setCredentials("jane", "secret");
        System.out.println("Jane Doe's Pin : " + client.get());
    }
}

WADL for this resource can be accessed from https://localhost:4463/application.wadl. You could access the URL from a browser as the server side client certificate authentication is disabled in the GrizzlyServer. (Uncommenting line # 70 would enable server side client cert auth, but this would force the browser to use the generated client keys). Test it yourself, you would be presented with a basic auth dialog (valid user/pass/role: admin/adminadmin/admin, john/secret/user, jane/secret/user) and you could access the resource methods with specified roles. I have tested with Firefox and Chrome. Enjoy!

Possibly Related Posts:


The central API for accessing RESTful services in Spring 3 is RestTemplate. It is like any other template mechanism provided by Spring for the client side access. The default implementation uses the java.net package for creating HTTP requests. RestTemplate can be backed by Commons HttpClient using ClientHttpRequestFactory. Using Commons HttpClient as the backend implementation supports basic authentication in the requests. The javadocs were the single source of information for Spring3 REST support until M2, but you can find the latest reference documentation with M3 release here. Chapter 18 covers REST support in Spring 3. You can also read Arjen’s blog posts here and here which outlines the REST support in Spring 3 both on the server-side and client-side.

I was playing with the RestTemplate and was quite impressed with it as it comes handy for Spring developers. But, this implementation is no different than most other REST Client APIs. Long story short, this is yet another attempt to provide a non-standard Client for accessing RESTful resources. I am leaning more towards the need for a standard JAX-RS client API and I hope JAX-RS 2.0 fuels that interest. Lets explore with an example how Spring 3 provides native support for accessing RESTful resources using RestTemplate.

Twitter is one of the most widely used service which has a decent REST API with basic authentication.

Lets start with the POM dependencies. I had built the latest Spring3 M3 snapshot on my box, but you should be able to fetch them from Spring maven snapshots repository.

<repositories>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - External Bundle Milestones</id>
        <url>http://repository.springsource.com/maven/bundles/milestone</url>
    </repository>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</id>
        <url>http://repository.springsource.com/maven/bundles/release</url>
    </repository>
    <repository>
        <id>SpringSource Enterprise Bundle Repository - External Bundle Releases</id>
        <url>http://repository.springsource.com/maven/bundles/external</url>
    </repository>
</repositories>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>org.springframework.web</artifactId>
        <version>3.0.0.M3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>org.springframework.xml</artifactId>
        <version>1.5.5.A</version>
    </dependency>
    <dependency>
        <groupId>org.apache.log4j</groupId>
        <artifactId>com.springsource.org.apache.log4j</artifactId>
        <version>1.2.15</version>
    </dependency>
    <dependency>
        <groupId>commons-httpclient</groupId>
        <artifactId>commons-httpclient</artifactId>
        <version>3.1</version>
    </dependency>
</dependencies>

The Spring configuration defines the beans required for this sample.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
    <bean id="twitterClient" class="spring3.restclient.TwitterClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="credentials"/>
    </bean>

    <bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
        <property name="authenticationPreemptive" value="true"/>
        <property name="connectionManagerClass"
                  value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
    </bean>
    <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
        <constructor-arg ref="httpClientParams"/>
    </bean>
    <bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials">
        <constructor-arg value="username"/>
        <constructor-arg value="password"/>
    </bean>
    <bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
        <constructor-arg ref="httpClient"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg ref="httpClientFactory"/>

        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
                    <property name="marshaller" ref="jaxbMarshaller"/>
                    <property name="unmarshaller" ref="jaxbMarshaller"/>
                </bean>
                <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>twitter.model.Statuses</value>
            </list>
        </property>
    </bean>
</beans>

MarshallingHttpMessageConverter is used to marshal/unmarshal JAXB beans. Statuses is a JAXB bean which is bound to the response returned by GET on the Twitter URI https://twitter.com/statuses/friends_timeline.xml. FormHttpMessageConverter is used to construct form parameters to POST on the Twitter URI https://twitter.com/statuses/update.xml.

Now, we will see the RestTemplate in action. You can see the code is just a one liner.

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.auth.AuthScope;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.util.MultiValueMap;
import org.springframework.util.LinkedMultiValueMap;
import twitter.model.Statuses;

import java.util.HashMap;
import java.util.Map;

public class TwitterClient {

    private final RestTemplate restTemplate;

    private final Credentials credentials;

    private static final String twitterGet = "https://twitter.com/statuses/friends_timeline.xml?count={count}";
    private static final String twitterPost = "https://twitter.com/statuses/update.xml";

    public TwitterClient(RestTemplate restTemplate, Credentials credentials) {
        this.restTemplate = restTemplate;
        this.credentials = credentials;
        CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) restTemplate.getRequestFactory();
        HttpClient client = factory.getHttpClient();
        client.getState().setCredentials(AuthScope.ANY, credentials);
    }

    public Statuses getStatuses() {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("count", "5");
        return restTemplate.getForObject(twitterGet, Statuses.class, vars);
    }

    public void setStatus(String status) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        map.add("status", status);
        restTemplate.postForLocation(twitterPost, map);
    }
}

The test code which posts the status to your Twitter account is shown below.

public class Spring3RestClientTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        TwitterClient twitter = applicationContext.getBean("twitterClient", TwitterClient.class);
        twitter.setStatus("Spring RestTemplate rocks!");
        Statuses statuses = twitter.getStatuses();
        for (Statuses.Status status : statuses.getStatus()) {
            System.out.println("Text : " + status.getText());
            System.out.println("User : " + status.getUser().getScreenName());
        }
    }
}

Here is an example output of the above test which lists recent five status messages from Twitter:
=====================================================
Text : Spring RestTemplate rocks!
User : aruld
Text : Just finished celebrating with the SpringSource & Hyperic teams at Tres Agaves. Really good to meet everyone! http://twitpic.com/4l1hb
User : cbeams
Text : 8 Java Puzzlers for JavaOne done. Wow. Soon we’ll scrape right through the bottom of the barrel.
User : gafter
Text : Disappointed my Maredsous 8 is all gone. I should’ve bought more.
User : dandiep
Text : Headed to San Francisco for a week of Core Spring training & looking forward to it!
User : cbeams
=====================================================

RestTemplate does serves its purpose, but it could be improved to support additional convenient methods for accessing URIs with query parameters. The other thing I noticed is about making HTTP POST with a name-value pair using postForLocation API. I could not find any details from the documentation so far. For instance, I would like to do a post to the URI http://twitter.com/statuses/update.xml with the form data “status=This is my current status” sent in the HTTP body. I am not sure whether this is possible with the current implementation. I will dig into this further and keep posted on what I find.

Update (5/4): Thanks to Arjen for clarifying the URITemplate use within query parameters and the FormHttpMessageConverter for using Form parameters. The blog has been updated to use these techniques. You could POST a twitter message just fine 🙂

Update (5/10): Updated the reference documentation to the official M3 build from SpringSource and updated the maven POM to use the M3 dependency.

Possibly Related Posts: