After I set http header in Controller
in such way:
@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) {
....
}
}
then later Spring overwrite it to plain text, callstack here:
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)
why would spring do that? it's so hard to understand, I think user customized logic has higher priority, spring should not make decision automatically.
actually, I think the question has enough explanation, but SO thinks there is mostly code, asking to add more detail, what can I do, thanks in advance.
There is a default spring configuration, based on 3 strategies which select the content type to be returned. The configuration could be modified.
There are two ways of customising the content-type header negotiation configuration for all the responses, by XML configuration and another via Annotation driven configuration.
On very same configuration, there is a way of injection a customised strategy for choosing which URLs should be affected by the rule of changing the content-type header.
Thankfully, on spring boot adding the produce attribute on the @RequestMapping anotation of the controller and also changing a property would be enough for getting the desire behaviour:
In Spring MVC there are three options to determine the media type of a request:
On this very order Spring negotiate the content-type header response and the body response format, and if none of these are enabled, we can specify a fallback to a default content type.
So for customising this behavior we should provide a fallback default content type and deactivate the three strategies described above. There are two approaches for acomplishing it, using XML configuration or annotation configuration:
@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");
}
}
Or the XML configuration
<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>
Another custom solution would be create your own @DefaultContentType annotation. Overrinding RequestMappingHandlerMapping#getCustomMethodCondition which checks for the annotation@DefaultContentType on the method. The custom condition would always match but in the compareTo, it would prioritize methods that have the annotation, over those that don't.
I would do the above solution if you need to use it a lot of times.
For a one off occurrence, you could plug in a custom defaultContentTypeStrategy via ContentNegotiationConfigurer that checks for a specific URL and returns a preferred media type like:
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;
}
}
Then, add your custom strategy via configuration:
@EnableWebMvc
@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {
configurer.defaultContentTypeStrategy(new MyCustomContentNegotiationStrategy());
}
}
Finally, if you're using spring boot, as @StavShamir has suggested, on answer https://stackoverflow.com/a/62422889/3346298, there are a bunch of common application properties which could be helpful on this case:
# 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=
On this very case, the spring.http.encoding.enabled property set to true and using the argument produces argument on @RequestMapping annotation would work:
@RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")