2016-07-09 9 views
0

Я пытаюсь реализовать простой RestfulController для своего приложения. Учитывая следующий класс домена:Grails RestfulController не отвечает JSON, когда присутствует заголовок Content-Type: application/x-www-form-urlencoded

class Test { 
    String name 
    int someInteger 

    static constraints = { 
    } 
} 

и его контроллер:

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

Внутри конф/UrlMappings.groovy я добавил следующие данные:

"/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"] 
} 

Получить запросы работают нормально, но отправлять и отправлять запросы по URL-адресу, например http://localhost:8080/app/api/test.json, когда присутствует заголовок Content-Type: application/x-www-form-urlencoded, который не отвечает JSON, как ожидалось. Вместо этого визуализируйте представление действия шоу после сохранения отправленного entrie.

Я также попытался использовать заголовок Accept: application/json без эффекта.

Как это исправить?

Edit:

Дальнейшего расследование RestfulController «s исходного файла и раздел документов о Content Negotiation я был в состоянии исправить это путем переопределения сохранения и метода обновления заменить строку:

request.withFormat { 

с:

withFormat { 

Это намеренно или есть недостаток в реализации RestfulController? Почему он рассматривает заголовок Content-Type вместо заголовка Accept для визуализации ответа?

ответ

0

Извините, что так долго отвечал. У меня были проблемы с работой. Большое спасибо @ Dónal за помощь. Закончилось используя следующий класс, чтобы сделать трюк:

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] 
      } 
     } 
    } 

} 

Используя def formatHolder = params.format ? this : request, а затем вызвать formatHolder.withFormat Теперь я могу переопределить формат ответа независимо от формата запроса.

Он еще не работает для Accept Header, но по крайней мере он работает.

1

Если это приемлемо для методов все вашего контроллера всегда реагировать с JSON (когда тело ответа), вы можете добиться этого с responseFormats так:

class TestController extends RestfulController<Test>{ 

    static responseFormats = ['json'] 

    TestController() { 
     super(Test) 
    } 

    def customJsonAction() { 
     respond Something.get(params.id) 
    } 

    def someActionThatRendersGsp() { 
     render view: 'myGsp', model: [foo: 'bar'] 
    } 
} 

Это означает, что контроллер всегда будет реагировать с JSON независимо от того, какие заголовки, параметры и т. д. отправляются клиентом.

+0

Эй, спасибо за быстрый ответ. К сожалению, этот же контроллер используется для API остатка и для рендеринга crud views. Возможно, это была плохая проектная стратегия, но усилия по ее изменению сейчас слишком велики. Моим обходным решением пока является создание класса 'MyRestfullController', расширяющего' RestfullController' изменение поведения методов сохранения и обновления для рассмотрения заголовка Accept вместо Content-Type. – ylima

+0

Я думаю, что мое предложение должно работать, даже если есть методы, которые отображают представления GSP, если эти методы вызывают 'render' вместо' reply' –

+0

Итак, вы предлагаете переопределить эти действия, чтобы использовать 'render' внутри' withFormat', вместо 'respond'? Я попробую это и ответ скоро, если это сработает. Спасибо – ylima