Spring changing media type on URI with .au at the end

1.4k Views Asked by At

Currently implemented a REST endpoint as below:

@RequestMapping(path = "/login/user/{username:.+}", method = POST, produces = "application/json; charset=utf-8")
@ResponseStatus(code = HttpStatus.OK)
public User userLogin(@PathVariable("username") String username, @RequestBody Password password) {
    //do stuff
    return new User(UUID.randomUUID());
}

I currently use email address as a username, and when I use one ending in .au, the endpoint returns a 406 Content not acceptable.

I tried playing around and changing the above to this

@RequestMapping(path = "/login/user/{username:.+}", method = POST, produces = "application/json; charset=utf-8")
@ResponseStatus(code = HttpStatus.OK)
public String userLogin(@PathVariable("username") String username, @RequestBody Password password) {
    //do stuff
    return "blah";
}

When I access it, it prompts me to download an .au file (audio format made by Sun microsystems...), which contains "blah". If I check the value of the username anytime within the method, I get the correct email address, with .au included.

I'm guessing something in the Spring stack is parsing the .au and trying to enforce a different media type so now it ignores application/json

3

There are 3 best solutions below

0
On BEST ANSWER

I recently faced the same issue and found the issue. Thought of sharing it here as it would help others. This behaviour @Patrick explained seems to be occurring due to URL (URL suffix) based content negotiation in Spring MVC.

What is Content Negotiation?

There are situations where we have to deal with multiple representations (or views) of the same data returned by the controller. Working out which data format to return is called Content Negotiation.

How does Content Negotiation Work?

When making a request via HTTP it is possible to specify what type of response you would like by setting the Accept header property. However, browsers actually send very confusing Accept headers, which makes relying on them impractical. Therefore Spring offers some alternative conventions for content negotiation.

Spring Content Negotiation Alternatives - URL suffixes and/or a URL Parameter

These work alongside the use of Accept headers. As a result, the content-type can be requested in any of three ways. By default they are checked in this order:

  • Add a path extension (suffix) in the URL. So, if the incoming URL is something like http://myserver/myapp/accounts/list.html then HTML is required. For a spreadsheet the URL should be http://myserver/myapp/accounts/list.xls. The suffix to media-type mapping is automatically defined via the JavaBeans Activation Framework or JAF (so activation.jar must be on the class path).

  • A URL parameter like this: http://myserver/myapp/accounts/list?format=xls. The name of the parameter is format by default, but this may be changed. Using a parameter is disabled by default, but when enabled, it is checked second.

    • Finally the Accept HTTP header property is checked. This is how HTTP is actually defined to work, but, as previously mentioned, it can be problematic to use.

In the above case explained in the question, what you see is path extension based content negotiation in action. (.au)

From the hava doc of ContentNegotiationConfigurer,

favorPathExtension

public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension)

Whether the path extension in the URL path should be used to determine the requested media type.

By default this is set to true in which case a request for /hotels.pdf will be interpreted as a request for "application/pdf" regardless of the 'Accept' header.

The Solution - Setting favorPathExtension to false

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false).
            favorParameter(true).
            parameterName("mediaType").
            ignoreAcceptHeader(true).
            useJaf(false).
            defaultContentType(MediaType.APPLICATION_JSON).
            mediaType("xml", MediaType.APPLICATION_XML).
            mediaType("json", MediaType.APPLICATION_JSON);
  }
}

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

    <property name="mediaTypes">
        <map>
            <entry key="json" value="application/json" />
            <entry key="xml" value="application/xml" />
       </map>
    </property>
</bean>

Please note that above configurations have some additional changes apart from setting favorPathExtension to false.

More details on this can be found here.

Just for completion, the response we get with the issue is as follows.

{
  "timestamp": 1518691842254,
  "status": 406,
  "error": "Not Acceptable",
  "exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
  "message": "Not Acceptable",
  "path": "/rest/token/something.au"
}
0
On

I think it causes from A data transfer object (DTO) when serializer and deserializer. So User should implement Serializable interface.

0
On

I faced the similar issue. My resource is mapped to /upload/( expecting the file path). So the resource URI will be like /upload/a/b/c/test1.jpg, upload/xy/test2.xml etc.,

Like @Rajind mentioned, it is considering the media type as the extension present in the URL(after the dot(.)).

HTTP Status 406 – Not Acceptable

{
    "timestamp": 1538992653298,
    "status": 406,
    "error": "Not Acceptable",
    "message": "Could not find acceptable representation",
    "path": "/file-manager-services/api-6.0/234833/upload/Mohan/tst.jpg"
}

I got it resolved by adding the below config.

@Configuration
@EnableWebMvc
public class **** implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

        configurer.favorPathExtension(false);
    }

....
....

}