在我以这种方式在 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 中,可以通过三个选项来确定请求的媒体类型:
- 请求中的 URL 后缀(扩展名)(如 .xml/.json)
- 请求中的 URL 参数(如 ?format=json)
- 在对您的 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=
在这种情况下,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/