REST
using-lishys-notes.notes

Guide


The guide pane on the right contains information organized into sections.

Use the and buttons to move through the sections, or click an accordion tab (shown below) to go directly to a particular topic.



Code Reference


Each paragraph in the guide is usually associated with a particular piece of code. An orange arrow indicates that the code displayed in the middle pane is relevant to the guide text.



If the arrow is gray, simply click anywhere on the paragraph and the relevant code will be displayed.



Code Highlighter


The highlight feature identifies areas of the code which relate to a word or term in the guide. Hover the mouse over any underlined word and the relevant code will be highlighted.



You can disable the highlighting feature at any time by clicking on the button.

Don't forget to make the appropriate code page visible by clicking on any paragraph with a gray arrow.
BrowserRestController.java
package lishy.rest.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping(value="/browser")
public class BrowserRestController {
    
    private int id = 100;

    @RequestMapping(value="/resource/{id}", method=RequestMethod.GET)
    public ModelAndView getRequest(@PathVariable("id") int resourceId) {
        return new ModelAndView("rest", "message", "GET request for resource " + resourceId);
    }
    
    @RequestMapping(value="/parent/{parentId}/child/{childId}", method=RequestMethod.GET)
    public ModelAndView getRequestWithParent(@PathVariable("parentId") int parentId,
                                             @PathVariable("childId") int childId) {        
        return new ModelAndView("rest", "message", "GET request for " + parentId + 
                                                   " and " + childId);
    }
    
    @RequestMapping(value="/resource", method=RequestMethod.GET)
    public ModelAndView getRequestForMultipleResources() {
        return new ModelAndView("rest", "message", "GET request for multiple resources (no id)");
    }
    
    @RequestMapping(value="/resource", method=RequestMethod.POST)
    public ModelAndView postRequest(@RequestParam("formfield") String formField) {
        id++;
        return new ModelAndView("rest", "message", "POST created resource " + id + 
                                                   " with " + formField);
    }
    
    @RequestMapping(value="/resource/{id}", method=RequestMethod.PUT)
    public ModelAndView putRequest(@PathVariable("id") int resourceId,
                                   @RequestParam("formfield") String formField) {
        return new ModelAndView("rest", "message", 
                                "PUT created or updated resource " +  resourceId + 
                                " with " + formField);  
    }
    
    @RequestMapping(value="/resource/{id}", method=RequestMethod.DELETE)
    public ModelAndView deleteRequest(@PathVariable("id") int resourceId) {       
        return new ModelAndView("rest", "message", "DELETE deleted resource " + resourceId);
    }

}
RestServerController.java
package lishy.rest.web;

import lishy.rest.RestUtils;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class RestServerController {
    
    private int id = 100;
    
    @RequestMapping(value="/resource/{id}", method=RequestMethod.GET)
    @ResponseBody public String getRequest(@PathVariable("id") int resourceId) {
        return "Return representation of resource " + resourceId + " in web response body";
    }

    @RequestMapping(value="/parent/{parentId}/child/{childId}", method=RequestMethod.GET)
    @ResponseBody public String getRequestWithParent(@PathVariable("parentId") int parentId,
                                                     @PathVariable("childId") int childId) {
        return "Return representation of resource " + parentId + " and " + childId;
    }

    @RequestMapping(value="/resource", method=RequestMethod.POST)
    public ResponseEntity<String> postRequest(@RequestBody String body) {
        id++;      
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("Location", "http://localhost:8080/rest/app/resource/" + id);
        return new ResponseEntity<String>("Created resource " + id + " with " + body, 
                                          responseHeaders, 
                                          HttpStatus.CREATED);
    }

    @RequestMapping(value="/resource/{id}", method=RequestMethod.PUT)
    public ResponseEntity<String> putRequest(@PathVariable("id") int resourceId,
                                             HttpEntity<String> requestEntity) {           
        System.out.println("Create or update resource " + resourceId + 
                           " with " + requestEntity.getBody()); 
        RestUtils.showHttpEntityContents(requestEntity);
        return new ResponseEntity<String>(" ", HttpStatus.OK);
    }
    
    @RequestMapping(value="/resource/{id}", method=RequestMethod.DELETE)
    public ResponseEntity<String> deleteRequest(@PathVariable("id") int resourceId,
                                                @RequestHeader("Host") String hostHeader) {        
        System.out.println("Delete resource " + resourceId);
        System.out.println("Host: " + hostHeader); 
       
        return new ResponseEntity<String>(" ", HttpStatus.OK);      
    }

}
RestClient.java
package lishy.rest;

