Content Negotiation
What is Content Negotiation?
Content negotiation is a way to determine what type of content to return to the client based on what the client can handle, and what the server can handle. This can be used to determine whether the client is wanting HTML or JSON returned, whether the image should be returned as a JPEG or PNG, what type of compression is supported and more. This is done by analyzing four different headers which can each support multiple value options, each with their own priority.
Trying to match this up manually can be pretty challenging. CodeIgniter provides the Negotiator
class that
can handle this for you.
At it’s heart Content Negotiation is simply a part of the HTTP specification that allows a single resource to serve more than one type of content, allowing the clients to request the type of data that works best for them.
A classic example of this is a browser that cannot display PNG files can request only GIF or JPEG images. When the server receives the request, it looks at the available file types the client is requesting and selects the best match from the image formats that it supports, in this case likely choosing a JPEG image to return.
This same negotiation can happen with four types of data:
Media/Document Type - this could be image format, or HTML vs. XML or JSON.
Character Set - The character set the returned document should be set in. Typically is UTF-8.
Document Encoding - Typically the type of compression used on the results.
Document Language - For sites that support multiple languages, this helps determine which to return.
Loading the Class
You can load an instance of the class manually through the Service class:
<?php
$negotiate = \Config\Services::negotiator();
This will grab the current request instance and automatically inject it into the Negotiator class.
This class does not need to be loaded on it’s own. Instead, it can be accessed through this request’s IncomingRequest
instance. While you cannot access it directly this way, you can easily access all of methods through the negotiate()
method:
<?php
$request->negotiate('media', ['foo', 'bar']);
When accessed this way, the first parameter is the type of content you’re trying to find a match for, while the second is an array of supported values.
Negotiating
In this section, we will discuss the 4 types of content that can be negotiated and show how that would look using both of the methods described above to access the negotiator.
Media
The first aspect to look at is handling ‘media’ negotiations. These are provided by the Accept
header and
is one of the most complex headers available. A common example is the client telling the server what format it
wants the data in. This is especially common in APIs. For example, a client might request JSON formatted data
from an API endpoint:
GET /foo HTTP/1.1
Accept: application/json
The server now needs to provide a list of what type of content it can provide. In this example, the API might be able to return data as raw HTML, JSON, or XML. This list should be provided in order of preference:
<?php
$supported = [
'application/json',
'text/html',
'application/xml',
];
$format = $request->negotiate('media', $supported);
// or
$format = $negotiate->media($supported);
In this case, both the client and the server can agree on formatting the data as JSON so ‘json’ is returned from
the negotiate method. By default, if no match is found, the first element in the $supported
array would be returned.
In some cases, though, you might need to enforce the format to be a strict match. If you pass true
as the
final value, it will return an empty string if no match is found:
<?php
$format = $request->negotiate('media', $supported, true);
// or
$format = $negotiate->media($supported, true);
Language
Another common usage is to determine the language the content should be served in. If you are running only a single
language site, this obviously isn’t going to make much difference, but any site that can offer up multiple translations
of content will find this useful, since the browser will typically send the preferred language along in the Accept-Language
header:
GET /foo HTTP/1.1
Accept-Language: fr; q=1.0, en; q=0.5
In this example, the browser would prefer French, with a second choice of English. If your website supports English and German you would do something like:
<?php
$supported = [
'en',
'de',
];
$lang = $request->negotiate('language', $supported);
// or
$lang = $negotiate->language($supported);
In this example, ‘en’ would be returned as the current language. If no match is found, it will return the first element
in the $supported
array, so that should always be the preferred language.
Encoding
The Accept-Encoding
header contains the character sets the client prefers to receive, and is used to
specify the type of compression the client supports:
GET /foo HTTP/1.1
Accept-Encoding: compress, gzip
Your web server will define what types of compression you can use. Some, like Apache, only support gzip:
<?php
$type = $request->negotiate('encoding', ['gzip']);
// or
$type = $negotiate->encoding(['gzip']);
See more at Wikipedia.
Character Set
The desired character set is passed through the Accept-Charset
header:
GET /foo HTTP/1.1
Accept-Charset: utf-16, utf-8
By default, if no matches are found, utf-8 will be returned:
<?php
$charset = $request->negotiate('charset', ['utf-8']);
// or
$charset = $negotiate->charset(['utf-8']);