Why SpringDoc OpenAPI doesn't understand MultipartFile payload? - java

Friends,
I am working on a Spring boot application which has a controller to help upload Multipart files.
#PostMapping("/files")
public ResponseEntity<?> uploadFiles(#RequestParam("file") MultipartFile[] file, String comment) throws IOException, ExecutionException, InterruptedException {
log.debug("Total files to store: {}", file.length);
log.debug("comment: {}", comment);
fileService.storeFile(Arrays.asList(file), comment);
return ResponseEntity.ok(environment.getProperty("file.upload.success"));
}
Problem: Somehow OpenDocAPI (swagger) doesn't understand this payload as file. It shows this field as "string" if I mention #RequestParam("file") MultipartFile file, or "string[ ]" if I use array of MultipartFiles.
My Spring boot parent:
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
The spring doc openapi dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
The swagger page:
The result page when I click on "Try it out"
The "Execute" Button doesn't work
Any idea what am I missing in the controller?
PS - I tried with mentioning #PostMapping(value = "/files", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}), still openapi would only treat it as string.

Related

Hide HttpServletRequest request in Swagger OpenAPI 3

I am currently using Spring Boot 3.0.2 with Swagger OpenAPI 3. But the SwaggerUI keeps marking a parameter in my controller as a required request parameter.
In my pom.xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.14</version>
</dependency>
In my RestController.java
#GetMapping("/endpoint")
public ResponseEntity<Object> Hello(HttpServletRequest request,
#RequestParam String paramOne){}
In my swagger UI, there are two required parameters: paramOne and request (which I don't want to be a part of). How can I hide or mark it as not a URL parameter?
Mark the HttpServletRequest parameter with #Parameter(hidden = true). Your code should look like this:
#GetMapping("/endpoint")
public ResponseEntity<Object> Hello(#Parameter(hidden = true) HttpServletRequest request,
#RequestParam String paramOne){}
This will hide the request parameter in Swagger UI and it will not be visible.

Spring Boot file upload doesn't work (bad request)

I'm trying to do a simple file upload via a Rest API created with Spring Boot and Kotlin. This is my code:
#RestController
#RequestMapping("/api")
class Controller {
#PostMapping("/upload")
fun handleFileUpload(#RequestParam("file") file: MultipartFile): ResponseEntity<String> {
try {
file.transferTo(File("C:\\upload\\" + file.originalFilename))
}
catch (e: Exception) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()
}
return ResponseEntity.ok("File uploaded successfully.")
}
}
When I use Postman to test it, I get the status "400 Bad Request".
I'm using a Post-Request with the URL http://localhost:8080/api/upload. In the Header Section I left everything how it is (I read somewhere that the Content-Type Header sometimes causes trouble and therefore turned it off temporarily but this didn't help). In the Body section I chose "form-data", added a key called "file" and selected my test-file as a value.
What am I doing wrong?
Try to check your configuration, in particular if you have in the application.yml or application.properties the following:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=${java.io.tmpdir}
And also in your pom.xml or build.gradle the dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.1</version>
</dependency>
You can get some inspiration from this tutorial: https://www.baeldung.com/spring-file-upload

Spring Boot File Upload - part is null after upgrade to Spring Boot 2.4

I use multipart file upload with upload requests similar to the following curl variant:
curl --request POST 'http://localhost:8080/api/upload' --form 'csv=#"/path/file.csv"'
The form data looks the following when viewed in browser:
------WebKitFormBoundarysT6cYRLsXHQ8EWtZ
Content-Disposition: form-data; name="csv"; filename="file.csv"
Content-Type: text/csv
------WebKitFormBoundarysT6cYRLsXHQ8EWtZ--
My Java code looks like this:
#PostMapping(value = "/api/upload")
public ResponseEntity<String> upload(#RequestBody MultipartFile csv) {
System.out.println(new String(csv.getBytes(), StandardCharsets.UTF_8));
return ResponseEntity.ok().body(null);
}
It used to work fine with Spring Boot 2.0 but once I upgraded to Spring Boot 2.4/2.5, the csv parameter is always null so csv.getBytes() throws NullPointerException.
What I've tried so far:
Changing #RequestBody to #RequestPart("csv") or #RequestParam("csv") makes the upload request return HTTP 400 code with the message "Required request part 'csv' is not present"
Adding consumes = MediaType.MULTIPART_FORM_DATA_VALUE to #PostMapping makes no difference.
I've found out that by default Spring Boot uses StandardServletMultipartResolver because MultipartAutoConfiguration is enabled, it can be seen here - https://github.com/spring-projects/spring-boot/blob/v2.4.13/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.java
I've found a workaround - adding CommonsMultipartResolver as a default resolver instead of StandardServletMultipartResolver solves the issue. This can be done by adding a new dependency and a corresponding bean:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
However Spring discourages using CommonsMultipartResolver as can be seen at https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.spring-mvc.multipart-file-uploads
It is recommended to use the container’s built-in support for
multipart uploads rather than introducing an additional dependency
such as Apache Commons File Upload.
That's why I'd like to use the built-in support. Any ideas how to solve it?
Adding filter before authentication filter fixed issue for me. Works with StandardServletMultipartResolver
public class HttpRequestPartsInitFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {
if (request.getParts().isEmpty()) {
log.info("Request.getParts() is empty for={}", request.getPathInfo());
} else {
log.info("Request.getParts() is NOT empty for={}", request.getPathInfo());
}
}
filterChain.doFilter(request, response);
}
}
I've registered filter in security #Configuration class that extends WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(new OktaErrorFilter(LOGIN_URL, OKTA_ERROR_URL),
OAuth2AuthorizationRequestRedirectFilter.class)
.addFilterBefore(new HttpRequestPartsInitFilter(), OktaErrorFilter.class)
.... }
I din't find explanation why this made it work.
Similar fix mentioned here:
Spring POST multipart/form-data, request parts always empty

