With Spring Boot 2.3 I was using the following Kotlin code
val mvcResultImage = this.mockMvc!!.perform(MockMvcRequestBuilders.multipart("/somepath)
.file("files[]", imageFile.getBytes())
.characterEncoding("UTF-8"))
.andReturn()
in an integration test for a controller with a function
#PostMapping(path = ["/somepath"],
consumes = [MediaType.MULTIPART_FORM_DATA_VALUE],
produces = [MediaType.APPLICATION_JSON_VALUE])
#ResponseBody
fun createFromBytes(#RequestParam("files[]") file: MultipartFile): ResponseEntity<Any> {
...
}
In 2.3 I was able to handle the request in the controller function whereas in 2.4 the controller function raises a org.springframework.web.multipart.support.MissingServletRequestPartException with the message Required request part 'files[]' is not present and causes HTTP response code 400.
I don't find anything in the migration guide and list of handled issues for this version change.
A rename to file in both controller and request doesn't help, I don't remember why I added [] in the code working with 2.3, but I think it was necessary to make it work.
I'm using Spring Boot through the maven parent mechanism with spring-boot-starter-parent:2.4.1.
This is a known issues in Spring Boot coming from Spring. It's fixed in Spring Boot 2.4.2. The linked issue contains a successfully tested workaround in case you're stuck with 2.4.1: Create MockMultipartFile with MockMultipartFile( String name, #Nullable String originalFilename, #Nullable String contentType, #Nullable byte[] content) (specification of originalFilename matters).
Related
I am new to swagger documentation etc
Please, could you share any good resource or steps for creating an open api spec for the following endpoint, which is an endpoint of a spring boot microservice:
#PostMapping(path = "/pdf", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<ByteArrayResource> createReport(#RequestParam MultipartFile template, #RequestParam MultipartFile templateDataAsJson) throws IOException {
log.info("Triggering PDF Generation and Download");
log.info("Step 1 Starts : Sending Json data to the template data binder microservice: Request:{}", templateDataAsJson);
String completedHtmlJson = restClient.populateTemplate(template, templateDataAsJson);
log.info("Steps 2 Starts: Sending populated html template to html-to-pdf microservice for rendering:{}", completedHtmlJson);
ResponseEntity<ByteArrayResource> response = restClient.html2PdfGeneration(completedHtmlJson);
return ResponseEntity.ok().contentType(APPLICATION_PDF).body(response.getBody());
}
Any help or references will be appreciated.
Thanks all.
You can look at SpringDoc
https://github.com/springdoc/springdoc-openapi
It will generate the documentation on the fly for for you.
I have created a Spring Boot 2 demo application with the Spring Initializr and added the controller below:
#Controller
#RequestMapping("/demo")
public class UploadController {
private final static Logger LOG = LoggerFactory.getLogger(UploadController.class);
#PostMapping("/upload")
public ResponseEntity<String> uploadFile(
#RequestParam("metadata") MultipartFile metadata,
#RequestParam("payload") MultipartFile payload) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map metadataMap = mapper.readValue(metadata.getInputStream(), Map.class);
LOG.info("Received call to upload file {}", metadataMap.get("filename"));
LOG.info("File size: {}", payload.getBytes().length);
LOG.info("File {} successfully uploaded", metadataMap.get("filename"));
return ResponseEntity.ok().build();
}
}
I then added an application.yaml file containing this configuration:
spring:
servlet:
multipart:
max-file-size: 2000000MB
max-request-size: 2000000MB
resolve-lazily: true
My goal is to have the controller parse and log the metadata file before it starts reading the payload file, but the resolve-lazily setting seems to be ignored by Boot: the code inside the controller won't be executed until the whole body is read.
I use the command below to test the controller:
curl -F metadata=#metadata.json -F payload=#payload.bin http://localhost:8080/demo/upload
Is there anything wrong with my code/configuration? Am I getting the meaning of the setting right?
At present, if you want to avoid reading (and buffering) the whole body all at once, I think you will have to provide your own parser, as described in the answers here. What would be really interesting (but generally unnecessary) would be to do so in the form of a new MultipartResolver implementation.
There are two existing implementations documented for interface MultipartResolver, and both supply a function setResolveLazily(boolean) (standard), (commons). I have tried with both, and neither seem to allow for parsing or streaming multipart files or parameters independently.
Default is "false", resolving the multipart elements immediately, throwing corresponding exceptions at the time of the resolveMultipart(javax.servlet.http.HttpServletRequest) call. Switch this to "true" for lazy multipart parsing, throwing parse exceptions once the application attempts to obtain multipart files or parameters.
Despite what it says in the documentation, I have found that once you call resolveMultipart, the entire body is parsed and buffered before the call returns. I know this because I can watch the temp-files being created.
One note about "Is there anything wrong with my code"...
Answer: Yes, because by using #RequestParam you have indirectly asked Spring to resolve your parameters ahead of time, before your controller is ever called. What you should be able to do instead (if the documentation were correct) is request the parameters independently from inside your controller:
Configuration (application.properties):
spring.servlet.multipart.enabled = true
spring.servlet.multipart.resolve-lazily = true
Controller:
#PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> postUpload(HttpServletRequest rawRequest) {
multipartResolver.setResolveLazily(true); // unclear why this is exists
MultipartHttpServletRequest request = multipartResolver.resolveMultipart(rawRequest);
String p1 = request.getParameter("first-parameter");
String p2 = request.getParameter("second-parameter");
System.out.println("first-parameter="+p1+", second-parameter"+p2);
multipartResolver.cleanupMultipart(request);
return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
}
One useful aspect of resolve-lazily that I have discovered is that it allows you to write your own parser for some rest controllers while using the built-in parser for others (see my answer here). In other words, you don't have to use spring.servlet.multipart.enabled = false to get your parser to work. This is a minor breakthrough relative to other advice that I had seen previously.
I'm trying to use the new reactive web-mvc implementation in a spring boot 2.0 application. I'm trying to define a method which consume multipart file but do not succeed at making it working :( - I always get a 415 error.
On one hand I have a controller containing the following request mapping :
#RequestMapping(method = RequestMethod.POST, path = "/myPath/{param}/{param2}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public Mono<Void> postFile(
#RequestBody MultipartFile data,
#PathVariable("param") String param,
#PathVariable("param2") String param2,
#RequestHeader(name = HEADER_DATE, required = false) #DateTimeFormat(pattern = DATE_FORMAT) Instant instant
){
return fileService.handleData(Mono.just(data), param, param2, instant);
}
On the other hand I had to add a server on the top of the basic dependencies as it seems netty do not handle multipart files. I so added the spring-boot-starter-tomcatdependency which enabled the MultipartAutoConfiguration to be matched and satisfied on application auto configuration.
When posting something using a curl call :
curl 'Meta-Date: 20170101104532' --form "file=#file.bin" http://localhost:8082/myPath/foo/bar
while debug logs are activated (logging.level.org.springframework.web=DEBUG) I got this exception :
org.springframework.web.server.UnsupportedMediaTypeStatusException: Request failure [status: 415, reason: "Content type 'multipart/form-data;boundary=------------------------58fa43b8f1a26de4' not supported"]
This error is thrown by the RequestBodyArgumentResolver which has the the following supported media types : [*/*, text/xml, application/*+json;charset=UTF-8, application/xml, text/plain;charset=UTF-8, application/x-www-form-urlencoded, application/json;charset=UTF-8] provided by 9 DecoderHttpMessageReader.
Before posting I also took a look at :
Spring MultiPart MediaType Unsupported which seems to not be relevant here as my autoconf report contains the following entry : MultipartAutoConfiguration#multipartResolver matched
set content-type to utf-8 with angularjs $http Adding a header setting Content-Transfer-Encoding: binary didn't changed anything.
My understanding is that Spring web 5.0 uses a new request decoder system as I don't find these classes on a spring 4 spring boot application, and there is not yet any DecoderHttpMessageReader dealing with multipart file
Did I miss something ? Or should I wait one to be implemented ?
Okay, It seems this is just not implemented for now as it currently exists a pull request for this feature : Add reactive multipart request support #1201
Should have check this earlier...
[EDIT] : The issue has been solved and merged into Spring master branch. Should no longer be an issue.
#PutMapping(value="/{..}",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> save(#RequestPart("file") FilePart multipartFormData,#RequestParam("fileName") String fileName,#PathVariable("..") String ..) throws IOException {
List<ByteBuffer> bytesList = new LinkedList<>();
multipartFormData.content().
subscribe(item->bytesList.add(item.asByteBuffer()));
int totalBytes = bytesList.stream().mapToInt(item->item.capacity()).sum();
ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
bytesList.stream().forEach(byteBuff->buffer.put(byteBuff));
baseImageHandler.saveImage(buffer, fileName, baseItemId);
return Mono.empty();
}
Please note that it is a dev verison, but this is how I have managed to do it.
I have implemented DeferredResult in spring MVC. It returns the right response in Tomcat8 but when I deployed into weblogic 12.1.3 gives me 404 error. I tried to debug to find out what is going on then at some point handler is looking for view in web-inf directory. I am confused here.
Could you please help me to understand?
I am using Spring
Java 7
Spring 4.2.0.RELEASE
Spring OAuth2
Weblogic 12.1.3
#RequestMapping(value = "/file/{id}")
#ResponseBody
public DeferredResult<ResponseEntity<Resource>> file(#PathVariable String id) {
DeferredResult<ResponseEntity<Resource>> result = new DeferredResult<>();
try {
final ImageObject image = null;
final Resource fileResource = new FileSystemResource(image.getImagePath().replace("E:", "C:"));
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(fileResource.contentLength());
result.setResult(new ResponseEntity<>(fileResource, headers, HttpStatus.OK));
} catch (Exception e) {
}
return result;
}
Thanks in advance.
In my case Spring wrongly concatenated a #RequestMapping path of #RestController and #RequestMapping path of a method. #RequestMapping path of #RestController was duplicated in a resulting URI in Spring logs.
The only workaround I've found is to create #RestControllers for each required DeferredResult method without specifying a #RequestMapping path in the methods.
WebLogic 12.2.1.3.0, Spring 4.3.23.
I am using 1.3.3.RELEASE version of Spring Boot.
I am trying to unit test my service which uses RestTemplate to call a url for fetching html page.
This is the code
Mockito.when(restTemplate.exchange(
Mockito.anyString(),
Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class),
Mockito.any(Class.class)))
.thenReturn(new ResponseEntity<String>("",
new HttpHeaders(), HttpStatus.OK));
But thenReturn part is having issues. What could be the right construction of ResponseEntity for getting an html page.
Appreciate any help.
Thanks
you need to mock out responseEntity
so:
ResponseEntity<String> mockResponse = mock(ResponseEntity.class);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
then like you did before:
when(mockRestTemplate.exchange(anyString(), anyObject(), anyObject(), eq(String.class)))
.thenReturn(mockResponse);