6.8 Content Negotiation - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: 2.0.4
6.8 Content Negotiation
Grails has built in support for Content negotiation using either the HTTPAccept 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 withingrails-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']
]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:*/*
Accept header):text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5
property to the response object that outlines the preferred response format. For the above example the following assertion would pass:assert 'html' == response.format
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' == response.format
import grails.converters.XMLclass BookController { def list() { def books = Book.list() withFormat { html bookList: books js { render "alert('hello')" } xml { render books as XML } } } }
html then Grails will execute the html() call only. This causes Grails to 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.
Request format vs. Response format
As of Grails 2.0, there is a separate notion of the request format and the response format. The request format is dictated by theCONTENT_TYPE header and is typically used to detect if the incoming request can be parsed into XML or JSON, whilst the response format uses the file extension, format parameter or ACCEPT header to attempt to deliver an appropriate response to the client.The withFormat available on controllers deals specifically with the response format. If you wish to add logic that deals with the request format then you can do so using a separate withFormat method available on the request:request.withFormat {
xml {
// read XML
}
json {
// read JSON
}
}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 aformat request parameter:/book/list?format=xml
"/book/list"(controller:"book", action:"list") { format = "xml" }
Content Negotiation with URI Extensions
Grails also supports content negotiation using URI extensions. For example given the following URI:/book/list.xml
/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 = falseTesting Content Negotiation
To test content negotiation in a unit or 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
}void testJavascriptOutput() {
def controller = new TestController()
controller.params.format = 'js' controller.testAction()
assertEquals "alert('hello')", controller.response.contentAsString
}