import java.net.URI;

import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class RestClient {

    private static final String URL = "http://localhost:8080/rest/app/";

    public static void main(String[] args) {      
        getRequest();
        postRequest();
        putRequest();
        deleteRequest();
    }
    
    private static void getRequest() {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> entity = restTemplate.getForEntity(URL + "resource/{id}", 
                                                                  String.class, 
                                                                  "123");
        RestUtils.showHttpEntityContents(entity);  

        String response = restTemplate.getForObject(URL + "parent/{parentId}/child/{childId}",
                                                    String.class, 
                                                    "123", "456");
        System.out.println(response);
    }

    private static void postRequest() {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> entity = restTemplate.postForEntity(URL + "resource",
                                                                   "Representation of a new resource",
                                                                   String.class);
        RestUtils.showHttpEntityContents(entity);   
        
        String response = restTemplate.postForObject(URL + "resource",
                                                     "Representation of another new resource",
                                                     String.class);
        System.out.println(response);
        
        URI uri = restTemplate.postForLocation(URL + "resource",
                                               "Representation of yet another new resource");
        System.out.println(uri);
    }
    
    private static void putRequest() {
        RestTemplate restTemplate = new RestTemplate(); 
        restTemplate.put(URL + "resource/{id}", 
                         "Representation of a new or existing resource",
                         "123");        
    }
    
    private static void deleteRequest() {
        RestTemplate restTemplate = new RestTemplate();   
        restTemplate.delete(URL + "resource/{id}", "123");
    }
}
RestUtils.java
package lishy.rest;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;

public class RestUtils {

    public static void showHttpEntityContents(HttpEntity<String> http) {
        
        System.out.println("Headers..");
        HttpHeaders headers = http.getHeaders();
        for (String key: headers.keySet()) {
            System.out.println("  " + key + ": " + headers.getFirst(key));
        }
        
        System.out.println("Body..");   
        System.out.println("  " + http.getBody());
    }
    
}
rest.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
  <body>
  
    <h1>REST from the Browser</h1>
    
    <div style="color:green">${message}</div>
    
    <p>
      <a href="/rest/app/browser/resource/321">GET request</a><br/>
      <a href="/rest/app/browser/parent/123/child/456">GET request with parent</a><br/>
      <a href="/rest/app/browser/resource">GET request for multiple resources</a>
    </p>
    
    <form:form action="/rest/app/browser/resource" method="POST">
      <button type="submit">Post</button> 
      <input type="text" name="formfield" value="abc">
    </form:form>
   
    <form:form action="/rest/app/browser/resource/321" method="PUT" methodParam="httpMethod">
      <button type="submit">Put</button> 
      <input type="text" name="formfield" value="xyz">
    </form:form>
    
    <form:form action="/rest/app/browser/resource/321" method="DELETE" methodParam="httpMethod">
      <button type="submit">Delete</button> <i>with</i> Spring form tag 
    </form:form>

    <form action="/rest/app/browser/resource/456" method="POST">
      <button type="submit">Delete</button> <i>without</i> Spring form tag 
      <input type="hidden" name="httpMethod" value="DELETE">
    </form>
    
  </body>
</html>
rest-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       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-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="lishy.rest.web"/>

  <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp"/>

