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
indicate a nested property value. The instances of Inventor class, pupin
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
Pupin's city of birth the following expressions are used </para>

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter>
<title>Spring REST support </title>
<chapter id="rest">
<title>REST support</title>
<section>
<title>Introduction</title>
@ -75,8 +73,7 @@ public String getUser(@PathVariable String userId) {
}</programlisting>
<para>The request <literal>http://www.example.com/users/fred</literal>
will bind the userId method parameter to the String value 'fred'.
</para>
will bind the userId method parameter to the String value 'fred'.</para>
<section id="path-variable">
<title>Mapping RESTful URLs with the @PathVariable annotation</title>
@ -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
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) {
// 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
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) {
// implementation omitted
}</programlisting>
@ -163,9 +160,150 @@ public class RelativePathUriTemplateController {
</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>
@ -177,13 +315,86 @@ public class RelativePathUriTemplateController {
<section>
<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>
<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>