Content negotiation
Content negotiation
Content negotiation means allowing different representations of a resource in the
same URI so that clients can make a choice on what suits them best.
"HTTP has provisions for several mechanisms for "content negotiation" - the
process of selecting the best representation for a given response when there are
multiple representations available."
– RFC 2616, Fielding et al.
There are different patterns for content negotiation.
These are as follows:
• Using HTTP headers
• Using URL patterns
Content negotiation using HTTP headers
When the client sends requests to create or update a resource, there is some form
of payload that should be transferred from the client to the endpoint. Also, when
a response is generated, a payload can be sent back to the client. These payloads
are handled by HTTP request and response entities, which are sent as part of the
HTTP messages body.
Entities are sent via a request, usually for HTTP POST and PUT methods, or they
are returned in a response for the HTTP methods. The Content-Type HTTP header
is used to indicate the MIME type of the entity being sent by the server. Common
examples of content types are "text/plain", "application/xml", "text/html",
"application/json", "image/gif", and "image/jpeg".
A client can make a request to the server and specify what media types it can
handle and what is its order of preference as part of the "Accept" HTTP header.
The client can also specify in what language it wants the response as part of the
"Accept-Language" header to be. If no Accept header is present in the request,
the server can send the representation it chooses.
The JAX-RS specification provides standard annotations to support content
negotiation. These are javax.ws.rs.Produces and javax.ws.rs.Consumes
annotations. The following snippet shows an example of the @Produces
annotation in a resource method:
@GET
@Path("orders")
@Produces(MediaType.APPLICATION_JSON)
public List<Coffee> getCoffeeList(){
return CoffeeService.getCoffeeList();
}
The getCoffeeList() method returns a list of coffees and is annotated with
@Produces(MediaType.APPLICATION_JSON). The @Produces annotation is used
to specify which MIME types the resource can send back to the client and match
it up to the client's Accept header.
This method will produce a response as shown:
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source
Edition 4.0 Java/Oracle Corporation/1.7)
Server: GlassFish Server Open Source Edition 4.0
Content-Type: application/json
Date: Thu, 31 Jul 2014 15:25:17 GMT
Content-Length: 268
{
"coffees": [
{
"Id": 10,
"Name": "Cappuchino",
"Price": 3.82,
"Type": "Iced",
"Size": "Medium"
},
{
"Id": 11,
"Name": "Americano",
"Price": 3.42,
"Type": "Brewed",
"Size": "Large"
}
]
}
In a resource, if no methods are able to produce the MIME type requested by a client
request, the JAX-RS runtime sends back an HTTP 406 Not Acceptable error.
The following snippet shows a resource method annotated with the
@Consumes annotation:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addCoffee(Coffee coffee) {
// Implementation here
}
The @Consumes annotation specifies which media types the resource can consume.
When a client makes a request, JAX-RS finds all the methods that will match the path,
and it will then invoke the method based on the content type sent by the client.
If a resource is unable to consume the MIME type of a client request, the JAX-RS
runtime sends back an HTTP 415 ("Unsupported Media Type") error.
Multiple MIME types can be specified in the @Produces or @Consumes annotation
as @Produces(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML).
Along with the support for static content negotiation, JAX-RS also contains runtime
content negotiation support using the javax.ws.rs.core.Variant class and the
javax.ws.rs.core.Request objects. A Variant object in a JAX-RS specification
is a combination of media types, content-language, and content encoding as well
as ETags, last-modified headers, and other preconditions. The Variant object
defines the resource representation that is supported by the server. The Variant.
VariantListBuilder class is used to build a list of representation variants.
The following code snippet shows how to create a list of resource representation
variants:
List<Variant> variants = Variant.mediatypes("application/xml",
"application/json").build();
The code snippet calls the build method of the VariantListBuilder class.
The Request.selectVariant method takes a list of Variant objects and chooses
the one based on the client's Accept header, as shown in the following snippet:
@GET
public Response getCoffee(@Context Request r) {
List<Variant> vs = ...;
Variant v = r.selectVariant(vs);
if (v == null) {
return Response.notAcceptable(vs).build();
} else {
Coffee coffee = ..//select the representation based on v
return Response.ok(coffee, v);
}
}
Content negotiation based on URL patterns
Another approach for content negotiation adopted by some APIs is to send the
resource representation based on the extension of a resource in the URL. For
example, a client can ask for details using http://foo.api.com/v2/library/
books.xml or http://foo.api.com/v2/library/books.json. The server has
different methods, which can handle the two URIs. However, both of these are
representations of the same resource.
@Path("/v1/books/")
public class BookResource {
@Path("{resourceID}.xml")
@GET
public Response getBookInXML(@PathParam("resourceID") String
resourceID) {
//Return Response with entity in XML
}
@Path("{resourceID}.json")
@GET
public Response getBookInJSON(@PathParam("resourceID") String
resourceID) {
//Return Response with entity in JSON
}
}
As you can see in the preceding snippet, there are two methods defined:
getBookInXML() and getBookInJSON(), and the response is returned based
on the path of the URL.
It is a good practice to use the HTTP content negotiation Accept
header. Using headers for content negotiation provides a clear
separation of IT concerns from business. The other advantage with
using the Accept header for content negotiation is that there is
only one resource method for all the different representations.
读书笔记:RESTful Java Patterns and Best Practices