Grails has built in support for
Content negotiation using either the HTTP
Accept
header, an explicit format request parameter or the extension of a mapped URI.
Configuring Mime Types
Before you can start dealing with content negotiation you need to tell Grails what content types you wish to support. By default Grails comes configured with a number of different content types within
grails-app/conf/Config.groovy
using the
grails.mime.types
setting:
grails.mime.types = [ xml: ['text/xml', 'application/xml'],
text: 'text-plain',
js: 'text/javascript',
rss: 'application/rss+xml',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
all: '*/*',
json: 'text/json',
html: ['text/html','application/xhtml+xml']
]
The above bit of configuration allows Grails to detect to format of a request containing either the 'text/xml' or 'application/xml' media types as simply 'xml'. You can add your own types by simply adding new entries into the map.
Content Negotiation using the Accept header
Every incoming HTTP request has a special
Accept header that defines what media types (or mime types) a client can "accept". In older browsers this is typically:
Which simply means anything. However, on newer browser something all together more useful is sent such as (an example of a Firefox
Accept
header):
text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Grails parses this incoming format and adds a
property
to the
request object that outlines the preferred request format. For the above example the following assertion would pass:
assert 'html' == request.format
Why? The
text/html
media type has the highest "quality" rating of 0.9, therefore is the highest priority. If you have an older browser as mentioned previously the result is slightly different:
assert 'all' == request.format
In this case 'all' possible formats are accepted by the client. To deal with different kinds of requests from
Controllers you can use the
withFormat method that acts as kind of a switch statement:
import grails.converters.*class BookController {
def books
def list = {
this.books = Book.list()
withFormat {
html bookList:books
js { render "alert('hello')" }
xml { render books as XML }
}
}
}
What happens here is that if the preferred format is
html
then Grails will execute the
html()
call only. What this is does is make Grails look for a view called either
grails-app/views/books/list.html.gsp
or
grails-app/views/books/list.gsp
. If the format is
xml
then the closure will be invoked and an XML response rendered.
How do we handle the "all" format? Simply order the content-types within your
withFormat
block so that whichever one you want executed comes first. So in the above example, "all" will trigger the
html
handler.
When using withFormat make sure it is the last call in your controller action as the return value of the withFormat
method is used by the action to dictate what happens next.
Content Negotiation with the format Request Parameter
If fiddling with request headers if not your favorite activity you can override the format used by specifying a
format
request parameter:
You can also define this parameter in the
URL Mappings definition:
"/book/list"(controller:"book", action:"list") {
format = "xml"
}
Content Negotiation with URI Extensions
Grails also supports content negotiation via URI extensions. For example given the following URI:
Grails will shave off the extension and map it to
/book/list
instead whilst simultaneously setting the content format to
xml
based on this extension. This behaviour is enabled by default, so if you wish to turn it off, you must set the
grails.mime.file.extensions
property in
grails-app/conf/Config.groovy
to
false
:
grails.mime.file.extensions = false
Testing Content Negotiation
To test content negotiation in an integration test (see the section on
Testing) you can either manipulate the incoming request headers:
void testJavascriptOutput() {
def controller = new TestController()
controller.request.addHeader "Accept",
"text/javascript, text/html, application/xml, text/xml, */*" controller.testAction()
assertEquals "alert('hello')", controller.response.contentAsString
}
Or you can set the format parameter to achieve a similar effect:
void testJavascriptOutput() {
def controller = new TestController()
controller.params.format = 'js' controller.testAction()
assertEquals "alert('hello')", controller.response.contentAsString
}