</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID"
         version="2.5">
         
  <display-name>Rest</display-name>
  
  <!-- DispatcherServlet -->
  <servlet>
    <servlet-name>rest</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

  <!-- Hidden HTTP Method Filter -->
  <filter>
    <filter-name>hiddenMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    <init-param>
      <param-name>methodParam</param-name>
      <param-value>httpMethod</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
      <filter-name>hiddenMethodFilter</filter-name>
      <url-pattern>/app/*</url-pattern>
  </filter-mapping>   
  
  <!-- Welcome File -->
  <welcome-file-list>
    <welcome-file>app/browser/resource/321</welcome-file>
  </welcome-file-list>

</web-app>

Spring REST

REST is implemented in Spring using standard controllers@Controller and request mappings@RequestMapping, with access to parts of the request URL/ using URI Templates{.
The @PathVariable annotation binds a method parameter to a URI Template variable which is specified in the path using curly braces{.

For example, this URL

http://host/root/app/browser/resource/321/resource/{id}:eq(0)

is sent to the getRequest()getRequest:eq(0) method and resourceIdresourceId:eq(0) is set to 321.
Multiple @PathVariable@PathVariable:gt(0):lt(2)s can be bound to multiple URI Template variables{parentId, as demonstrated by the getRequestWithParent() method.

Ant-style patterns can be used in path mappings if, for example, part of the URL is variable, but the receiving handler method does not care about the value.

For example, if the getRequestWithParent()getRequestWithParent method does not need the parent idparentId:eq(2) then we can use this path mapping instead

/parent/*/child/{childId}
The method element of the @RequestMapping annotation is used to narrow the primary mapping (eg /resource) to a particular type of HTTP request (GETRequestMethod.GET, POSTRequestMethod.POST, PUTRequestMethod.PUT, DELETERequestMethod.DELETE etc).

REST from the Browser

Requests use the HTTP methodmethod:not(:contains(methodParam)) GET, POST, PUT, DELETEDELETE:first etc) to convey the action to be carried out on the server. However, although HTTP supports all of the methods, HTML currently only supports GET and POST.

A popular technique to overcome this limitation is to use a POST with an additional hidden form field which contains the 'real' HTTP method.

The Spring form:form tag supports this technique and allows us to specify the name of the hidden field using the methodParam attribute. However, if the default name of _method is acceptable then the methodParam can be omitted.

Alternatively, we could implement this ourselves with a regular formform:not(:contains(:)):gt(2) element and a hidden fieldhidden.

On the server, the HiddenHttpMethodFilter servlet filter converts the posted request parameter (in our case httpMethod) into the equivalent HTTP request method.

Again, if the default value of _method is being used for the parameter name then there is no need for the <init-param>init-param element.

The request is sent to the appropriate method of a registered controller using a combination of the URL path/ and HTTP request methodmethod.

Headers and Body

The @RequestBody and @RequestHeader annotations bind a method parameter to the body or the header of a web request.
The @ResponseBody annotation indicates that a method return value should be written directly to the HTTP response body (and not placed in the model, or interpreted as a view name).
HttpEntityHttpEntity:eq(1) can be used as a method parameter and represents the headers and the body of an HTTP request.
The showHttpEntityContents()showHttpEntityContents method demonstrates how these values can be accessed.

Handler methods can return a ResponseEntityResponseEntity:gt(0) object. ResponseEntity extends HttpEntity and adds an HTTP status codeHttpStatus:gt(0).

Note: The ResponseEntity did not work for me when the first parameter of the constructor (the body) was omitted.
We will see why these features are useful in the next section which deals with RestTemplate.

REST Client

RestTemplateRestTemplate:gt(0) is the central class for client-side HTTP access in Spring.

Methods of this class are named after the six main HTTP methods; GETgetFor, POSTpostFor, PUTput:last, DELETEdelete:last, HEAD and OPTIONS.
getForEntity()getForEntity performs a GET request on the specified URL and returns a ResponseEntity object which contains the headers, the body and the status code of the response.

getForObject()getForObject does the same but returns a representation of the requested resource. In our example, we tell the getForObject() method that the representation will be returned as a StringString:eq(5).
RestTemplate supports three POST methods which return either a representation (postForObject), a ResponseEntity object (postForEntity), or the location of a resource (postForLocation).

The postForLocation()postForLocation method returns the HTTP Location header as a URI object. This will typically indicate where a new resource has been stored.
putput:last will create or overwrite a resource and deletedelete:last will delete a resource. These are both idempotent operations so they can safely be repeated if, for example, there is a problem such as a network failure.

We can see how RestTemplate uses the headers and body of an HTTP request or response to interact with a controller on the server.
Home  |  Getting Started  |  Controllers  |  Views  |  Forms  |  REST  |  Testing  |  Configuration  |  MVC Walkthrough  |   lishblog  |  Email Lishy