Cannot send http post request to springboot

When I send an HTTP post request to spring boot rest API from my angular application, request is failing with below error
Browser Error
HTTP Status 415 – Unsupported Media Type
Type Status Report
Description: The origin server is refusing to service the request because the payload is in a format
not supported by this method on the target resource
Spring boot console error
java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.LinkedHashMap
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187) ~[spring-webmvc-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:203) ~[spring-webmvc-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
.......
What I have tried so far
As this solution mentioned, i have added the necessary headers to the request from angular end
this.http.post(ipUrl, args, { headers: new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json', 'Access-Control-Allow-Headers': 'Content-Type'})});
As this answer, I have added getters/setters to the Model objects
I want to know where i went wrong and how to resolve this issue?
UPDATE
Springboot Rest Controller method
#PostMapping("/login")
public #ResponseBody ResponseWrapper<WebUser> login(#RequestBody LoginData loginData){
try {
return loginService.loginProcess(loginData);
}
catch (Exception ex){
ProgrammerAlert.printStackTrace(ex);
return new ResponseWrapper<>(ResponseWrapper.ERROR, ex.getMessage());
}
}
Could you write your controller in this way. and see if it responds.
#ResponseBody
#RequestMapping(value = "/login",
method = RequestMethod.POST)
public ResponseWrapper<WebUser> login(...){
.
.
.
}
Is there a reason that you do not want to use RequestMapping ?
Is there any reasons not to add produces and consumes properties?
#PostMapping("/login", produces = MediaType.APPLICATION_JSON_UTF8, consumes = MediaType.APPLICATION_JSON_UTF8)
public #ResponseBody ResponseWrapper<WebUser> login(#RequestBody LoginData loginData){
try {
return loginService.loginProcess(loginData);
}
catch (Exception ex){
ProgrammerAlert.printStackTrace(ex);
return new ResponseWrapper<>(ResponseWrapper.ERROR, ex.getMessage());
}
}
or,
Did you add any JSON message converter, like jackson? Check your application has below dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
your return object should be converted to JSON properly. Add Jackson to you pom (separately from starter web) and retry.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
As per your recent edit, you're now getting 406 Not Acceptable, to fix this error, keep the media type of your Spring boot application's response same as the Accept header of your client. Try the following.
Change the value of produces to MediaType.APPLICATION_JSON as you have accept header in client as "application/json". Also please note:
APPLICATION_JSON_UTF8 is deprecated in favor of APPLICATION_JSON
Reference: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/MediaType.html
For those who are facing a similar issue, you can also check if there is any typo in Accept header as I often face this problem.
I also came across the same issue, I guess the answer of this question is very clear
HTTP Status 415 – Unsupported Media Type
while sending the request make the content type json instead of text

Post data Spring web services Restful

I am trying to save data on my database with a web service POST wich serializes a HTML form to save a object. The rest client firefox says this:
"The server refused this request because the requested entity is in a format not supported by the requested resource for the requested method"
The eclipse console shows the message:
org.jasig.cas.client.util.CommonUtils - safeGetParameter called on a POST HttpServletRequest for LogoutRequest. Cannot complete check safely. Reverting to standard behavior for this Parameter
I understand that the object that i want to save is not valid, but I don't see what the problem is.
#RequestMapping(value="/solicitudCita", method = RequestMethod.POST)
public #ResponseBody String putSolicitud(#ModelAttribute("Solicitud") Solicitud solicitud) throws Exception{
System.out.println(solicitud.toString());
solicitudCitaAppMService.createOrUpdate(solicitud);
String solicitudAdded = "Solicitud de cita -> {" + solicitud.toString() + "} añadida";
System.out.println(solicitud);
return solicitudAdded;
}
Help me please
Thanks
If you want to call this controller in a RESTful manner, you have to annotate the solicitud parameter as #RequestBody. Second, you have to have the Jackson libraries in you classpath so Spring can pick them up and use them for unmarshalling the object.
If you use Maven, use these dependencies:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
BTW, why do you have to serialize the HTML form and sent it across? I would suggest you use a REST client, for instance this one, that is available in the Chrome WebStore.

Categories