More REST documentation
This commit is contained in:
parent
5a87334168
commit
4fc77944ad
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"> <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"/></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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}</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"><form:form method="delete">
|
||||||
|
<p class="submit"><input type="submit" value="Delete Pet"/></p>
|
||||||
|
</form:form></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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue