简体   繁体   中英

Spring Boot - Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

Im using the latest Spring Boot version, currently 2.2.2-RELEASE.

I have this endpoint:

@RequestMapping(method = RequestMethod.POST, value = "/test", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<?> post(@RequestParam(required = false) MultiValueMap<?, ?> paramMap) throws Exception {
    // CODE
    return new ResponseEntity<>(HttpStatus.OK);
}

If I call it (from postman) setting the body as a x-www-form.urlencoded all is fine and I receive my 200 OK status code.

But if I modify the above endpoint (adding another parameter) as follows:

@RequestMapping(method = RequestMethod.POST, value = "/test", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<?> post(@RequestParam(required = false) MultiValueMap<?, ?> paramMap, RequestEntity<?> req) throws Exception {
    // CODE
    return new ResponseEntity<>(HttpStatus.OK);
}

I receive this error:

{
    "timestamp": 1576961587242,
    "status": 415,
    "error": "Unsupported Media Type",
    "message": "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
    "path": "/test"
}

Something is missing?

Thank you in advance.

As @Sunil Dabburi suggests debugging the readWithMessageConverters method in the AbstractMessageConverterMethodArgumentResolver sheds light on this issue, which turns out to be pretty simple once understood. Spring has about 15 default message converters, which are responsible for converting the content of an incoming request to a RequestEntity or HttpEntity . If the request has body, the converter reads it and converts it to the object type it is responsible for. Depending on the incoming request's Media Type, each converter decides whether it can read the request. In the case of the application/x-www-form-urlencoded Media Type none of the default Spring converters can process such a request. Therefore, the above mentioned error occurs.

Adding a custom converter for the application/x-www-form-urlencoded Media Type to the list of Spring's http message converters would solve the issue. In the example below the custom converter is responsible only for reading requests and only for requests with the desired Media Type.

** These reads help understanding the media type itself and how does spring work with it, thus how should the custom converter convert the incoming request:

Here is an example of such custom http message converter:

@Component
public class FormUrlencodedHttpMessageConverter extends 
AbstractGenericHttpMessageConverter<MultiValueMap<String, String>> {

private static final MediaType CONVERTER_MEDIA_TYPE = MediaType.APPLICATION_FORM_URLENCODED;

public FormUrlencodedHttpMessageConverter() {
    super(CONVERTER_MEDIA_TYPE);
}

@Override
public MultiValueMap<String, String> read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {

    String urlEncodedData = new BufferedReader(
            new InputStreamReader(inputMessage.getBody(), StandardCharsets.UTF_8)).readLine();

    String[] keyValuePairs = urlEncodedData.split("&");
    MultiValueMap<String, String> keyValuePairsMap = new LinkedMultiValueMap<>();

    for (String keyValuePair : keyValuePairs) {
        String[] pairElements = keyValuePair.split("=");
        String key = pairElements[0];
        String value = pairElements[1];
        keyValuePairsMap.add(key, value);
    }

    return keyValuePairsMap;
}

@Override
protected boolean canRead(@Nullable MediaType mediaType) {

    return CONVERTER_MEDIA_TYPE.includes(mediaType);
}

@Override
protected boolean canWrite(@Nullable MediaType mediaType) {

    return CONVERTER_MEDIA_TYPE.includes(mediaType);
}

@Override
protected void writeInternal(MultiValueMap<String, String> t, Type type, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {
    throw new RuntimeException("Method 'writeInternal' in " + this.getClass().getSimpleName() + " is not implemented");
}

@Override
protected MultiValueMap<String, String> readInternal(Class<? extends MultiValueMap<String, String>> clazz, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {
    throw new RuntimeException("Method 'readInternal' in " + this.getClass().getSimpleName() + " is not implemented");
}

After a lot of search I found a lot of material but I didn't like the proposed solutions.
So I decided to experiment different approaches.
At the moment I developed this code, autowiring the HttpServletRequest I'm able to access to all desired values like headers, body, params. So I start intercepting it and then I create my customized HttpEntity .
Here's the code if someone is facing the same problem and please feel free to suggests modifications and improvements.

@RequestMapping(method = RequestMethod.POST, value = "/**", consumes = MediaType.ALL_VALUE, produces = MediaType.ALL_VALUE)
public ResponseEntity<?> post(HttpServletRequest servletRequest) throws Exception {
    String url = servletRequest.getRequestURI().replaceFirst("/", "");

    HttpHeaders httpHeaders = (Collections
            .list(servletRequest.getHeaderNames())
            .stream()
            .collect(Collectors.toMap(
                    Function.identity(),
                    h -> Collections.list(servletRequest.getHeaders(h)),
                    (oldValue, newValue) -> newValue,
                    HttpHeaders::new
            ))
    );

    HttpEntity<?> request;

    String body = servletRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
    if (!body.isEmpty()) {
        request = new HttpEntity<>(body, httpHeaders);
    } else {
        Map<String, String> payload = new HashMap<>();
        Stream.of(servletRequest.getParameterMap()).forEach(stringMap -> stringMap.forEach((k, v) ->
                Stream.of(v).forEach(value -> payload.put(k, value))
        ));
        request = new HttpEntity<>(payload, httpHeaders);
    }

    return getSuccessResponse(proxyService.doProxyTest(request, url, new HttpPost(url)));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM