Приоритет text/html над другими типами контента, когда клиент отправляет Accept: */*

Я пытаюсь написать контроллер с Spring MVC 3.2, который потребляет/производит как JSON, так и HTML. У меня есть два метода обработчика, которые создают разные типы контента:

@Controller
public class FooController {
    @RequestMapping(value="/foo", produces="text/html")
    public String fooHTML() {
        // ...
    }

    @RequestMapping(value="/foo", produces="application/json")
    public String fooJSON() {
        // ...
    }
}

Это прекрасно работает, если заголовок Accept от клиента содержит либо text/html, либо application/json,

...но потом появился Internet Explorer. Как отмечено здесь, заголовок Accept в IE различается, но никогда не содержит text/html и всегда имеет */* в конце. Когда Spring получает запрос от IE, он не видит типов контента, непосредственно равных типам, созданным моим контроллером, но, привязываясь к подстановочному знаку */*, он (правильно) решает, что будут применяться оба сопоставления.

Столкнувшись с несколькими совпадающими сопоставлениями обработчиков, Spring (в bean-компоненте RequestMappingHandlerMapping) сортирует сопоставления по тому, что по существу соответствует лексикографическому порядку, выбирает первое и движется дальше. Проблема, с моей точки зрения, в том, что этот процесс отдает приоритет application/json, а не text/html. Я бы предпочел вернуть text/html, если только клиент специально не запрашивает application/json — таким образом я могу предоставлять HTML для тупых клиентов, таких как IE, и JSON для клиентов, разбирающихся в типах контента, таких как пользователи моего API.

Кто-нибудь знает способ сделать это, который не требует расширения RequestMappingHandlerMapping для другой сортировки обработчиков? У вас есть простые обходные пути?

ПРИМЕЧАНИЕ. Я попытался установить тип контента по умолчанию в ContentNegotiationManager, как описано в блоге Spring. Это не решает мою проблему, потому что этот параметр вступает в силу, только если заголовок Accept не указан.


person Tim Yates    schedule 06.06.2013    source источник


Ответы (2)


Одним из решений является снижение приоритета fooJSON() с помощью параметра value.

На практике шаблоны /foo и /foo{1} эквивалентны. Однако второй считается «более общим» и используется в последнюю очередь:

@Controller
public class FooController {
    @RequestMapping(value="/foo", produces="text/html")
    public String fooHTML() {
        // ...
    }

    @RequestMapping(value="/foo{1}", produces="application/json")
    //                         ^^^------- changed here
    public String fooJSON() {
        // ...
    }
}

Сюда:

  • text/html идет к fooHTML()
  • application/json идет к fooJSON()
  • */* идет к fooHTML()
  • все остальное дает ошибку 406 - Not Acceptable
person acdcjunior    schedule 06.06.2013
comment
Что вы имеете в виду под чем-то еще? Есть ли что-то, что не будет отображаться на */*??? - person Pavel Horal; 07.06.2013
comment
Не обращайте внимания на мой комментарий... только что понял, что вы имели в виду. - person Pavel Horal; 07.06.2013
comment
Очень креативно. Я бы точно не додумался. К сожалению, это ограничивает меня определенным образом, что неприемлемо. Я не могу заставить оба /foo/ и /foo/some-foo-id/ отвечать на оба типа содержимого, потому что, если я объявлю сопоставление /foo/{1} -> JSON и сопоставление /foo/{someID}/ -> HTML, они перекрываются, и /foo /some-foo-id/ с */* выбирает JSON. - person Tim Yates; 07.06.2013
comment
Я думаю, следует добавить, что эта ситуация является проблемой, когда у меня есть контроллер class, сопоставленный с /foo, и два метода, сопоставленные с {1} -> JSON и /{someID}/ -> HTML. На самом деле невозможно создать обработчик JSON, который иначе отвечает на /foo/. Суть в том, что это просто исключает множество вариантов. Но в некоторых местах это все еще может быть ценным методом. - person Tim Yates; 07.06.2013
comment
Хм, я не уверен, что понимаю. Можете ли вы привести точный пример? (Если вам нужен шаблон /foo/{someID}, то JSON будет /foo{1}/{someID}.) - person acdcjunior; 07.06.2013
comment
Д'о. Я только пробовал комбинации с фиктивной переменной пути в конце строки. Кажется, это работает отлично. - person Tim Yates; 07.06.2013

Я нашел один способ обойти эту проблему — добавить ВСЕ MIME-типы к сопоставлению запросов, которые обслуживают HTML-версию ответа.

@RequestMapping(value="/foo", produces={"text/html", "*/*"})
public String fooHTML() {
    // ...
}

Это означает, что ответ HTML обслуживается для любых ранее несопоставленных типов mime. В моем случае такое поведение было желательным, но если вам нужно вернуть код ответа 406 (неприемлемо), то это вам не подойдет.

person Peter Bouquet    schedule 21.05.2015