More REST documentation

This commit is contained in:
Mark Pollack 2009-04-11 04:49:11 +00:00
parent 5a87334168
commit 4fc77944ad
2 changed files with 230 additions and 19 deletions

View File

@ -425,7 +425,7 @@ Object nullValue = parser.parseExpression("null").getValue();
<para>Navigating with property references is easy, just use a period to <para>Navigating with property references is easy, just use a period to
indicate a nested property value. The instances of Inventor class, pupin indicate a nested property value. The instances of Inventor class, pupin
and tesla, were populated with data listed in section Section <link and tesla, were populated with data listed in section Section <link
linkend="expressions-examples-classes">Classes used in the linkend="expressions-example-classes">Classes used in the
examples</link>. To navigate "down" and get Tesla's year of birth and examples</link>. To navigate "down" and get Tesla's year of birth and
Pupin's city of birth the following expressions are used </para> Pupin's city of birth the following expressions are used </para>

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" <chapter id="rest">
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> <title>REST support</title>
<chapter>
<title>Spring REST support </title>
<section> <section>
<title>Introduction</title> <title>Introduction</title>
@ -23,7 +21,7 @@
url="http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/">entry</ulink>.) url="http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/">entry</ulink>.)
With little effort, you can marshall data out of a RESTful request using With little effort, you can marshall data out of a RESTful request using
@RequestMapping and @PathVariable annotations and return different views @RequestMapping and @PathVariable annotations and return different views
as determined by the request's Context-Type header. </para> as determined by the request's Context-Type header.</para>
<para>In this chapter we describe all the features of Spring's REST <para>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 support. It is divided into two main two chapters, one for the server-side
@ -31,7 +29,7 @@
linkend="mvc">MVC framework</link>, you may want to read through the linkend="mvc">MVC framework</link>, you may want to read through the
reference documentation on <link linkend="mvc-annotation">annotation-based reference documentation on <link linkend="mvc-annotation">annotation-based
controller configuration</link> to undestand the general programming controller configuration</link> to undestand the general programming
model. </para> model.</para>
</section> </section>
<section> <section>
@ -40,7 +38,7 @@
<para>Spring's annotation-based MVC framework serves as the basis for <para>Spring's annotation-based MVC framework serves as the basis for
creating RESTful Web Services. As such, you configure your servlet creating RESTful Web Services. As such, you configure your servlet
container as you would for a Spring MVC application using Spring's <link container as you would for a Spring MVC application using Spring's <link
linkend="mvc-servlet">DispatcherServlet</link>. </para> linkend="mvc-servlet">DispatcherServlet</link>.</para>
<section> <section>
<title>URI templates</title> <title>URI templates</title>
@ -60,7 +58,7 @@
<programlisting>http://www.example.com/users/fred</programlisting> <programlisting>http://www.example.com/users/fred</programlisting>
<para>When processing an request the URI can be compared to an expected <para>When processing an request the URI can be compared to an expected
URI Template in order to extract a collection of variables. </para> URI Template in order to extract a collection of variables.</para>
<para>Spring uses the <classname>@RequestMapping</classname> annotation <para>Spring uses the <classname>@RequestMapping</classname> annotation
to define the URI Template for the request. to define the URI Template for the request.
@ -75,15 +73,14 @@ public String getUser(@PathVariable String userId) {
}</programlisting> }</programlisting>
<para>The request <literal>http://www.example.com/users/fred</literal> <para>The request <literal>http://www.example.com/users/fred</literal>
will bind the userId method parameter to the String value 'fred'. will bind the userId method parameter to the String value 'fred'.</para>
</para>
<section id="path-variable"> <section id="path-variable">
<title>Mapping RESTful URLs with the @PathVariable annotation</title> <title>Mapping RESTful URLs with the @PathVariable annotation</title>
<para>The <classname>@PathVariable</classname> method level annotation <para>The <classname>@PathVariable</classname> method level annotation
is used to indicate that a method parameter should be bound to the is used to indicate that a method parameter should be bound to the
value of a URI template variable. </para> value of a URI template variable.</para>
<para>The following code snippet shows the use of a single <para>The following code snippet shows the use of a single
<classname>@PathVariable</classname> in a controller method:</para> <classname>@PathVariable</classname> in a controller method:</para>
@ -101,7 +98,7 @@ public String findOwner(<emphasis role="bold">@PathVariable</emphasis> String ow
request, the value of ownerId is set the the value in the request URI. 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 For example, when a request comes in for /owners/fred, the value
'fred' is bound to the method parameter <literal>String 'fred' is bound to the method parameter <literal>String
ownerId</literal>. </para> ownerId</literal>.</para>
<para>The matching of method parameter names to URI Template variable <para>The matching of method parameter names to URI Template variable
names can only be done if your code is compiled with debugging names can only be done if your code is compiled with debugging
@ -109,7 +106,7 @@ public String findOwner(<emphasis role="bold">@PathVariable</emphasis> String ow
name of the URI Template variable name to bind to in the @PathVariable name of the URI Template variable name to bind to in the @PathVariable
annotation. For example</para> annotation. For example</para>
<programlisting>@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) <programlisting language="java">@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(<emphasis role="bold">@PathVariable</emphasis>("ownerId") String ownerId, Model model) { public String findOwner(<emphasis role="bold">@PathVariable</emphasis>("ownerId") String ownerId, Model model) {
// implementation omitted // implementation omitted
} }
@ -119,7 +116,7 @@ public String findOwner(<emphasis role="bold">@PathVariable</emphasis>("ownerId"
so you may also use create a controlled method with the signature so you may also use create a controlled method with the signature
shown below</para> shown below</para>
<programlisting>@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET) <programlisting language="java">@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(<emphasis role="bold">@PathVariable</emphasis>("ownerId") String theOwner, Model model) { public String findOwner(<emphasis role="bold">@PathVariable</emphasis>("ownerId") String theOwner, Model model) {
// implementation omitted // implementation omitted
}</programlisting> }</programlisting>
@ -163,9 +160,150 @@ public class RelativePathUriTemplateController {
</section> </section>
<section> <section>
<title>Content Negotiation</title> <title>Returning multiple representations</title>
<para>blah blah</para> <para>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.</para>
<para>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<literal>
http://www.example.com/users/fred.pdf</literal> requests a PDF
representation of the user fred while
<literal>http://www.example.com/users/fred.xml</literal> requests an XML
representation.</para>
<para>The second strategy is for the client to use the same URI to
locate the resource but set the <literal>Accept</literal> HTTP request
header to list the <ulink
url="http://en.wikipedia.org/wiki/Internet_media_type">media
types</ulink> that it understands. For example, a HTTP request for
<literal>http://www.example.com/users/fred</literal> with an
<literal>Accept</literal> header set to <literal>application/pdf
</literal>requests a PDF representation of the user fred while
<literal>http://www.example.com/users/fred</literal> with an
<literal>Accept</literal> header set to <literal>text/xml</literal>
requests an XML representation. This strategy is known as <ulink
url="http://en.wikipedia.org/wiki/Content_negotiation">content
negotiation</ulink>.</para>
<note>
<para>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</para>
<programlisting>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
</programlisting>
<para>For this reason it is common to see the use of a distinct URI
for each representation.</para>
</note>
<para>To support multiple representations of a resource Spring provides
the <classname>ContentNegotiatingViewResolver</classname> to resolve a
view based on the file extension or <literal>Accept</literal> header of
the HTTP request. <classname>ContentNegotiatingViewResolver</classname>
does not perform the view resolution itself, but instead delegates to a
list of view resolvers set using the property
<literal>ViewResolvers</literal>.</para>
<para>The <classname>ContentNegotiatingViewResolver</classname> selects
an appropriate <classname>View</classname> to handle the request by
comparing the request media type(s) with the media type (a.k.a.
<literal>Content-Type</literal>) supported by the
<classname>View</classname> associated with each of its
<classname>ViewResolvers</classname>. The first
<classname>View</classname> in the list that has a compatible
<literal>Content-Type</literal> is used to return the representation to
the client. The <literal>Accept</literal> header may include wild cards,
for example 'text/*', in which case a <classname>View</classname> whose
Context-Type was 'text/xml' is a compatible match.</para>
<para>To support the resolution of a view based on a file extension,
<classname>ContentNegotiatingViewResolver</classname>'s property
<literal>MediaTypes</literal> 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
<classname>ContentNegotiatingViewResolver</classname>..</para>
<para>Here is an example configuration of a
<classname>ContentNegotiatingViewResolver</classname></para>
<programlisting language="xml"> &lt;bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"&gt;
&lt;property name="mediaTypes"&gt;
&lt;map&gt;
&lt;entry key="atom" value="application/atom+xml"/&gt;
&lt;entry key="html" value="text/html"/&gt;
&lt;/map&gt;
&lt;/property&gt;
&lt;property name="viewResolvers"&gt;
&lt;list&gt;
&lt;bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/&gt;
&lt;bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"&gt;
&lt;property name="prefix" value="/WEB-INF/jsp/"/&gt;
&lt;property name="suffix" value=".jsp"/&gt;
&lt;/bean&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/&gt;</programlisting>
<para>The <classname>InternalResourceViewResolver</classname> handles
the translation of view names and JSP pages while the
<classname>BeanNameViewResolver</classname> returns a view based on the
name of a bean. (See "<link
linkend="mvc-viewresolver-resolver">Resolving views - the ViewResolver
interface</link>" for more details on how Spring looks up and
instantiates a view.) In this example, the <literal>content</literal>
bean is a class that inherits from
<classname>AbstractAtomFeedView</classname> which returns an Atom RSS
feed. For more information on creating an Atom Feed representation see
the section 'Atom Views'. </para>
<para>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 <classname>InternalResourceViewResolver</classname> 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
<classname>BeanNameViewResolver</classname> that maps to the
<classname>SampleContentAtomView</classname> 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.</para>
<note>
<para>If <classname>ContentNegotiatingViewResolver</classname>'s list
of ViewResolvers is not configured explicitly, then it will
automatically use any ViewResolvers defined in the application
context.</para>
</note>
<para>The corresponding controller code that returns an Atom RSS feed
for a URI of the form <literal>http://localhost/content.atom</literal>
or <literal>http://localhost/content</literal> with an
<literal>Accept</literal> header of application/atom+xml is shown
below</para>
<programlisting language="java">@Controller
public class ContentController {
private List&lt;SampleContent&gt; contentList = new ArrayList&lt;SampleContent&gt;();
@RequestMapping(value="/content", method=RequestMethod.GET)
public ModelAndView getContent() {
ModelAndView mav = new ModelAndView();
mav.setViewName("content");
mav.addObject("sampleContentList", contentList);
return mav;
}
}</programlisting>
<para></para>
</section> </section>
<section> <section>
@ -177,13 +315,86 @@ public class RelativePathUriTemplateController {
<section> <section>
<title>HTTP Method Conversion</title> <title>HTTP Method Conversion</title>
<para>blah blah</para> <para>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
<classname>HiddenHttpMethodFilter</classname> 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.</para>
<section>
<title>Supporting Spring form tags</title>
<para>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</para>
<programlisting language="html">&lt;form:form method="delete"&gt;
&lt;p class="submit"&gt;&lt;input type="submit" value="Delete Pet"/&gt;&lt;/p&gt;
&lt;/form:form&gt;</programlisting>
<para>This will actually perform an HTTP POST, with the 'real' DELETE
method hidden behind a request parameter, to be picked up by the
<classname>HiddenHttpMethodFilter</classname>. The corresponding
@Controller method is shown below</para>
<programlisting language="java">@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}</programlisting>
</section>
</section> </section>
<section> <section>
<title>ETag support</title> <title>ETag support</title>
<para>blah blah</para> <para>An <ulink
url="http://en.wikipedia.org/wiki/HTTP_ETag">ETag</ulink> (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
<literal>Last-Modified</literal> header. When a server returns a
representation with an ETag header, client can use this header in
subsequent GETs, in a <literal>If-None-Match</literal> header. If the
content has not changed, the server will return <literal>304: Not
Modified</literal>.</para>
<para>Support for ETags is provided by the servlet filter
<classname>ShallowEtagHeaderFilter</classname>. Since it is a plain
Servlet Filter, and thus can be used in combination any web framework.
The <classname>ShallowEtagHeaderFilter</classname> 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 <literal>If-None-Match</literal> value. The filter notices this,
renders the view again, and compares the two hashes. If they are equal,
a <literal>304</literal> 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.</para>
<para>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.</para>
</section> </section>
<section> <section>