From 4fc77944ad34f37dddc45672a373e88886609ebe Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Sat, 11 Apr 2009 04:49:11 +0000 Subject: [PATCH] More REST documentation --- .../src/expressions.xml | 2 +- spring-framework-reference/src/rest.xml | 247 ++++++++++++++++-- 2 files changed, 230 insertions(+), 19 deletions(-) diff --git a/spring-framework-reference/src/expressions.xml b/spring-framework-reference/src/expressions.xml index 2c17838c64d..b94c2d11f39 100644 --- a/spring-framework-reference/src/expressions.xml +++ b/spring-framework-reference/src/expressions.xml @@ -425,7 +425,7 @@ Object nullValue = parser.parseExpression("null").getValue(); Navigating with property references is easy, just use a period to indicate a nested property value. The instances of Inventor class, pupin and tesla, were populated with data listed in section Section Classes used in the + linkend="expressions-example-classes">Classes used in the examples. To navigate "down" and get Tesla's year of birth and Pupin's city of birth the following expressions are used diff --git a/spring-framework-reference/src/rest.xml b/spring-framework-reference/src/rest.xml index f45b9a8984f..6b0debad794 100644 --- a/spring-framework-reference/src/rest.xml +++ b/spring-framework-reference/src/rest.xml @@ -1,8 +1,6 @@ - - - Spring REST support + + REST support
Introduction @@ -23,7 +21,7 @@ url="http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/">entry.) With little effort, you can marshall data out of a RESTful request using @RequestMapping and @PathVariable annotations and return different views - as determined by the request's Context-Type header. + as determined by the request's Context-Type header. In this chapter we describe all the features of Spring's REST support. It is divided into two main two chapters, one for the server-side @@ -31,7 +29,7 @@ linkend="mvc">MVC framework, you may want to read through the reference documentation on annotation-based controller configuration to undestand the general programming - model. + model.
@@ -40,7 +38,7 @@ Spring's annotation-based MVC framework serves as the basis for creating RESTful Web Services. As such, you configure your servlet container as you would for a Spring MVC application using Spring's DispatcherServlet. + linkend="mvc-servlet">DispatcherServlet.
URI templates @@ -60,7 +58,7 @@ http://www.example.com/users/fred When processing an request the URI can be compared to an expected - URI Template in order to extract a collection of variables. + URI Template in order to extract a collection of variables. Spring uses the @RequestMapping annotation to define the URI Template for the request. @@ -75,15 +73,14 @@ public String getUser(@PathVariable String userId) { } The request http://www.example.com/users/fred - will bind the userId method parameter to the String value 'fred'. - + will bind the userId method parameter to the String value 'fred'.
Mapping RESTful URLs with the @PathVariable annotation The @PathVariable method level annotation is used to indicate that a method parameter should be bound to the - value of a URI template variable. + value of a URI template variable. The following code snippet shows the use of a single @PathVariable in a controller method: @@ -101,7 +98,7 @@ public String findOwner(@PathVariable String ow request, the value of ownerId is set the the value in the request URI. For example, when a request comes in for /owners/fred, the value 'fred' is bound to the method parameter String - ownerId. + ownerId. The matching of method parameter names to URI Template variable names can only be done if your code is compiled with debugging @@ -109,7 +106,7 @@ public String findOwner(@PathVariable String ow name of the URI Template variable name to bind to in the @PathVariable annotation. For example - @RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) + @RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable("ownerId") String ownerId, Model model) { // implementation omitted } @@ -119,7 +116,7 @@ public String findOwner(@PathVariable("ownerId" so you may also use create a controlled method with the signature shown below - @RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) + @RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted } @@ -163,9 +160,150 @@ public class RelativePathUriTemplateController {
- Content Negotiation + Returning multiple representations - blah blah + A RESTful architecture may expose multiple representations of a + resource. There are two strategies for a client to inform the server of + the representation it is interested in receiving. + + The first strategy is to use a distinct URI for each resource. + This is typically done by using a different file extension in the URI. + For example the URI + http://www.example.com/users/fred.pdf requests a PDF + representation of the user fred while + http://www.example.com/users/fred.xml requests an XML + representation. + + The second strategy is for the client to use the same URI to + locate the resource but set the Accept HTTP request + header to list the media + types that it understands. For example, a HTTP request for + http://www.example.com/users/fred with an + Accept header set to application/pdf + requests a PDF representation of the user fred while + http://www.example.com/users/fred with an + Accept header set to text/xml + requests an XML representation. This strategy is known as content + negotiation. + + + One issue with the Accept header is that is impossible to change + it in a web browser, in HTML. For instance, in Firefox, it's fixed + to + + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + For this reason it is common to see the use of a distinct URI + for each representation. + + + To support multiple representations of a resource Spring provides + the ContentNegotiatingViewResolver to resolve a + view based on the file extension or Accept header of + the HTTP request. ContentNegotiatingViewResolver + does not perform the view resolution itself, but instead delegates to a + list of view resolvers set using the property + ViewResolvers. + + The ContentNegotiatingViewResolver selects + an appropriate View to handle the request by + comparing the request media type(s) with the media type (a.k.a. + Content-Type) supported by the + View associated with each of its + ViewResolvers. The first + View in the list that has a compatible + Content-Type is used to return the representation to + the client. The Accept header may include wild cards, + for example 'text/*', in which case a View whose + Context-Type was 'text/xml' is a compatible match. + + To support the resolution of a view based on a file extension, + ContentNegotiatingViewResolver's property + MediaTypes is used to specify a mapping of file + extensions to media types. For more information on the algorithm to + determine the request media type, refer to the API documentation for + ContentNegotiatingViewResolver.. + + Here is an example configuration of a + ContentNegotiatingViewResolver + + <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> + <property name="mediaTypes"> + <map> + <entry key="atom" value="application/atom+xml"/> + <entry key="html" value="text/html"/> + </map> + </property> + <property name="viewResolvers"> + <list> + <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> + <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> + <property name="prefix" value="/WEB-INF/jsp/"/> + <property name="suffix" value=".jsp"/> + </bean> + </list> + </property> + </bean> + + + <bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/> + + The InternalResourceViewResolver handles + the translation of view names and JSP pages while the + BeanNameViewResolver returns a view based on the + name of a bean. (See "Resolving views - the ViewResolver + interface" for more details on how Spring looks up and + instantiates a view.) In this example, the content + bean is a class that inherits from + AbstractAtomFeedView which returns an Atom RSS + feed. For more information on creating an Atom Feed representation see + the section 'Atom Views'. + + In this configuration, if a request is made with a .html extension + the view resolver will look for a view that matches the text/html media + type. The InternalResourceViewResolver provides + the matching view for text/html. If the request is made with the file + extension .atom, the view resolver will look for a view that matches the + application/atom+xml media type. This view is provided by the + BeanNameViewResolver that maps to the + SampleContentAtomView if the view name returned + is 'content'. Alternatively, client requests could be made without a + file extension and setting the Accept header to the preferred media-type + and the same resolution of request to views would be occur. + + + If ContentNegotiatingViewResolver's list + of ViewResolvers is not configured explicitly, then it will + automatically use any ViewResolvers defined in the application + context. + + + The corresponding controller code that returns an Atom RSS feed + for a URI of the form http://localhost/content.atom + or http://localhost/content with an + Accept header of application/atom+xml is shown + below + + @Controller +public class ContentController { + + private List<SampleContent> contentList = new ArrayList<SampleContent>(); + + @RequestMapping(value="/content", method=RequestMethod.GET) + public ModelAndView getContent() { + ModelAndView mav = new ModelAndView(); + mav.setViewName("content"); + mav.addObject("sampleContentList", contentList); + return mav; + } + +} + +
@@ -177,13 +315,86 @@ public class RelativePathUriTemplateController {
HTTP Method Conversion - blah blah + Another key principle of REST is the use of the Uniform Interface. + This means that all resources (URLs) can be manipulated using the same + four HTTP method: GET, PUT, POST, and DELETE. For each of methods, the + HTTP specification defines exact semantics. For instance, a GET should + always be a safe operation, meaning that is has no side effects, and a + PUT or DELETE should be idempotent, meaning that you can repeat these + operations over and over again, but the end result should be the same. + While HTTP defines these four methods, HTML only supports two: GET and + POST. Fortunately, there are two possible workarounds: you can either + use JavaScript to do your PUT or DELETE, or simply do a POST with the + 'real' method as an additional parameter (modeled as a hidden input + field in an HTML form). This latter trick is what the + HiddenHttpMethodFilter does. This filter was + introduced in Spring 3.0 M1, and is a plain Servlet Filter. As such, it + can be used in combination with any web framework (not just Spring MVC). + Simply add this filter to your web.xml, and a POST with a hidden _method + parameter will be converted into the corresponding HTTP method + request. + +
+ Supporting Spring form tags + + To support HTTP method conversion the Spring MVC form tag was + updated to support setting the HTTP method. For example, the following + snippet taken from the updated Petclinic sample + + <form:form method="delete"> + <p class="submit"><input type="submit" value="Delete Pet"/></p> +</form:form> + + This will actually perform an HTTP POST, with the 'real' DELETE + method hidden behind a request parameter, to be picked up by the + HiddenHttpMethodFilter. The corresponding + @Controller method is shown below + + @RequestMapping(method = RequestMethod.DELETE) +public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { + this.clinic.deletePet(petId); + return "redirect:/owners/" + ownerId; +} +
ETag support - blah blah + An ETag (entity tag) + is an HTTP response header returned by an HTTP/1.1 compliant web server + used to determine change in content at a given URL. It can be considered + to be the more sophisticated successor to the + Last-Modified header. When a server returns a + representation with an ETag header, client can use this header in + subsequent GETs, in a If-None-Match header. If the + content has not changed, the server will return 304: Not + Modified. + + Support for ETags is provided by the servlet filter + ShallowEtagHeaderFilter. Since it is a plain + Servlet Filter, and thus can be used in combination any web framework. + The ShallowEtagHeaderFilter filter creates + so-called shallow ETags (as opposed to a deep ETags, more about that + later). The way it works is quite simple: the filter simply caches the + content of the rendered JSP (or other content), generates a MD5 hash + over that, and returns that as a ETag header in the response. The next + time a client sends a request for the same resource, it use that hash as + the If-None-Match value. The filter notices this, + renders the view again, and compares the two hashes. If they are equal, + a 304 is returned. It is important to note that this + filter will not save processing power, as the view is still rendered. + The only thing it saves is bandwith, as the rendered response is not + sent back over the wire. + + Deep ETags are a bit more complicated. In this case, the ETag is + based on the underlying domain objects, RDMBS tables, etc. Using this + approach, no content is generated unless the underlying data has + changed. Unfortunately, implementing this approach in a generic way is + much more difficult than shallow ETags. Spring may provide support for + deep ETags in a later release by relying on JPA's @Version annotation, + or an AspectJ aspect.