通过 REST 模板发送 json 文件时 415 不支持的媒体类型

415 Unsupported Media Type while sending json file over REST Template

我正在尝试通过 REST 模板发送 json 文件。当我通过 POST man 作为 MULTIPART_FORM_DATA 发送它时,它工作正常。我应该给出的名称是特定的(可以说 aaa)。附加屏幕截图 of POSTMAN. But when I try same in code as specified in another Whosebug post,我收到 415 Unsupported Media Type 错误

org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:616) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:572) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:532) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:332) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
    at 

请不要将其标记为重复,因为指定的答案对我不起作用。不共享代码,因为我的代码与 this 完全相同,除了

requestParamerterMap.add("attachment", resource);

我的代码在哪里

requestParamerterMap.add("aaa", resource);

从服务器端调试后,看起来请求正在到达服务器。我能够在服务器端看到以下错误:

[{error=Unsupported Media Type, exception=org.springframework.web.HttpMediaTypeNotSupportedException, message=Content type 'application/octet-stream' not supported, status=415, timestamp=1532557180124}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@74d4827a]

因此,从服务器端日志中,我不确定内容类型添加为 application/octet-stream 的位置,因为我已将内容类型设置为

headers.setContentType(MediaType.MULTIPART_FORM_DATA);

下面是来自服务器控制器的代码。服务器端代码使用 Spring boot.

    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,consumes = {"multipart/form-data"})
        @ResponseBody
        public MyResponse uploadPhoto(@RequestPart(value = "aaa", required = false) Optional<MyRequest> myRequest,
                                    @RequestPart(value = "file", required = false) Optional<MultipartFile> file,
                                    HttpServletRequest request) {
//some logic
            return myResponse;
        }

服务器代码有一个拦截器,我可以在其中看到我的请求的内容类型为 multipart/form-data。它没有到达 RestController

当我调试服务端代码时有2种情况:

  1. POSTMAN 请求

  1. 客户端代码请求

有一件事我发现当我从 POST post 时,文件 iteam 的内容类型为 application/json当请求来自 客户端代码 .

时,MAN 并且内容类型为 application/octet-stream

在我的客户端代码中,我将 JSONObject 创建为

JSONObject json = new JSONObject();
json.append("myKey", "myValue");

并将其转换为字节数组

json.toString().getBytes("UTF-8")

那我关注了this。我的代码的不同之处在于,我将我的 JSONObject 作为字节流发送,因为我无法创建文件(性能问题)。

而且我无法将 JSONObject 作为字符串发送,因为服务器需要 文件和 aaa

的多部分表单数据

我已将 restTemplate 创建为

 public RestTemplate myRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setReadTimeout(HTTP_CLIENT_TIMEOUT);
        requestFactory.setConnectTimeout(HTTP_CLIENT_TIMEOUT);

        RestTemplate restTemplate = new RestTemplate(requestFactory);
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
        messageConverters.add(new FormHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter());
        restTemplate.setMessageConverters(messageConverters);
        return restTemplate;

这是调用服务的客户端代码:

public Optional<JSONObject> callService(byte[] multipartFile) {
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        InputStream stream = new ByteArrayInputStream(multipartFile);
        MultipartByteArrayResource resource = new MultipartByteArrayResource(multipartFile,fileName);


       body.add("aaa", resource);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);

        try {
            response =  restTemplate.postForObject(url, requestEntity , String.class);


        } catch (Exception exception) {
            LOG.error("Error", exception);
            return Optional.empty();
        }
    }

   public class MultipartInputStreamFileResource extends InputStreamResource {

        private final String filename;

        MultipartInputStreamFileResource(InputStream inputStream, String filename) {
            super(inputStream);
            this.filename = filename;
        }

        @Override
        public String getFilename() {
            return this.filename;
        }

        @Override
        public long contentLength() throws IOException {
            return -1; // we do not want to generally read the whole stream into memory ...
        }
}

当我发送文件时相同的代码有效(注意 fileaaa 是两个不同的东西,尽管它们都是 multipart/form-data 在服务器端。file 只是任何时间的 文件 (image/text/pdf) 但是aaajson数据文件)

再调试一点之后,我观察到服务器端控制器期望文件内容为 json,因为 Jackson 试图将 json 反序列化为 MyRequest 对象。当我从 POSTMAN 发送 post 时,它具有 json 内容,因此按预期工作,但从客户端代码来看,内容是 byteArray ,并且它没有反序列化为 MyRequest 对象。不确定如何解决这个问题

服务器端异常是由org.springframework.http.converter.json.MappingJackson2HttpMessageConverter产生的。 Jackson 是一个 JSON 库,Spring 使用 MessageConverter 来格式化请求和响应。

难道客户端发送了一个"Accept: application/octet-stream",而服务器端却有一个@Produces(APPLICATION_JSON)注解?这将意味着服务器处理请求并且只在发送 响应 时遇到问题。您可以在服务器中添加一些 log.info() 语句来验证这一点。

我终于解决了这个问题。正如问题中提到的,在从 POSTMAN vs 代码发送请求时,具有不同内容类型的多部分文件是我开始的地方。如果有人有任何问题,我会详细解释。

    public Optional<JSONObject> save(byte[] multipartFile, String fileName) {
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        Resource content = new MultipartByteArrayResource(multipartFile , fileName);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<Resource> requestEntityBody = new HttpEntity<Resource>(content, headers);
        body.add("aaa", requestEntityBody);
        String result = "";
        JSONParser parser = new JSONParser();
        JSONObject json = null;


        HttpHeaders requestHeaders = new HttpHeaders();
        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, requestHeaders);
        ResponseEntity<String> response = null;
        try {
           RestTemplate restTemplate = customizeRestTemplate(); //I have defined this in different config file in my actual code
           response =  restTemplate.exchange(url , HttpMethod.POST , requestEntity , String.class);
           result = (response != null && response.getBody() != null) ? response.getBody().toString() : result;
           json = (JSONObject) parser.parse(result);
           LOG.info( "Response:", response );

        } catch (Exception exception) {
            LOG.error("Error , exception);
            return Optional.empty();
        }
        return Optional.ofNullable(json);
    }

   public class MultipartByteArrayResource extends ByteArrayResource{

       private String fileName;

        public MultipartByteArrayResource(byte[] byteArray , String filename) {
               super(byteArray);
               this.fileName = filename;
           }

        public String getFilename() { 
            return fileName; 
          }

        public void setFilename(String fileName) {
            this.fileName= fileName;
         }

     }

      public RestTemplate customizeRestTemplate() {

            SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
            requestFactory.setReadTimeout(10000);
            requestFactory.setConnectTimeout(10000);

            RestTemplate restTemplate = new RestTemplate(requestFactory);
            List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
            messageConverters.add(new FormHttpMessageConverter());
            messageConverters.add(new StringHttpMessageConverter());
            restTemplate.setMessageConverters(messageConverters);
            return restTemplate;
        }

}