Upgrading to Spring 5 broke RestTemplate MultipartFile upload - spring

I upgraded from Spring 4.3.16 to Spring 5.0.7. When trying to upload a file using the restTemplate I started to get a "400 - Bad Request". After messing around the only difference in behavior I noticed was removing requestEntity from the exchange call and the error (bad request) goes away, however it fails because the file is not present to be uploaded.
Any help would be greatly appreciated!
The client:
public <T> ResponseEntity<T> uploadMultipartFile(String requestParamName, byte[] byteArray, String fileName,
Class<T> responseType) {
ByteArrayResource byteArrayAsResource = new ByteArrayResource(byteArray) {
#Override
public String getFilename() {
return fileName;
}
};
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add(requestParamName, byteArrayAsResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
return getRestTemplate().exchange(buildUriWithVariables(new HashMap<>()), HttpMethod.POST, requestEntity, responseType);
}
And the controller:
#RequestMapping("/multipleUpload")
public ModelAndView multipleUpload(HttpServletRequest request, HttpServletResponse response,
#RequestParam(value = "files", required = false) MultipartFile[] files,
#ModelAttribute("document") Document document,
BindingResult result) {}
It looks like Apache is complaining about the request body:
[Wed Jul 18 11:02:29.705758 2018] [:error] [pid 1313:tid
140177850750720] [client ] ModSecurity: Access denied with
code 400 (phase 2). Match of "eq 0" against "REQBODY_ERROR" required.
[file "/etc/modsecurity/modsecurity.conf"] [line "54"] [id "200001"]
[msg "Failed to parse request body."] [data "Multipart: Invalid
boundary in C-T (characters)."] [severity "CRITICAL"] [hostname
""] [uri "/restless/documentUpload/multipleUpload"]
[unique_id "W09WhX8AAAEAAAUhs7IAAABI"]

Related

When trying to invoke rest API throws httpmediatypenotsupportedexception content type 'application/x-www-form-urlencoded' not supported

I am trying to invoke rest API below which consumes a multi-part file. First paramter is a MultipartFile and second is s String. and this functions processes some business logic
#PostMapping( value="/uploadFile", consumes = MediaType.MULTIPART_FORM_DATE_VALUE)
public ResponseEntity<String> upload(#RequestPart("file") MultipartFile file,
#RequestParam("path") String path){
//businness logic
}
Invoking above API from code below. But it throws
httpmediatypenotsupportedexception content type
'application/x-www-form-urlencoded' not supported. I have also tried
added header "Content-type", MediaType.MULTIPART_FORM_DATA_VALUE OR
"Content-Type", "multipart/form-data" OR "Accept",
"multipart/form-data" in the headers below, but that has not helped
either
public void uploadFile() {
Path path = Paths.get("C:/ABC.txt");
byte[] content = null;
try{
content = Files.readAllBytes(path); // All file is read in content variable
} catch(final IOException e){
}
MultipartFile file = new MockMultipartFile("ABC.txt",content);
UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(oauthURL);
urlBuilder.queryParam("file", file);
urlBuilder.queryParam("path", "/temp);
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> response = null;
HttpEntity<?> entity = new HttpEntity<>(headers);
try{
response = restTemplate.exchange(urlBuilder.build().encode().toUri(), HttpMethod.POST, entity. String.class);
}
catch (Exception e){
}
}
}
Your server accepts (consumes) "multipart/form-data" however you are sending the file and path in the URL. This will always result in a "application/x-www-form-urlencoded".
So you need to change the server to accept it as you send them or send the file and path as the body (within the entity)
EDIT (some code to show how):
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
body.add("path","/temp");
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
response = restTemplate.postForEntity(serverUrl, requestEntity, String.class);

Spring boot RestTemplate upload file to SharePoint online but file is corrupted

There is a RestController and I try to upload a MultiPartFile to SharePointOnline using, the SharePoint REST API I'm also using proxy due to corporate restrictions.
#Override
public ResponseEntity uploadFile(MultipartFile file) throws ApiException, IOException {
RestTemplate restTemplate = createBasicRestTemplate();
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file",file.getResource());
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> uploadBodyEntity = new HttpEntity<>(body, header);
ResponseEntity<String> response = restTemplate.exchange(BASE_URL, HttpMethod.POST,
uploadBodyEntity, String.class);
return response;
}
public RestTemplate createBasicRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder(new ProxyCustomizer()).build();
return restTemplate;
}
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
return super.determineProxy(target, request, context);
}
})
.build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
The file upload is success, but it's cannot be opened. For example if upload a txt it will looks like this:
--raF_ORlUJptia2_av7ppLBeeMcGf5BUr
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
Content-Length: 159
--38dc5323d6b92b5c14c33fade0178306
Content-Disposition: form-data; name="file"; filename="test.txt"
blablalblalalal
--38dc5323d6b92b5c14c33fade0178306--
--raF_ORlUJptia2_av7ppLBeeMcGf5BUr--
If I upload an xlsx it's simply just not open, it shows 'File Format and Extension Don't Match' error.
I try to convert the MultiPartFile to simple File with this method:
public File convertFile(MultipartFile file) {
File convFile = new File(file.getOriginalFilename());
try {
convFile.createNewFile();
FileOutputStream fos = new FileOutputStream(convFile);
fos.write(file.getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
return convFile;
}
and change the controller to:
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file",convertFile(file));
But the same thing happens.
How can I upload file with RestTemplate?
This is a sample request to the SharePoint REST API and based on documentation the endpoint should receive a array buffer
POST https://{site_url}/_api/web/GetFolderByServerRelativeUrl('/Folder Name')/Files/add(url='a.txt',overwrite=true)
Authorization: "Bearer " + accessToken
Content-Length: {length of request body as integer}
X-RequestDigest: "{form_digest_value}"
"Contents of file"
This is what i can see in the https log: http log
Solution was to remove MultiValueMap and replace with:
HttpEntity<byte[]> entity = new HttpEntity<>(file.getBytes(), spoHelperService.createAuthHeader(authToken));
ResponseEntity<SpoUploadResponse> response = restTemplate.exchange(uploadFileUrl, HttpMethod.POST,
entity, SpoUploadResponse.class);

Spring Webclient multipart/form-data request

I am new to Java (Spring Boot), and i am trying to send a multipart/form-data POST request to s3 to upload a file.
I managed to do this using spring's RestTemplate like this :
public String uploadFile(byte[] file, Map<String, Object> fields, String url) throws URISyntaxException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> formData= new LinkedMultiValueMap<String, Object>();
for (Map.Entry<String, Object> entry : fields.entrySet()) {
formData.add(entry.getKey(), entry.getValue());
}
formData.add("file", file);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(formData, headers);
String response = restTemplate.postForObject(new URI(url), request, String.class);
return response;
}
Then i tried to do the same using webclient, but i can not and AWS respond with The body of your POST request is not well-formed multipart/form-data.
Here is the code using webclient :
public String uploadFileWebc(byte[] file, Map<String, Object> fields, String url) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
for (Map.Entry<String, Object> entry : fields.entrySet()) {
builder.part(entry.getKey(), entry.getValue(), MediaType.TEXT_PLAIN);
}
builder.part("file", file).filename("file");
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Void result = webClient.filter(errorHandlingFilter()).build().post().uri(url)
.contentType(MediaType.MULTIPART_FORM_DATA)
.contentLength(file.length)
.bodyValue(parts)
.retrieve()
.bodyToMono(Void.class)
.block();
return "Done Uploading.";
}
Can anybody please point out what am i missing ?
It turns out that webclient does not add the content-length header due to its streaming nature, and S3 API needs this header to be sent.
I ended up using restTemplate for uploading files to S3.

