I'm trying to upload file with POST request. POST must be of the enclosure-type multipart/form-data. I insert file as byte array:
MultiValueMap<String, Object> requestParameters = new LinkedMultiValueMap<String, Object>();
byte[] image=getImage();
// ... adding parameters in requestParameters
// TODO filename
requestParameters .add("file", image);
Then I make post request with
org.springframework.http.converter.FormHttpMessageConverter:
FormHttpMessageConverter multipartFormConverter = new FormHttpMessageConverter();
multipartFormTemplate = new RestTemplate();
multipartFormTemplate.setMessageConverters(Collections.<HttpMessageConverter<?>> singletonList(multipartFormConverter));
ResponseEntity<String> uploadAnswer=multipartFormTemplate.postForEntity(freePlanResponse.getUrl(), requestParameters, String.class);
The problem is - I cant found, how to change filename. The result is always default filename (${filename}) for file hosting, I'm using:
image hosting. It uses:
amazon s3. Here is post example >>
I found solution:
Inherit FormHttpMessageConverter and override getFileName method:
public class FormHttpMessageConverterImageName extends FormHttpMessageConverter {
private String filename;
public void setFilename(String filename){
this.filename=filename;
}
#Override
protected String getFilename(Object part) {
return filename;
}
}
but maybe there is some more beautifull solution.
Related
I am trying to consume a rest api to upload a Multipart file. I have set MultipartFile in UploadFIleObject and I am sending the same to rest api. However when trying to invoke a rest API as below, I am getting error No serializer found for class java.io.ByteArrayInputStream
public class UploadFileObject implements Serializable{
MultipartFile file;
//getters and setters
}
public void uploadFile() {
Path path = Paths.get("C:/Accounts.txt");
byte[] content = null;
try{
content = Files.readAllBytes(path);
} catch(final IOException e){
}
MultipartFile file = new MockMultipartFile("ABC.txt","ABC.txt","text/plain", content);
UploadFileObject obj = new UploadFileObject();
obj.setFile(file)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set(MediaType.MULTIPART_FORM_DATA);
headers.setContentType("Accept", Mediatype.APPLICATION_JSON_VALUE);
headers.setContentType("Content-type", Mediatype.APPLICATION_JSON_VALUE);
HttpEntity<?> requestEntity = new HttpEntity<>(obj, headers);
String result = getRestTemplate().postForEntity("url", requestEntity, String.class);
}
in application.yml
I have set
spring.serialization.fail-on-empty-beans: false
and in pom.xml I have below of version 2.12.3
jackson-annotation
jackson-core
jackson-databind
jackson-dataformat
jackson-dataformat-xml
}
In order to accept 'text/plain' as a media type in body messages I had to add a custom MappingJackson2HttpMessageConverter to my RestTemplate:
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> myMediaTypes = new ArrayList<>();
myMediaTypes.add(MediaType.APPLICATION_JSON);
myMediaTypes.add(MediaType.TEXT_PLAIN);
myMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
converter.setSupportedMediaTypes(myMediaTypes);
messageConverters.add(converter);
sslTemplate.setMessageConverters(messageConverters);
When I did this, I started to get errors on an API Endpoint which uploads Multipart form data to a remote API that was working prior to this implementation. I have verified that commenting out the block above will allow the endpoint to POST correct, and uncommenting out the block above breaks it. Spring suggested that I add the Jackson SerializationFeature FAIL_ON_EMPTY_BEANS=false, which I did below:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return new MappingJackson2HttpMessageConverter(mapper);
}
When making the actual POST request with my MultipartFile, I am getting errors stating that: Byte array resource [resource loaded from byte array] cannot be resolved to absolute file path; nested exception is java.io.FileNotFoundException: Byte array resource [resource loaded from byte array] cannot be resolved to absolute file path. Stepping through my code, I can verify that the file exists where it is supposed too, can be read, and the MultipartFile object returns true when calling the exists method. My code for creating and POSTing the MultiPart file is below:
private MultipartFile createMultipartFile(File file) throws IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
FileItem fileItem = factory.createItem("file", "multipart/form-data", false, file.getName());
IOUtils.copy(new FileInputStream(file.getAbsoluteFile()), fileItem.getOutputStream());
return new CommonsMultipartFile(fileItem);
}
private HttpEntity<LinkedMultiValueMap<String, Object>> createFilePOSTEntity(MultipartFile file) throws IOException {
ByteArrayResource fileAsResource = new ByteArrayResource(file.getBytes()) {
#Override
public String getFilename() {
return file.getOriginalFilename();
}
#Override
public long contentLength() {
return file.getSize();
}
};
HttpHeaders headers = new HttpHeaders();
headers.set("accept", "text/plain");
headers.set("Content-Type", "multipart/form-data");
LinkedMultiValueMap<String, Object> fileMap = new LinkedMultiValueMap<>();
fileMap.add("file", fileAsResource);
//fileMap.add("type", "text/csv");
return new HttpEntity<>(fileMap, headers);
}
restTemplate.postForObject(messageURL, createFilePOSTEntity(multipartFile), AsyncResponse.class);
I have tried a number of different ways to create the multipart file, create the HttpEntity<>, and POST. None have worked, but when I remove the new converter code to accept text/plain messages, it works without any fuss.
I use #RequestParam to get the parameter value,but I find the if I pass the value like 'name=abc&def&id=123',I will get the name value 'abc' instead of 'abc&def'. I find the encode and decode the parameter value can solve my problem.But I have to write the encode and decode mehtod in every controller method,Do spring have the global mehtod that decode every #RequestParam value?When using #RequestParam, is it necessary to encode and decode every value?
Here is my code:
#PostMapping("/getStudent")
public Student getStudent(
#RequestParam String name,
#RequestParam String id) {
name= URLDecoder.decode(name, "UTF-8");
//searchStudent
return Student;
}
#PostMapping("/getTeacher")
public teacher getTeacher(
#RequestParam String name,
#RequestParam String teacherNo) {
name= URLDecoder.decode(name, "UTF-8");
//searchTeacher
return teacher;
}
Somebody say the the Spring will have already done this,but I have try,the result is not right.Only use curl cmd is ok,but java code is not ok.
#PostMapping(value = "/example")
public String handleUrlDecode1(#RequestParam String param) {
//print ello%26test
System.out.println("/example?param received: " + param);
return "success";
}
#GetMapping(value = "/request")
public String request() {
String url = "http://127.0.0.1:8080/example?param=ello%26test";
System.out.println(url);
RestTemplate restTemplate = new RestTemplate();
return restTemplate.postForObject(url, null, String.class);
}
You must create an HTTP entity and send the headers and parameter in body.
#GetMapping(value = "/request")
public String request() {
String url = "http://127.0.0.1:8080/example";
System.out.println(url);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("param","ello&test");
map.add("id","ab&c=def");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
return restTemplate.postForObject(url, request, String.class);
}
As you can read here, the escape character for & is %26.
So you should use the following
name=abc%26def&id=123
If you don't use an escape character according to URL standards, Spring will try to use what follows & and try to match it as a new query parameter.
No need to manually use URLDecoder, SpringBoot controllers will handle it for you.
#RestController
public class UrlDecodeController {
#GetMapping(value = "/example")
public String handleUrlDecode(#RequestParam String param) {
System.out.println("/example?param received: " + param);
return "success";
}
#PostMapping(value = "/example2")
public String handleUrlDecodeInPostRequest(#RequestParam String param1, ExamplePayload payload) {
System.out.println("/example2?param1 received: " + param1);
System.out.println("request body - value1: " + payload.getValue1());
return "success";
}
#GetMapping(value = "/request")
public String request() {
String url = "http://localhost:8080/example2?param1=test1&test2";
System.out.println(url);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("value1","test1&test2");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
return restTemplate.postForObject(url, request, String.class);
}
class ExamplePayload{
private String value1;
private String value2;
//getters and setters
public ExamplePayload() {
}
}
}
Call with GET /example?param=hello%26test and the System.out.println outputs:
/example?param received: hello&test
Call the POST using curl as an example:
curl -X POST "http://localhost:8080/example2?param1=test1%26test2" -d "value1=test3%26test4"
Prints:
/example2?param1 received: test1&test2
request body - value1: test3&test4
Added GET /request to show using RestTemplate with the application/x-www-form-urlencoded Content-Type. Note that RestTemplate will automatically url encode any values passed as request parameters or in the request body. If you pass a String value of "%26" it will pass it as is, this is what you are seeing in your example. If you pass "&" it will url encode it to "%26" for you, and the Controller decodes it automatically on the other side.
I have two servers, where server A is sending files to server B. On server B i have an endpoint which recieves files of given category. This is signature of endpoint i need to send files to:
#PostMapping("/uploadMultipleFiles/{projectId}")
public List<UploadFileResponseDts> uploadMultipleFiles(#RequestParam("files") MultipartFile[] files, #RequestParam("categoryId") Long categoryId, #PathVariable("projectId") Long projectId) {
return uploadMulitpleFiles(files, categoryId, projectId);
}
Now im having trouble creating such request with RestTemplate exchange. On Server A i'm trying to send files one by one (endpoint on server B needs to accept multipart array since its used somewhere else as well). This is what i tried:
public Optional<String> uploadFile(File file, Long projectId) throws Exception {
String authToken = getAccessToken();
String projectFileUploadEndpoint = fileUploadEndpoint + SEPARATOR + projectId;
FileInputStream input = new FileInputStream(file);
byte[] bytes = IOUtils.toByteArray(input);
MultiValueMap<String, Object> parts =
new LinkedMultiValueMap<>();
parts.add("files", new ByteArrayResource(bytes));
parts.add("categoryId", 0L);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set("Authorization", authToken);
HttpEntity<MultiValueMap<String, Object>> requestEntity =
new HttpEntity<>(parts, headers);
ResponseEntity<String> response =
restTemplate.exchange(projectFileUploadEndpoint ,
HttpMethod.POST, requestEntity, String.class);
return Optional.empty();
}
this almost works, the only issue is that on server B files from #RequestParam("files") MultipartFile[] files is always null. I assume that the issue is with the way im assembling parts in on my server A, however i cant find my mistake. Could you point it out?
in your controller all is fine, the problem is about your client code.
Basically you should use FileSystemResource object instead of ByteArrayResource.
The basic motivation is about file metadata. Using FileSystemResource spring is able to rebuild the required MultipartFile info, therefore the your code may be like below
#Test
public void contextLoads()
{
File file = new File("test.txt");
FileSystemResource fileSystemResource = new FileSystemResource(file);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("categoryId", 0L);
parts.add("files", fileSystemResource);
String serviceUrl = "http://localhost:8080/uploadMultipleFiles/0";
RequestEntity<MultiValueMap<String, Object>> requestEntity = post(fromPath(serviceUrl).build().toUri())
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new ResponseErrorHandler()
{
#Override
public boolean hasError(ClientHttpResponse response) throws IOException
{
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException
{
System.out.println(response);
}
});
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.POST, requestEntity, String.class);
assertThat(200, Is.is(response.getStatusCode()));
}
I hope that this test case can help you to solve the problem
I am trying to update or create xml file if not present. Then I use code below to send the file using PUT method of a service.
public void importClusterProperties(RestManPropertyHolder propertyHolder,File file,String id) throws RestManServiceException {
testRestTemplate = new TestRestTemplate(propertyHolder.getSbusUserName(), propertyHolder.getSbusUserPassword());
String sbusUrl = utils.prepareGatewayURI(propertyHolder);
try {
HttpHeaders requestHeaders = new HttpHeaders();
List <MediaType> mediaTypeList = new ArrayList<MediaType>();
mediaTypeList.add(MediaType.APPLICATION_ATOM_XML);
requestHeaders.setAccept(mediaTypeList);
requestHeaders.setContentType(MediaType.APPLICATION_ATOM_XML);
HttpEntity<String> requestEntity = new HttpEntity<String>(requestHeaders);
// Create the HTTP PUT request,
ResponseEntity<String> response = testRestTemplate.exchange(sbusUrl + "/clusterproperty?",HttpMethod.PUT, requestEntity,String.class);
if (null != response) {
System.out.println("RESPONSE::" + response.toString());
}
} catch (RestClientException rce) {
System.out.println("REST EXCEPTION:::" + rce.getMessage());
}
}
How to pass raw xml file into RestTemplate without converting it first into a java object?
enter image description here
Convert file to byte array and send it using ByteArrayHttpMessageConverter.
RestTemplate restTemplate = new RestTemplate();
// Add ByteArrayHttpMessageConverter if not present by default.
restTemplate.getMessageConverters().add(
new ByteArrayHttpMessageConverter());
String fileName = "path + file name";
// FileUtils is from Apache Commons IO
// import org.apache.commons.io.FileUtils;
byte[] requestBody = FileUtils.readFileToByteArray(new File(fileName));
HttpEntity<byte[]> requestEntity = new HttpEntity<byte[]>(requestBody , requestHeaders);
// Create the HTTP PUT request,
ResponseEntity<byte[]> response =
restTemplate.exchange("URL ...." , HttpMethod.PUT ,
requestEntity , byte[].class);