Зачем Spring перезаписывать Content-Type в заголовке? - PullRequest
2 голосов
/ 17 июня 2020

После того, как я установил HTTP-заголовок в Controller таким образом:

@Controller
@Slf4j
public class PlayerController {    
    @ModelAttribute
    public void setVaryResponseHeader(HttpServletResponse response) {
        response.setHeader("Content-Type", "application/vnd.apple.mpegurl");
    }

    @ResponseBody
    @RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET)
    public String playbackLive(@RequestParam(value = "delay") Integer delay) {
        ....
    }
}

, позже Spring перезапишет его обычным текстом, стек вызовов здесь:

writeWithMessageConverters:184, AbstractMessageConverterMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:174, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:81, HandlerMethodReturnValueHandlerComposite (org.springframework.web.method.support)
......
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

зачем Spring делать который? это так сложно понять, я думаю, что настроенный пользователем logi c имеет более высокий приоритет, spring не должен принимать решение автоматически.

на самом деле, я думаю, что вопрос имеет достаточно объяснений, но SO думает, что в основном код, спрашивающий Чтобы добавить больше деталей, что я могу сделать, заранее спасибо.

Ответы [ 2 ]

3 голосов
/ 17 июня 2020

TL; DR

Почему и как пружина перезаписывает ContentType в заголовке?

Существует конфигурация пружины по умолчанию, основанная на 3 стратегиях, которые выбирают тип содержимого подлежит возврату. Конфигурация может быть изменена.

Настройка всех ответов заголовка типа содержимого от всех контроллеров

Существует два способа настройки конфигурации согласования заголовка типа содержимого для всех ответов: XML, а другой - через конфигурацию, управляемую аннотациями.

Настройка значения заголовка типа содержимого для определенных URL-адресов

В той же конфигурации существует способ внедрения индивидуальной стратегии для выбора URL-адресов должно быть затронуто правило изменения заголовка типа содержимого.

При загрузке Spring

К счастью, при загрузке Spring добавление атрибута production в аннотацию @RequestMapping контроллера, а также изменение свойства было бы достаточным для получения желаемого поведения:

  • @ RequestMapping (value = "/live/timeshift.m3u8", method = RequestMethod.GET, производит = "application / vnd.apple.mpegurl")
  • Свойство -> spring .http.encoding.enabled установлено значение true

Длинный ответ

Почему и как Spring перезаписывает ContentType в заголовке?

В Spring MVC есть три варианта определения типа носителя запроса:

  1. суффиксы (расширения) URL-адреса в запросе (например, .xml /.json)
  2. Параметр URL в запросе (например,? Format = json)
  3. Заголовок Accept в запросе, выполненном для метода вашего контроллера

Именно в этом порядке Spring согласование ответ заголовка типа содержимого и формат ответа тела, и если не Если из них включены, мы можем указать откат к типу контента по умолчанию.

Настройка всех ответов заголовков типа контента от всех контроллеров

Поэтому для настройки этого поведения мы должны предоставить откатить тип содержимого по умолчанию и отключить три описанные выше стратегии. Есть два подхода к его выполнению, используя конфигурацию XML или конфигурацию аннотации:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false).
    favorParameter(false).
    ignoreAcceptHeader(true).
    useJaf(false).
    defaultContentType("application/vnd.apple.mpegurl");
  }
}

или XML конфигурация

<bean id="contentNegotiationManager"
  class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false" />
    <property name="favorParameter" value="false"/>
    <property name="ignoreAcceptHeader" value="true" />
    <property name="defaultContentType" value="application/vnd.apple.mpegurl"/>
    <property name="useJaf" value="false" />
</bean>

Настройка значения заголовка типа содержимого для определенных URL-адреса

Еще одно индивидуальное решение - создать собственную аннотацию @DefaultContentType. Переопределение RequestMappingHandlerMapping # getCustomMethodCondition, которое проверяет наличие annotation@DefaultContentType в методе. Пользовательское условие всегда будет совпадать, но в compareTo оно будет отдавать приоритет методам, имеющим аннотацию, по сравнению с теми, у которых их нет.

Я бы применил вышеуказанное решение, если вам нужно использовать его много раз.

Для разового возникновения вы можете подключить настраиваемый defaultContentTypeStrategy через ContentNegotiationConfigurer, который проверяет указанный c URL и возвращает предпочтительный тип мультимедиа, например:

public class MyCustomContentNegotiationStrategy implements ContentNegotiationStrategy {

  @Override
  public List<MediaType> resolveMediaTypes (final NativeWebRequest nativeWebRequest)
            throws HttpMediaTypeNotAcceptableException {
      final List<MediaType> mediaTypes = new ArrayList<>();
      final String url =((ServletWebRequest)request).getRequest().getRequestURI().toString();
      final String yourUrlpatternString = ".*http://.*";

      final Pattern yourUrlPattern = Pattern.compile(patternString);
      final Matcher matcher = pattern.matcher(url);

     if(matcher.matches()) {
          mediaTypes.add("application/vnd.apple.mpegurl");
      return mediaTypes;
  }
}

Затем добавьте ваша индивидуальная стратегия через конфигурацию:

@EnableWebMvc
@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {
      configurer.defaultContentTypeStrategy(new MyCustomContentNegotiationStrategy());
  }
}

При загрузке Spring

Наконец, если вы используете весеннюю загрузку, как предложил @StavShamir, в ответе { ссылка }, есть несколько общих свойств приложения, которые могут быть полезны в этом случае:

# HTTP encoding (HttpEncodingProperties)
# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly.
spring.http.encoding.charset=UTF-8 
# Whether to enable http encoding support.
spring.http.encoding.enabled=true 
# Whether to force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force= 
# Whether to force the encoding to the configured charset on HTTP requests. Defaults to true when "force" has not been 
spring.http.encoding.force-request= specified.
# Whether to force the encoding to the configured charset on HTTP responses.
spring.http.encoding.force-response= 
# Locale in which to encode mapping.
spring.http.encoding.mapping= 

https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common -application-properties

Именно в этом случае , для свойства spring.http.encoding.enabled установлено значение true, и использование аргумента создает аргумент в аннотации @RequestMapping:

@RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")
2 голосов
/ 17 июня 2020

Это хороший вопрос, но самым простым решением для вас может быть использование аргумента produces для аннотации сопоставления:

@ResponseBody
@RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")
public String playbackLive(@RequestParam(value = "delay") Integer delay) {
    ....
}

Если вам не нужно динамически назначать тип содержимого ответа, Я считаю этот способ наиболее простым.

...