Enterprise Java

HTTP – Content negotiation

With HTTP, resources are identified using URIs. And a uniquely identified resource might support multiple resource representations. A representation is a specific form of a particular resource.

For example:

  • a HTML page /index.html might be available in different languages
  • product data located at /products/123 can be served in JSON, XML or CSV
  • an avatar image /user/avatar might available in JPEG, PNG and GIF formats

In all these cases one underlying resource has multiple different representations.

Content negotiation is the mechanism used by clients and servers to decide which representation should be used.

Server-driven and agent-driven content negotiation

We can differentiate between server-driven and agent-driven content negotiation.

With server-driven content negotiation the client tells the server which representations are preferable. The server then picks the representation that best fits the clients needs.

When using agent-driven content negotiation the server tells the client which representations are available. The client then picks the best matching option.

In practice nearly only server-driven negotiation is used. Unfortunately, there is no standardized format for doing agent-driven negotiation. Additionally, agent-driven negotiation is usually also worse for performance as it requires an additional request / response round trip. In the rest of this article we will therefore focus on server-driven negotiation.

Accept headers

With server-driven negotiation the client uses headers to indicate supported content formats. A server-side algorithm then uses these headers to decide which resource representation should be returned.

Most commonly used is the Accept-Header, which communicates the media-type preferred by the client. For example, consider the following simple HTTP request containing an Accept header:

GET /monthly-report
Accept: text/html; q=1.0, text/*; q=0.8

The header tells the server that the client understands HTML (media-type text/html) and other text based formats (mediatype text/*).

text/* indicates that all subtypes of the text type are supported. To indicate that all media types are supported we can use */*.

In this example HTML is preferred over other text based formats because it has a higher quality factor (q).

Ideally a server would respond with a HTML document to this request. For example:

HTTP/1.1 200 OK
Content-Type: text/html

<html>
    <body>
        <h1>Monthly report</h1>
        ...
    </body>
</html>

If returning HTML is not feasible, the server can also respond with another text based format, like text/plain:

200 OK
Content-Type: text/plain

Monthly report
Bla bli blu
...

Besides the Accept header there are also the Accept-Language and Accept-Encoding headers, we can use. Accept-Language indicates the language preference of the client while Accept-Encoding defines the acceptable content encodings.

Of course all these headers can be used together. For example:

GET /monthly-report
Accept: text/html
Accept-Language: en-US; q=1.0, en; q=0.9, fr; q=0.4
Accept-Encoding: gzip, br

Here the client indicates that he prefers

  • an HTML document
  • US English (preferred, q=1.0) but other English variations are also fine (q=0.9). If English is not available, French can do the job too (q=0.4)
  • gzip and brotli (br) encoding is supported

An acceptable response might look like this:

200 Ok
Content-Type: text/html
Content-Language: en
Content-Encoding: gzip

<gzipped html document>

What if the server cannot return an acceptable response?

If the server is unable to fulfill the clients preferences the HTTP status code 406 (Not Acceptable) can be returned. This status code indicates that the server is unable to produce a response matching the clients preference.

Depending on the situation it might also be viable to return a response that does not exactly match the clients preference. For example, assume no language provided in the Accept-Language header is supported by the server. In this case, it can still be a valid option to return a response using a predefined default language. This might be more useful for the client than nothing. In this case, the client can look at the Content-Language header of the response and decide if he wants to use the response or ignore it.

Content negotiation in REST APIs

For REST APIs it can be a viable option to support more than one standard representation for resources. For example, with content negotiation we can support JSON and XML and let the client decide what he wants to use.

CSV can also be an interesting option to consider in certain situations as the response can directly be viewed with tools like Excel. For example, consider the following request:

GET /users
Accept: text/csv

Instead of returning a JSON (or XML) collection, the server now can respond with a list of users in CSV format.

HTTP/1.1 200 Ok
Content-Type: text/csv

Id;Username;Email
1;john;john.doe@example.com
2;anna91;anna91@example.com

Published on Java Code Geeks with permission by Michael Scharhag, partner at our JCG program. See the original article here: HTTP – Content negotiation

Opinions expressed by Java Code Geeks contributors are their own.

Michael Scharhag

Michael Scharhag is a Java Developer, Blogger and technology enthusiast. Particularly interested in Java related technologies including Java EE, Spring, Groovy and Grails.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button