We are using spring controllers to handle file uploads:
For example:
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
But we are not using any multipart resolver, we are streaming the files from the servlet request input stream. We need to start processing the file immediately for performance reasons.
When doing this this way, we can't seem to use the typical detection/configuration for multipart files. I know Springfox (which we use to generate our swagger docs) will generate the appropriate swagger controls if it sees a MultipartFile as a controller parameter, which will not be the case for us.
Are there any other config options available to hint to springfox that we want a file upload here?
Regarding breaking changes in Springfox v2.7.0:
You need to use dataType = "__file" instead of file as commented in https://github.com/springfox/springfox/issues/1285
Found my answer here: https://github.com/springfox/springfox/issues/1285
The following implicit params give me what I need:
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "file", name = "file", required = true,paramType = "form")}
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
This adds a simple file picker to the API. To make things more confusing, turns out this functionality was broken in Springfox 2.4 - the version I was using. Adding that annotation and updating versions was all I needed to do.
That's right
https://stackoverflow.com/a/44385675/3810914
In Controller It should be:
#ApiOperation(value = "Upload file", response = String.class)
#ApiResponses({
#ApiResponse(code = 500, message = "Internal Server Error"),
#ApiResponse(code = 400, message = "Bad request")
})
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "__file", name = "fileData", required = true,paramType = "form")})
#PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<?> uploadFileSimple(UploadFile form) {
// Create folder to save file if not exist
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
MultipartFile fileData = form.getFileData();
String name = fileData.getOriginalFilename();
if (name != null && name.length() > 0) {
try {
// Create file
File serverFile = new File(UPLOAD_DIR + "/" + name);
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(serverFile));
stream.write(fileData.getBytes());
stream.close();
return ResponseEntity.ok("/file/" + name);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error when uploading");
}
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Bad request");
}
And In Model:
package com.xxx.xxx.request;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UploadFile {
private MultipartFile fileData;
}
Related
I'm new to MockMVC. I've successfully written some basic tests, but I got stuck on trying to test an use case with the endpoint that requires a POST request with two parameters - a POJO and an array of MultipartFile. The test is written as such:
#Test
public void vytvorPodnetTest() throws Exception {
var somePojo = new SomePojo();
somePojo.setSomeVariable("test_value");
var roles = List.of("TEST_USER");
var uid = "00000000-0000-0000-0000-000000000001";
MockMultipartFile[] attachments = {new MockMultipartFile("file1.txt", "file1.txt", "text/plain", "file1 content".getBytes()),
new MockMultipartFile("file2.txt", "file2.txt", "text/plain", "file2 content".getBytes())};
MockMultipartHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart("/some-pojo/create");
builder.with(req - {
req.setMethod("POST");
return req;
});
MvcResult result = mockMvc.perform(builder.file(attachments[0]).file(attachments[1])
.param("SomePojo", new ObjectMapper().writeValueAsString(somePojo))
.file(attachment[0])
.with(TestUtils.generateJWTToken(uid, roles)))
.andExpect(status.isOk())
.andReturn();
}
The controller method is as follows:
#PostMapping(value = "/create", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public UUID createPojo(
#RequestPart(value = "SomePojo") SomePojo somePojo,
#RequestPart(value = "attachments", required = false) MultipartFile[] attachments) {
return pojoService.create(somePojo, attachments);
}
It stops here, before reaching the service. I've tried adding the files both as a param "attachments" and like shown above, but all I get is "400 Bad Request"
Finally found the way to send the parameters as MockMultipartFile from MockMVC to the controller:
MockMultipartFile pojoJson = new MockMultipartFile("SomePojo", null,
"application/json", JsonUtils.toJSON(podnet).getBytes());
mockMvc.perform(MockMvcRequestBuilders.multipart("/some-pojo/create")
.file(pojoJson)
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.with(new TestUtils().generateJWTToken(uid, roles)))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
I am trying to use swagger with java.
Using NSwag studio I am able to generate all my endpoints except one that returns a list of objects.
Here is my action in controller:
#ApiOperation(value = "getAll", nickname = "getAll", responseContainer = "List", response = DiakEntity.class)
#GetMapping("/api/diakok")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<DiakEntity> GetDiakok() throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return request.getObject();
}
I am using swagger-annotations 1.5.23, springfox-swagger-ui 2.9.2, springfox-swagger2 2.9.2.
If I test from Postman it works.
Also tried like this:
#ApiOperation(value = "getAll", nickname = "getAll")
#ApiResponse(code = 200, responseContainer="List", response=DiakEntity.class, message = "Gets all diak objects")
#GetMapping("/api/diakok")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public ResponseEntity<List<DiakEntity>> GetDiakok() throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return new ResponseEntity<>(request.getObject(), HttpStatus.OK);
}
thnx
Please try with the following annotation for swagger.
#ApiOperation(value = "getAll", nickname = "getAll")
#ApiResponse(code = 200, responseContainer="List", response=DiakEntity.class)
At the end I changed my action as below, and it started to work
#ApiOperation(value = "all", nickname = "all")
#PostMapping("/api/diak/all")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<DiakEntity> GetAll(#RequestBody #Valid RequestDiakByName data) throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return request.getObject();
}
Hello i have the following jaxrs entry
#PUT()
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_OCTET_STREAM)
#ApiOperation(value = "Bla bla.")
#Path("secure/flappy")
public Response testput(
#ApiParam(value = "pwet",type = "file",format = "binary", required = true) InputStream certificate) throws Throwable {
try (InputStream stream = certificate) {
//Consume stream
return Response.ok().build();
}
}
And the generated swagger-ui page for this entry
I would like to know how to document my paremeter for getting only one parameter presented as a file chooser in swagger-ui.
#sdc: You're right i had to use multi part form data for getting a working file chooser in Swagger-ui. I also had to use #ApiImplicitParam for getting it working.
#PUT()
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
#ApiOperation(value = "Bla bla.")
#Path("secure/flappy")
#ApiImplicitParams({
#ApiImplicitParam(name = "file", value = "bla bla.", required = true, dataType = "java.io.File", paramType = "form")
})
public Response testput(#ApiParam(hidden = true) #FormDataParam("file") final InputStream certificate
) throws Throwable {
//TODO do something
return Response.ok().build();
}
I am not very familiar with Swagger UI, but this thread might be helpful
https://github.com/swagger-api/swagger-ui/issues/72
I see an example using an#ApiParam annotation
and this post talks about a file upload with the
def uploadFile annotation
https://github.com/swagger-api/swagger-ui/issues/169
#PUT
#Path("/secure/flappy")
#Consumes(Array(MediaType.MULTIPART_FORM_DATA))
#ApiOperation(value = "test a put file upload")
def uploadFile(
#ApiParam(value = "file to upload", required=false) #FormDataParam("file") inputStream: InputStream,
#ApiParam(value = "file detail") #FormDataParam("file") fileDetail: FormDataContentDisposition) = {
val uploadedFileLocation = "./" + fileDetail.getFileName
IOUtils.copy(inputStream, new FileOutputStream(uploadedFileLocation))
val msg = "File uploaded to " + uploadedFileLocation + ", " + (new java.io.File(uploadedFileLocation)).length + " bytes"
val output = new com.wordnik.swagger.sample.model.ApiResponse(200, msg)
Response.status(200).entity(output).build()
}
I suppose you need to use in the parameter description following:
#RequestPart("file") MultipartFile file
At least for me it gives a swagger form with a file selection button.
The solution was found in the examples here:
https://github.com/springfox/springfox-demos
I have an rest API in a Spring for generating and downloading a PDF file. The controller definitation is as follows -
#RequestMapping(
value = "/foo/bar/pdf",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
#ResponseBody
#Nullable
public ByteArrayResource downloadPdf(#RequestParam int userId) {
byte[] result = null;
ByteArrayResource byteArrayResource = null;
result = service.generatePdf(userId);
if (result != null) {
byteArrayResource = new ByteArrayResource(result);
}
return byteArrayResource;
}
I use Jackson for JSON handling JSON and have an Exception handler ControllerAdvice. The problem is when this API generates an exception and I return a custom exception class (contains message and one additional field).
As I already specified produces = MediaType.APPLICATION_OCTET_STREAM_VALUE this custom class is also attempted to be converted to an octet stream by Spring, which it fails at and produces HttpMediaTypeNotAcceptableException: Could not find acceptable representation.
I tried solutions on this Stackoverflow question, particularly this answer but it still fails. This solution, along with other changes suggests removing produces part from #RequestMapping but when I debugged into AbstractMessageConverterMethodProcessor.getProducibleMediaTypes it only detects application/json as available response media type.
tl;dr
How can I have this API return the file on success and correctly return custom exception class's JSON representation on error.
I had the same problem with similar code. I just removed the produces attribute from my #PostMapping and I was able to return the file or the json (when the api have some error):
#Override
#PostMapping
public ResponseEntity<InputStreamResource> generate(
#PathVariable long id
) {
Result result = service.find(id);
return ResponseEntity
.ok()
.cacheControl(CacheControl.noCache())
.contentLength(result.getSize())
.contentType(MediaType.parseMediaType(MediaType.APPLICATION_PDF_VALUE))
.body(new InputStreamResource(result.getFile()));
}
When some error occur, I had a #ExceptionHandler to care of that:
#ExceptionHandler
public ResponseEntity<ApiErrorResponse> handleApiException(ApiException ex) {
ApiErrorResponse error = new ApiErrorResponse(ex);
return new ResponseEntity<>(error, ex.getHttpStatus());
}
Try implements your action as
#RequestMapping(
value = "/foo/bar/pdf",
method = RequestMethod.GET)
#ResponseBody
public HttpEntity<byte[]> downloadPdf(#RequestParam int userId) {
byte[] result = service.generatePdf(userId);
HttpHeaders headers = new HttpHeaders();
if (result != null) {
headers.setContentType(new MediaType("application", "pdf"));
headers.set("Content-Disposition", "inline; filename=export.pdf");
headers.setContentLength(result.length);
return new HttpEntity(result, headers);
}
return new HttpEntity<>(header)
}
About exception handling for example you may throw YourCustomError and in controller annotated with #ControllerAdvice annotate a method with #ExceptionHandler(YourCustomError.class) and work with it.
I have the following controller method:
#RequestMapping(value = { "/member/uploadExternalImage",
"/member/uploadExternalImage" }, method = RequestMethod.GET)
public String handleFileUpload(#RequestParam String url, RedirectAttributes redirectAttributes
) throws IOException {
byte[] binaryFile = IOUtils.toByteArray(
new URL(url)
.openStream());
File file = File.createTempFile("tmp", ".txt", new File(System.getProperty("user.dir")));
FileUtils.writeByteArrayToFile(file, binaryFile);
redirectAttributes.addFlashAttribute(file);
return "redirect:/member/uploadImage";
}
Here I get external link, download file by this link and redirect it to the another controller:
It looks like this:
#RequestMapping(value = { "/member/createCompany/uploadImage",
"/member/uploadImage" })
#ResponseBody
public ResponseEntity<String> handleFileUpload(#Validated MultipartFileWrapper file,
BindingResult result, Principal principal) throws IOException {
MultipartFileWrapper:
#Component
public class MultipartFileWrapper {
#Extensions(imageFormats = {".jpg",".png",".gif",".bmp"}, videoFormats = {".mp4",".mov"})
MultipartFile multipartFile;
...
}
But redirect doesn't happen properly. It breaks on validation. Accepted multipartFile is null.
How to fix it ?
P.S.
I tryed this
File file = File.createTempFile("tmp", ".jpg", new File(System.getProperty("user.dir")));
FileUtils.writeByteArrayToFile(file, binaryFile);
FileItem fileItem = new DiskFileItem("trololo", ".jpg", false, "fileName", 1024_000_0, file);
fileItem.getOutputStream();
fileItem.getInputStream();
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
MultipartFileWrapper wrapper = new MultipartFileWrapper();
wrapper.setMultipartFile(multipartFile);
redirectAttributes.addFlashAttribute(wrapper);
return "redirect:/member/uploadImage";
it redirects correctly but size equals 0
You add a File object as a flash attribute. So you will get it in the Model for the redirected request. But I cannot imagine how you could get it in a MultipartFile which is for uploaded files. IMHO your second controller should be:
#RequestMapping(value = { "/member/createCompany/uploadImage",
"/member/uploadImage" })
#ResponseBody
public ResponseEntity<String> handleFileUpload(Model model, Principal principal) throws IOException {
File file = (File) model.getAttribute("file");
...
As shown the code snippet below, just read the file from input stream and write it into the output stream,
final File TEST_FILE = new File("C:/Users/arrows.gif");
final DiskFileItem diskFileItem = new DiskFileItem("file", "image/jpeg", true, TEST_FILE.getName(), 100000000, TEST_FILE.getParentFile());
InputStream input = new FileInputStream(TEST_FILE);
OutputStream os = diskFileItem.getOutputStream();
int ret = input.read();
while ( ret != -1 )
{
os.write(ret);
ret = input.read();
}
os.flush();
MultipartFile multipartFile = new CommonsMultipartFile(diskFileItem);
redirectAttributes.addFlashAttribute("multipartFile", multipartFile);
return "redirect:request2";
In the "request2" mapping method, just get it from model map,
I hope it should resolve the issue.
I believe the multipartFile is null because it does not exist in the request. You add a file attribute to the redirect attributes, but that is not going to be bound to the MultipartFileWrapper.
Try wrapping your file in a CommonsMultipartFile or MockMultipartFile before redirecting. This was clearly a bad advice since this is no multiform request, and no binding will take place.
The best thing to do would be to handle the file content directly or create a separate method where you handle the file content either it comes from external download or user upload. Then you can add the file as a flash attribute and redirect to this method from both your handleFileUpload methods.
In the common method you will have to pick up the file instance from the model. (like described by Serge Ballesta)