spring - 为什么 spring 会覆盖 header 中的 Content-Type?

标签 spring content-type

在我以这种方式在 Controller 中设置 http header 之后:

@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会那样做?太难理解了,我觉得用户自定义逻辑优先级更高,spring不应该自动决策。

实际上,我认为这个问题有足够的解释,但 SO 认为主要是代码,要求添加更多细节,我能做什么,提前致谢。

最佳答案

长话短说

spring 为什么以及如何覆盖 header 中的 ContentType?

有一个默认的 spring 配置,基于 3 种选择要返回的内容类型的策略。可以修改配置。

自定义来自所有 Controller 的所有内容类型 header 响应

有两种方法可以为所有响应自定义内容类型 header 协商配置,一种是通过 XML 配置,另一种是通过注释驱动配置。

为某些 URL 自定义内容类型 header 值

在非常相同的配置上,有一种方法可以注入(inject)自定义策略来选择哪些 URL 应该受到更改内容类型 header 的规则的影响。

在 Spring Boot 上

值得庆幸的是,在 spring boot 上,在 Controller 的 @RequestMapping 注释上添加 produce 属性并更改属性就足以获得所需的行为:

  • @RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")
  • 属性->spring.http.encoding.enabled设置为true

长答案

spring 为什么以及如何覆盖 header 中的 ContentType?

在 Spring MVC 中,可以通过三个选项来确定请求的媒体类型:

  1. 请求中的 URL 后缀(扩展名)(如 .xml/.json)
  2. 请求中的 URL 参数(如 ?format=json)
  3. 在对您的 Controller 方法完成的请求中接受 header

在这个顺序上,Spring 协商内容类型 header 响应和正文响应格式,如果这些都未启用,我们可以指定回退到默认内容类型。

自定义来自所有 Controller 的所有内容类型 header 响应

因此,为了自定义此行为,我们应该提供后备默认内容类型并停用上述三种策略。有两种方法可以完成它,使用 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 自定义内容类型 header 值

另一个自定义解决方案是创建您自己的@DefaultContentType 注释。覆盖 RequestMappingHandlerMapping#getCustomMethodCondition,它检查方法上的注释@DefaultContentType。自定义条件将始终匹配,但在 compareTo 中,它会优先考虑具有注释的方法,而不是那些不具有注释的方法。

如果您需要多次使用它,我会采用上述解决方案。

对于一次性事件,您可以通过 ContentNegotiationConfigurer 插入自定义 defaultContentTypeStrategy 以检查特定 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 Boot 上

最后,如果您正在使用 spring boot,正如@StavShamir 所建议的那样,请回答 https://stackoverflow.com/a/62422889/3346298 , 有一堆常见的应用程序属性可能对这种情况有帮助:

# 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 并使用 argument produces argument on @RequestMapping annotation 会起作用:

@RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")

关于spring - 为什么 spring 会覆盖 header 中的 Content-Type?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62422380/

相关文章:

spring - tomcat 启动时自动启动 Servlet ...?

java - H2数据库是否兼容Oracle 'Insert All'语句?

java - 必须在 servlet 上以及异步请求处理中涉及的所有过滤器上启用异步支持

php - 链接远程文件问题

java - AnnotationAwareAspectJAutoProxyCreator 仅适用于 Java 1.5 及更高版本

spring - 如何告诉 Spring 缓存不要在 @Cacheable 注释中缓存空值

java - 如何设置过滤器仅适用于内容类型 "html/text"?

html - 在浏览器中下载的 EML 文件,而不是使用电子邮件应用程序内联打开

html - multipart/x-mixed-replace 内容类型的视频流问题

php - 了解大型 mysql 数据关系