Spring Boot-不支持内容类型'application / x-www-form-urlencoded; charset = UTF-8'

时间:2019-12-21 20:57:10

标签: java spring spring-boot

我使用的是最新的Spring Boot版本,当前为2.2.2-RELEASE。

我有这个端点:

@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);
}

如果我(从邮递员那里)调用它,则将正文设置为x-www-form.urlencoded表示一切正常,并且我收到了200 OK状态代码。

但是,如果我如下修改上述端点(添加另一个参数):

@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);
}

我收到此错误:

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

缺少什么?

谢谢。

2 个答案:

答案 0 :(得分:3)

@Sunil Dabburi建议调试readWithMessageConverters中的AbstractMessageConverterMethodArgumentResolver方法可以阐明这个问题,一旦被理解,这很简单。 Spring有大约15个默认消息转换器,它们负责将传入请求的内容转换为RequestEntityHttpEntity。如果请求具有主体,则转换器将读取该主体并将其转换为它负责的对象类型。根据传入请求的媒体类型,每个转换器决定是否可以读取该请求。对于application/x-www-form-urlencoded媒体类型,默认的Spring转换器均无法处理此类请求。因此,发生上述错误。

application/x-www-form-urlencoded媒体类型的自定义转换器添加到Spring的http消息转换器的列表中将解决此问题。在下面的示例中,自定义转换器仅负责读取请求,并且仅负责具有所需媒体类型的请求。

**这些读物有助于理解媒体类型本身以及spring如何使用它,因此自定义转换器应如何转换传入的请求:

以下是此类自定义http消息转换器的示例:

@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");
}

答案 1 :(得分:0)

经过大量搜索,我发现了很多材料,但我不喜欢建议的解决方案。
所以我决定尝试不同的方法。
目前,我开发了这段代码,自动装配了HttpServletRequest,我可以访问所有所需的值,例如标头,正文,参数。因此,我开始拦截它,然后创建定制的HttpEntity
如果有人遇到相同的问题,请使用以下代码,请随时提出修改和改进建议。

@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)));
}