HttpClientErrorException$BadRequest: 400 : [no body] when calling restTemplate.postForObject

I am calling a POST service getOrder3 written in SpringBoot which is working fine (tested in Postman), but getting error when called via restTemplate.postForObject from another service. I tried 2 versions of the client service getOrderClient and getOrderClient2, but both are giving same error :
HttpClientErrorException$BadRequest: 400 : [no body]
Please find the details below. Any help is appreciated.
getOrder3
#PostMapping(value="/getOrder3/{month}",produces="application/json")
public ResponseEntity<OrderResponse> getOrder3(
#PathVariable("month") String month,
#RequestParam String parmRequestSource,
#RequestParam(required=false) String parmAudienceType,
#RequestBody OrderRequestForm orderRequestForm) {
OrderResponse orderResponse = new OrderResponse();
log.info("In getOrder3...parmRequestSource = " + parmRequestSource + " parmAudienceType = " + parmAudienceType);
try {
//validate JSON schema
//orderService.validateMessageAgainstJSONSchema(orderRequestForm);
//process order
orderResponse = orderService.processOrder(orderRequestForm);
orderResponse.setParmRequestSource(parmRequestSource);
orderResponse.setParmAudienceType(parmAudienceType);
orderResponse.setMonth(month);
}catch (Exception e) {
throw new OrderException("101", e.getMessage(), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(orderResponse,HttpStatus.OK);
}
The service is working fine , tested in postman
Now when I try to call via another microservice via restTemplate.postForObject, I get the error. Tried 2 versions of the client as below, getOrderClient and getOrderClient2
getOrderClient
#PostMapping(value="/getOrderClient/{month}",produces="application/json")
public OrderResponse getOrderClient(
#PathVariable("month") String month,
#RequestParam String parmRequestSource,
#RequestParam String parmAudienceType,
#RequestBody OrderRequestForm orderRequestForm) throws URISyntaxException, JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
URI uri = new URI("http://localhost:51001/orders/v1/getOrder/"+month+"?parmRequestSource="+parmRequestSource+"&parmAudienceType="+parmAudienceType);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String requestJson = new ObjectMapper().writeValueAsString(orderRequestForm);
HttpEntity<String> httpEntity = new HttpEntity<String>(requestJson,headers);
String response = restTemplate.postForObject(uri, httpEntity, String.class);
return new ObjectMapper().readValue(response, OrderResponse.class);
}
getOrderClient2
#PostMapping(value="/getOrderClient2/{month}",produces="application/json")
public OrderResponse getOrderClient2(
#PathVariable("month") String month,
#RequestParam String parmRequestSource,
#RequestParam String parmAudienceType,
#RequestBody OrderRequestForm orderRequestForm) throws URISyntaxException, JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
URI uri = new URI("http://localhost:51001/orders/v1/getOrder/"+month+"?parmRequestSource="+parmRequestSource+"&parmAudienceType="+parmAudienceType);
return restTemplate.postForObject(uri, orderRequestForm, OrderResponse.class);
}
Both are giving same error :
HttpClientErrorException$BadRequest: 400 : [no body]
Please suggest.
To improve the visibility of the solution, #astar fixed the issue by annotating the model object's properties with #JsonProperty.

Okhttp create MultipartBody with spring MultipartFile and json object

I have a controller like so that accepts a MultipartFile and json object:
#PostMapping(value = "/v1/submit")
public ResponseEntity submit(
#RequestParam(value="myFile", required = true) MultipartFile myFile
, #Valid #RequestPart(value="fileMeta", required=true) FileMeta fileMeta
){
I need to forward this to a new url using an okhttpclient post with a Multipartbody containing both myFile and fileMeta objects:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), okhttp3.RequestBody.create(file, MediaType.parse("pdf"))
.addFormDataPart("fileMeta", fileMeta)
.build();
I am getting following error:
Cannot resolve method 'create(org.springframework.web.multipart.MultipartFile, okhttp3.MediaType)'
The method definition of OkHttp's RequestBody create is the following: create(MediaType contentType, byte[] content). It expects the first the MediaType and second the payload (either as byte[], File or other formats).
So you first have to switch the order of the method arguments and second convert the MultipartFile from Spring to a proper format that the create() method accepts, e.g. byte[] or File:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), RequestBody.create(MediaType.parse("pdf"), file)
.addFormDataPart("fileMeta", fileMeta)
.build();
There are already multiple solutions available on StackOverflow to convert MultipartFile to File: How to convert a multipart file to File?
UPDATE: Example for using RestTemplate
#RestController
public class FileSendingController {
#PostMapping("/files")
public void streamFile(#RequestParam("file") MultipartFile file) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForEntity("http://upload.to", requestEntity, String.class);
}
}

Resources