blob: 1b5593bed4d92a479459278f14b3230e4b5f8bbf [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Apache Wink : JAX-RS Content Negotiation</title>
<link rel="stylesheet" href="styles/site.css" type="text/css" />
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
<tr>
<td valign="top" class="pagebody">
<div class="pageheader">
<span class="pagetitle">
Apache Wink : JAX-RS Content Negotiation
</span>
</div>
<div class="pagesubheading">
This page last changed on Sep 22, 2009 by <font color="#0050B2">michael</font>.
</div>
<h1><a name="JAX-RSContentNegotiation-WhatisContentNegotiation%3F"></a>What is Content Negotiation?</h1>
<p>One of the more popular features of REST applications is the ability to return different representations of resources. For instance, data can be in <a href="http://json.org">JSON</a> format or in <a href="http://www.w3.org/XML/">XML</a>. Or it can even be available in either format depending on the request. Content negotiation is the way for clients and servers to agree on what content format is sent.</p>
<p>Data is returned in multiple formats because the needs of each client's request can be different. A browser might prefer JSON format. Another command line based client might prefer XML format. A third client might request the same resource as a PNG image.</p>
<p>It is up to the service to determine which formats are supported.</p>
<p>There are many practical ways of performing content negotiation.</p>
<p><a name="JAX-RSContentNegotiation-URL"></a></p>
<h3><a name="JAX-RSContentNegotiation-ContentNegotiationBasedonURL"></a>Content Negotiation Based on URL</h3>
<p>Many popular public REST APIs perform content negotiation based on the URL. For instance, a resource in XML format might be at <a href="http://example.com/resource.xml">http://example.com/resource.xml</a>. The same resource could be offered in JSON format but located at <a href="http://example.com/resource.json">http://example.com/resource.json</a>.</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>ContentNegotiationViaURL.java</b></div><div class="codeContent panelContent">
<pre class="code-java">@Path(<span class="code-quote">"/resources"</span>)
<span class="code-keyword">public</span> class Resource {
@Path(<span class="code-quote">"{resourceID}.xml"</span>)
@GET
<span class="code-keyword">public</span> Response getResourceInXML(@PathParam(<span class="code-quote">"resourceID"</span>) <span class="code-object">String</span> resourceID) {
<span class="code-keyword">return</span> Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
}
@Path(<span class="code-quote">"{resourceID}.json"</span>)
@GET
<span class="code-keyword">public</span> Response getResourceInJSON(@PathParam(<span class="code-quote">"resourceID"</span>) <span class="code-object">String</span> resourceID) {
<span class="code-keyword">return</span> Response.ok(/* entity in JSON format */).type(MediaType.APPLICATION_JSON).build();
}
}
</pre>
</div></div>
<p>In the above code, a request to <b>"/resources/resourceID.myformat"</b> would result in a 404 status code.</p>
<p>Another way of implementing the above is to use a single resource method like below:</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>ContentNegotiationViaSingleMethod.java</b></div><div class="codeContent panelContent">
<pre class="code-java">@Path(<span class="code-quote">"/resources"</span>)
<span class="code-keyword">public</span> class Resource {
@Path(<span class="code-quote">"{resourceID}.{type}"</span>)
@GET
<span class="code-keyword">public</span> Response getResource(@PathParam(<span class="code-quote">"resourceID"</span>) <span class="code-object">String</span> resourceID, @PathParam(<span class="code-quote">"type"</span>) <span class="code-object">String</span> type) {
<span class="code-keyword">if</span> (<span class="code-quote">"xml"</span>.equals(type)) {
<span class="code-keyword">return</span> Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
} <span class="code-keyword">else</span> <span class="code-keyword">if</span> (<span class="code-quote">"json"</span>.equals(type)) {
<span class="code-keyword">return</span> Response.ok("/* entity in JSON format */).type(MediaType.APPLICATION_JSON).build();
}
<span class="code-keyword">return</span> Response.status(404).build();
}
}
</pre>
</div></div>
<p>The previous code excerpt essentially behaves the same as the ContentNegotiationViaURL.java example.</p>
<p><a name="JAX-RSContentNegotiation-Parameter"></a></p>
<h3><a name="JAX-RSContentNegotiation-ContentNegotiationBasedonRequestParameter"></a>Content Negotiation Based on Request Parameter</h3>
<p>Another popular method is for the client to specify the format in a parameter. For instance, by default, a resource could be offered in XML at <a href="http://example.com/resource">http://example.com/resource</a>. The JSON version could be retrieved by using a query parameter like <a href="http://example.com/resource?format=json">http://example.com/resource?format=json</a>.</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>ContentNegotiationViaParameter.java</b></div><div class="codeContent panelContent">
<pre class="code-java">@Path(<span class="code-quote">"/resources"</span>)
<span class="code-keyword">public</span> class Resource {
@Path(<span class="code-quote">"{resourceID}"</span>)
@GET
<span class="code-keyword">public</span> Response getResource(@PathParam(<span class="code-quote">"resourceID"</span>) <span class="code-object">String</span> resourceID,
@QueryParam(<span class="code-quote">"format"</span>) <span class="code-object">String</span> format) {
<span class="code-keyword">if</span> (format == <span class="code-keyword">null</span> || <span class="code-quote">"xml"</span>.equals(format)) {
<span class="code-keyword">return</span> Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
} <span class="code-keyword">else</span> <span class="code-keyword">if</span> (<span class="code-quote">"json"</span>.equals(format)) {
<span class="code-keyword">return</span> Response.ok(/* entity in JSON format */).type(MediaType.APPLICATION_JSON).build();
}
<span class="code-keyword">return</span> Response.notAcceptable(Variant.mediaTypes(MediaType.APPLICATION_XML_TYPE,
MediaType.APPLICATION_JSON_TYPE).add()
.build()).build();
}
}
</pre>
</div></div>
<p><a name="JAX-RSContentNegotiation-AcceptHeader"></a></p>
<h3><a name="JAX-RSContentNegotiation-ContentNegotiationBasedonAcceptHeader"></a>Content Negotiation Based on Accept Header</h3>
<p>Perhaps the most powerful form of content negotiation, the HTTP <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">Accept header</a> is another way of specifying the preferred representation format.</p>
<p>The following excerpt shows sample client code using the Wink RestClient:</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>RestClientWithAcceptHeader.java</b></div><div class="codeContent panelContent">
<pre class="code-java"> RestClient client = <span class="code-keyword">new</span> RestClient();
ClientResponse response = client.resource(<span class="code-quote">"http:<span class="code-comment">//example.com/resources/resource1"</span>).header(<span class="code-quote">"Accept"</span>, <span class="code-quote">"application/json;q=1.0, application/xml;q=0.8"</span>).get();
</span> <span class="code-comment">// find what format was sent back
</span> <span class="code-object">String</span> contentType = response.getHeaders().getFirst(<span class="code-quote">"Content-Type"</span>);
<span class="code-keyword">if</span> (contentType.contains(<span class="code-quote">"application/json"</span>)) {
JSONObject entity = response.getEntity(JSONObject.class); /* or use a <span class="code-object">String</span>, InputStream, or other provider that supports the entity media type */
} <span class="code-keyword">else</span> <span class="code-keyword">if</span> (contentType.contains(<span class="code-quote">"application/xml"</span>) {
<span class="code-object">String</span> entity = response.getEntity(<span class="code-object">String</span>.class); /* or use a JAXB class, InputStream, etc. */
}
</pre>
</div></div>
<p>The following example implements sample client code using the <a href="http://hc.apache.org/httpclient-3.x/">Apache HttpClient</a>.</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>HttpClientWithAcceptHeader.java</b></div><div class="codeContent panelContent">
<pre class="code-java"> HttpClient client = <span class="code-keyword">new</span> HttpClient();
GetMethod getMethod =
<span class="code-keyword">new</span> GetMethod(<span class="code-quote">"http:<span class="code-comment">//example.com/resources/resource1"</span>);
</span> <span class="code-comment">// prefer JSON over XML but both are acceptable to the client
</span> getMethod.setRequestHeader(<span class="code-quote">"Accept"</span>, <span class="code-quote">"application/json;q=1.0, application/xml;q=0.8"</span>);
<span class="code-keyword">try</span> {
client.executeMethod(getMethod);
<span class="code-comment">// find what format was sent back
</span> getMethod.getResponseHeader(<span class="code-quote">"Content-Type"</span>).getValue();
<span class="code-comment">// use getMethod.getResponseBodyAsString() or getMethod.getResponseBodyAsStream()
</span> <span class="code-comment">// to read the body
</span> } <span class="code-keyword">finally</span> {
getMethod.releaseConnection();
}
</pre>
</div></div>
<p>Using the @Context HttpHeaders injected variable, the client preferred response representation is found.</p>
<div class="code panel" style="border-style: solid;border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><b>ContentNegotiationViaAcceptHeaderAndHTTPHeaders.java</b></div><div class="codeContent panelContent">
<pre class="code-java">@Path(<span class="code-quote">"/resources"</span>)
<span class="code-keyword">public</span> class Resource {
@Context
<span class="code-keyword">private</span> HttpHeaders headers;
@Path(<span class="code-quote">"{resourceID}"</span>)
@GET
<span class="code-keyword">public</span> Response getResource(@PathParam(<span class="code-quote">"resourceID"</span>) <span class="code-object">String</span> resourceID) {
List&lt;MediaType&gt; acceptHeaders = headers.getAcceptableMediaTypes();
<span class="code-keyword">if</span> (acceptHeaders == <span class="code-keyword">null</span> || acceptHeaders.size() == 0) {
<span class="code-keyword">return</span> Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
}
<span class="code-keyword">for</span> (MediaType mt : acceptHeaders) {
<span class="code-object">String</span> qValue = mt.getParameters().get(<span class="code-quote">"q"</span>);
<span class="code-keyword">if</span>(qValue != <span class="code-keyword">null</span> &amp;&amp; <span class="code-object">Double</span>.valueOf(qValue).doubleValue() == 0.0) {
<span class="code-keyword">break</span>;
}
<span class="code-keyword">if</span>(MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
<span class="code-keyword">return</span> Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
} <span class="code-keyword">else</span> <span class="code-keyword">if</span> (MediaType.APPLICATION_JSON_TYPE.isCompatible(mt)) {
<span class="code-keyword">return</span> Response.ok(/* entity in JSON format */).type(MediaType.APPLICATION_JSON).build();
}
}
<span class="code-keyword">return</span> Response.notAcceptable(Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE,
MediaType.APPLICATION_XML_TYPE).add()
.build()).build();
}
}
</pre>
</div></div>
<p>If the client request does not have an Accept HTTP header, then by default the XML format is returned. The @Context HttpHeaders.getAcceptableMediaTypes() method returns an ordered list, sorted by the client preference of the representations.</p>
<p>Looping through the acceptable media types, if the preferred media type is compatible with one of the service's available return types, then the preferred media type is used.</p>
<div class='panelMacro'><table class='infoMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="images/icons/emoticons/information.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Note</b><br />Note that the quality factor is checked. In fairly rare requests, clients can let the service know that a media type should not be sent back (if the quality factor is 0.0).</td></tr></table></div>
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="12" background="http://cwiki.apache.org/confluence/images/border/border_bottom.gif"><img src="images/border/spacer.gif" width="1" height="1" border="0"/></td>
</tr>
<tr>
<td align="center"><font color="grey">Document generated by Confluence on Nov 11, 2009 06:57</font></td>
</tr>
</table>
</body>
</html>