TL;DR
Why and how spring overwrite the ContentType in header?
There is a default spring configuration, based on 3 strategies which select the content type to be returned. The configuration could be modified.
Customising all the content-type header responses from all the controllers
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.
Customising the content-type header value for certain URLs
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.
On Spring Boot
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:
- @RequestMapping(value = "/live/timeshift.m3u8", method = RequestMethod.GET, produces = "application/vnd.apple.mpegurl")
- Property - > spring.http.encoding.enabled set to true
Long Answer
Why and how spring overwrite the ContentType in header?
In Spring MVC there are three options to determine the media type of a request:
- URL suffixes (extensions) in the request (like .xml/.json)
- URL parameter in the request (like ?format=json)
- Accept header in the request done to your controller method
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.
Customising all the content-type header responses from all the controllers
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>
Customising the content-type header value for certain URLs
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());
}
}
On Spring Boot
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=
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-application-properties
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")