restgrailsgrails-2.5

Grails RestfulController doesn't respond with a JSON when Content-Type: application/x-www-form-urlencoded header is present


I'm trying to implement a simple RestfulController for my application. Given the following domain class:

class Test {
    String name
    int someInteger

    static constraints = {
    }
}

and its controller:

class TestController extends RestfulController<Test>{
    TestController() {
        super(Test)
    }
}

Inside conf/UrlMappings.groovy I added the following entries:

"/api/$controller?(.${format})?" {
    action = [POST: "save", PUT: "save", GET: "index", DELETE:"error"]
}

"/api/$controller/$id?(.${format})?" {
    action = [POST: "update", PUT: "update", GET: "show", DELETE: "delete"]
}

Get requests are working fine, but Post and Put requests to a URL like http://localhost:8080/app/api/test.json when the Content-Type: application/x-www-form-urlencoded Header is present fail to respond with a JSON as expected. Instead render the show action view after persisting the entrie sent.

I also tried to use the Header Accept: application/json with no effect.

How can I fix that?

Edit:

Further investigating RestfulController's source file and the docs section regarding Content Negotiation I was able fix it by overriding the save and update methods replacing the line:

request.withFormat {

with:

withFormat {

Is it intentional or is there a flaw on RestfulController's implementation? Why does it consider the Content-Type header instead of the Accept header to render response?


Solution

  • Sorry for taking so long to respond. I had some trouble putting everything to work. Thanks a lot @Dónal for all the help. Ended using the following class to do the trick:

    import org.codehaus.groovy.grails.web.servlet.HttpHeaders;
    import org.springframework.http.HttpStatus;
    
    import grails.artefact.Artefact;
    import grails.rest.RestfulController;
    import grails.transaction.Transactional;
    
    @Artefact("Controller")
    @Transactional(readOnly = true)
    class MyRestfulController<T> extends RestfulController<T> {
    
        public MyRestfulController(Class<T> resource, boolean readOnly = false) {
            super(resource, readOnly);
        }
    
        @Override
        @Transactional
        def save() {
            if(handleReadOnly()) {
                return
            }
            T instance = createResource(getParametersToBind())
    
            instance.validate()
            if (instance.hasErrors()) {
                respond instance.errors, view:'create' // STATUS CODE 422
                return
            }
    
            instance.save flush:true
    
            def formatHolder = params.format ? this : request
            formatHolder.withFormat {
                form multipartForm {
                    flash.message = message(code: 'default.created.message', args: [message(code: "${resourceName}.label".toString(), default: resourceClassName), instance.id])
                    redirect instance
                }
                '*' {
                    response.addHeader(HttpHeaders.LOCATION,
                            g.createLink(
                                    resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
                                    namespace: hasProperty('namespace') ? this.namespace : null ))
                    respond instance, [status: HttpStatus.CREATED]
                }
            }
        }
    
        @Override
        @Transactional
        def update() {
            if(handleReadOnly()) {
                return
            }
    
            T instance = queryForResource(params.id)
            if (instance == null) {
                notFound()
                return
            }
    
            instance.properties = getParametersToBind()
    
            if (instance.hasErrors()) {
                respond instance.errors, view:'edit' // STATUS CODE 422
                return
            }
    
            instance.save flush:true
    
            def formatHolder = params.format ? this : request
            formatHolder.withFormat {
                form multipartForm {
                    flash.message = message(code: 'default.updated.message', args: [message(code: "${resourceClassName}.label".toString(), default: resourceClassName), instance.id])
                    redirect instance
                }
                '*'{
                    response.addHeader(HttpHeaders.LOCATION,
                            g.createLink(
                                    resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
                                    namespace: hasProperty('namespace') ? this.namespace : null ))
                    respond instance, [status: HttpStatus.OK]
                }
            }
        }
    
    }
    

    By using def formatHolder = params.format ? this : request and then call formatHolder.withFormat I am now able to override the response format independently from the request format.

    It doesn't work for the Accept Header yet but at least it works.