java - make - Cómo enviar datos de formulario multiparte con restTemplate Spring-mvc
spring boot make a post request (2)
Estoy intentando subir un archivo con RestTemplate a Raspberry Pi con Jetty. En Pi hay un servlet corriendo:
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter outp = resp.getWriter();
StringBuffer buff = new StringBuffer();
File file1 = (File) req.getAttribute("userfile1");
String p = req.getParameter("path");
boolean success = false;
if (file1 == null || !file1.exists()) {
buff.append("File does not exist/n");
} else if (file1.isDirectory()) {
buff.append("File is a directory/n");
} else {
File outputFile = new File(req.getParameter("userfile1"));
if(isValidPath(p)){
p = DRIVE_ROOT + p;
final File finalDest = new File(p
+ outputFile.getName());
success = false;
try {
copyFileUsingFileChannels(file1, finalDest);
finalDest.setWritable(true);
success = true;
} catch (Exception e) {
e.printStackTrace();
}
if (success){
buff.append("File successfully uploaded./n");
}
else{
buff.append("Failed to save file.");
}
}
else{
buff.append("Invalid path./n");
}
}
outp.write(buff.toString());
}
Soy capaz de hacerlo con éxito con curl
curl --form userfile1=@/home/pi/src/CreateNewFolderServlet.java --form press=OK localhost:2222/pi/GetFileServlet?path="/media/"
Este es el método que se supone que tiene la misma funcionalidad en la aplicación web.
@ResponseBody
@RequestMapping(value="/upload/",method=RequestMethod.POST ,produces = "text/plain")
public String uploadFile(MultipartHttpServletRequest request2, HttpServletResponse response2){
Iterator<String> itr = request2.getFileNames();
MultipartFile file = request2.getFile(itr.next());
System.out.println(file.getOriginalFilename() +" uploaded!");
System.out.println(file.toString());
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("userfile1",file);
//reqEntity.addPart("userfile1", file);
String path="/public/";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
System.out.println("1");
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(parts, headers);
String url = url2+"/pi/GetFileServlet?path="+path;
System.out.println("2");
/* restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());*/
System.out.println("3");
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request,String.class);
System.out.println("4");
System.out.println("response : " +response);
if(response==null||response.getBody().trim()==""){
return "error";
}
return response.getBody();
}
Esta es la salida que obtengo:
ui-elements.html subido!
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@47e7673e
1
2
3
Como puede ver, el número 4 no está impreso. No hay excepción en la consola. Excepciones encontradas durante la depuración:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"])
Está recibiendo la excepción porque ninguno de los MessageConverters predeterminados de RestTemplate sabe cómo serializar el InputStream que contiene el archivo MultipartFile. Al enviar objetos a través de RestTemplate, en la mayoría de los casos desea enviar POJOs. Puede solucionar este problema agregando los bytes del MultipartFile al MultiValueMap en lugar del mismo MultipartFile.
Creo que también hay algo mal con su parte servlet. Por ejemplo
File file1 = (File) req.getAttribute("userfile1");
Siempre debe devolver un valor nulo, ya que el método getAttribute de ServletRequest no devuelve los parámetros de solicitud / formulario sino los atributos establecidos por el contexto del servlet . ¿Estás seguro de que realmente está funcionando con tu ejemplo de rizo?
Este es un ejemplo de un método Spring MVC que reenvía un archivo a un servlet:
Servlet (aunque lo probé corriendo en un contenedor Spring MVC), adaptado desde here :
@RequestMapping("/pi")
private void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
final String path = request.getParameter("destination");
final Part filePart = request.getPart("file");
final String fileName = request.getParameter("filename");
OutputStream out = null;
InputStream fileContent = null;
final PrintWriter writer = response.getWriter();
try {
out = new FileOutputStream(new File(path + File.separator
+ fileName));
fileContent = filePart.getInputStream();
int read = 0;
final byte[] bytes = new byte[1024];
while ((read = fileContent.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
writer.println("New file " + fileName + " created at " + path);
} catch (FileNotFoundException fne) {
writer.println("You either did not specify a file to upload or are "
+ "trying to upload a file to a protected or nonexistent "
+ "location.");
writer.println("<br/> ERROR: " + fne.getMessage());
} finally {
if (out != null) {
out.close();
}
if (fileContent != null) {
fileContent.close();
}
if (writer != null) {
writer.close();
}
}
}
Método MVC de primavera:
@ResponseBody
@RequestMapping(value="/upload/", method=RequestMethod.POST,
produces = "text/plain")
public String uploadFile(MultipartHttpServletRequest request)
throws IOException {
Iterator<String> itr = request.getFileNames();
MultipartFile file = request.getFile(itr.next());
MultiValueMap<String, Object> parts =
new LinkedMultiValueMap<String, Object>();
parts.add("file", new ByteArrayResource(file.getBytes()));
parts.add("filename", file.getOriginalFilename());
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity =
new HttpEntity<MultiValueMap<String, Object>>(parts, headers);
// file upload path on destination server
parts.add("destination", "./");
ResponseEntity<String> response =
restTemplate.exchange("http://localhost:8080/pi",
HttpMethod.POST, requestEntity, String.class);
if (response != null && !response.getBody().trim().equals("")) {
return response.getBody();
}
return "error";
}
Usando estos puedo subir exitosamente un archivo a través del método MVC al servlet mediante el siguiente bucle:
curl --form [email protected] localhost:8080/upload/
Leer todo el archivo en un ByteArrayResource
puede ser un problema de consumo de memoria con archivos grandes.
Puede hacer un proxy de carga de archivos en un controlador mvc spring usando un InputStreamResource
:
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResponseEntity<?> uploadImages(@RequestPart("images") final MultipartFile[] files) throws IOException {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
String response;
HttpStatus httpStatus = HttpStatus.CREATED;
try {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
map.add("images", new MultipartInputStreamFileResource(file.getInputStream(), file.getOriginalFilename()));
}
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
String url = "http://example.com/upload";
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
response = restTemplate.postForObject(url, requestEntity, String.class);
} catch (HttpStatusCodeException e) {
httpStatus = HttpStatus.valueOf(e.getStatusCode().value());
response = e.getResponseBodyAsString();
} catch (Exception e) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
response = e.getMessage();
}
return new ResponseEntity<>(response, httpStatus);
}
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 ...
}
}