Uploading a file via Spring - java

I have a dev server which revolves angular 2 at localhost: 4200, and tomcat with Spring on localhost: 8080.
I try to upload a file to the server in the following manner:
angular code:
uploadAvatar(file:File){
let xhr = new XMLHttpRequest()
xhr.open("POST",`http://localhost:8080/api/upload/avatar`)
xhr.setRequestHeader("Content-Type","multipart/form-data")
xhr.send(file)
}
Controller code Spring:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST)
public String uploadFile(MultipartFile file){
log.info(file);
return file.getName();
}
But after trying to download a file error appears in the java-console:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request;
nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
How do I fix this error?
Thank you.
UPDATE 1
The "duplicate" is used with Spring MVC + JSP, I'm trying to download a file via Ajax. And the version of the decision does not help me given there.
UPDATE 2
Spring Boot(v1.4.3.RELEASE)
I use the java configuration, if you want I will give an example of a full configuration.

I found the solution to my problem, I postorabs below describe in detail what I did for this.
As the presentation I use Angular 2, sending the file takes place in the following manner.
uploadFile(file:File){
let form = new FormData();
form.append("file",file)
let xhr = new XMLHttpRequest()
xhr.open("POST",`${URL}/api/upload/avatar`)
xhr.send(form)
}
Content-Type and the boundary in this case, are automatically linked.
The following manipulations need to be done on the server Stronie:
Add two bean:
#Bean(name = "commonsMultipartResolver")
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
#Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("10MB");
factory.setMaxRequestSize("10MB");
return factory.createMultipartConfig();
}
The controller looks like this:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(#RequestPart MultipartFile file){
log.info(file);
return file.getOriginalFilename();
}

Related

React Spring Boot App Using Multipart Form Data - Required request part 'file' is not present

After days of attempting to upload a file using a React frontend and Spring Boot backend, I'm coming here to see if anyone can guide me in the right direction. Everything seems to be in place - I select my file, I see the file properties in the console, and I see form data being passed to the REST API, but I still get an error.
Some React snippets:
const onFileChangeHandler = (e) => {
e.preventDefault();
setFileAttachment({
fileAttachment: e.target.files[0]
})};
const formData = new FormData();
formData.append('file',fileAttachment)
const requestOptionsInsertNote = {
method: "POST",
body: formData
};
<input type="file" name="file" onChange={onFileChangeHandler}/>
Some Spring Boot snippets:
#PostMapping( "/api/notes/insertNote")
public void insertJournalNote(#RequestPart(value="file") MultipartFile file{
UploadedFileInfo uploadedFileInfo = new UploadedFileInfo();
try{
uploadedFileInfo.setFileData(file.getBytes());
uploadedFileInfo.setFileType(file.getContentType());
uploadedFileInfo.setFileName(file.getOriginalFilename());
}catch (IOException e){
e.printStackTrace();
}}
Console log data for console.log(fileAttachment):
Object { fileAttachment: File }​
fileAttachment: File { name: "file.jpg", lastModified: 1650655091391, size: 148823, … }​​
lastModified: 1650655091391​​
name: "file.jpg"​​
size: 148823​​
type: "image/jpeg"​​
webkitRelativePath: ""
Request sent to rest api:
-----------------------------174062142330182702901981377266
Content-Disposition: form-data; name="file"
[object Object]
-----------------------------174062142330182702901981377266--
Error message in Intellij:
Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]
You should enable multipart in application.properties file:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size = -1
spring.servlet.multipart.max-request-size=-1
Also, don't forget to set the 'Content-Type':'multipart/form-data' in request header while sending your request.
EDIT:You need to define the CommonMultipartResolver Bean.
#Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipart = new CommonsMultipartResolver();
multipart.setMaxUploadSize(3 * 1024 * 1024);
return multipart;
}
#Bean
#Order(0)
public MultipartFilter multipartFilter() {
MultipartFilter multipartFilter = new MultipartFilter();
multipartFilter.setMultipartResolverBeanName("multipartResolver");
return multipartFilter;
}
Also, it seems like we should leave the 'Content-Type' to blank, so that Chrome would set the boundary itself.
Turns out this:
setFileAttachment({
fileAttachment: e.target.files[0]
})
Needed to be this:
setFileAttachment(e.target.files[0])
It's always the little things

WebFlux Swagger (Open API) integraton - Post Request sample

I have integrated Swagger (OpenAPI) with Spring Webflux as mentioned here: https://springdoc.org/#spring-weblfuxwebmvcfn-with-functional-endpoints using RouterOperation. The integration works fine and is accessible at /swagger-ui.html
However, for POST APIs, I am not seeing the "Request" sample when I click on "Try it out" button. My Post API accepts a Json as Request Body.
How do I configure this ? Can that be done via Annotations along with RouterOperation or something else ?
Edit: Below is my Router class code:
#Configuration
public class MyRouter {
#RouterOperations({
#RouterOperation(path = "/data", beanClass = MyHandler.class, beanMethod = "getData"),
#RouterOperation(path = "/allData", beanClass = MyHandler.class, beanMethod = "getAllData") })
#Bean
public RouterFunction<ServerResponse> route(MyHandler MyHandler) {
return RouterFunctions
.route(RequestPredicates.POST("/data").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), MyHandler::getData)
.andRoute(RequestPredicates.GET("/allData").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), MyHandler::getAllData);
}
}
Upon adding RouterOperations annotation I can see the swagger-ui showing both the GET and POST APIs correctly but not the request schema sample.
I also came across yaml / json file to describe this. But I am not getting where to put this file in my application so that swagger-ui uses it.
Finally found it
Using #Operation and #Schema, can define the class that is required as input in request body. This will be shown as sample json structure in Swagger-ui. No other configuration required.
#RouterOperations({
#RouterOperation(
path = "/data", beanClass = MyHandler.class, beanMethod = "getData",
operation = #Operation(
operationId = "opGetData",
requestBody = #RequestBody(required = true, description = "Enter Request body as Json Object",
content = #Content(
schema = #Schema(implementation = ApiRequestBody.class))))),
#RouterOperation(path = "/allData", beanClass = MyHandler.class, beanMethod = "getAllData")})
#Bean
public RouterFunction<ServerResponse> route(MyHandler myHandler) {
return RouterFunctions
.route(RequestPredicates.POST("/data").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), myHandler::getData)
.andRoute(RequestPredicates.GET("/allData").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), myHandler::getAllData);
}

File is always null in srpingboot 1.3.7 when processing multipart/form-data request

I am trying to communicate a grails application with a springboot application. I am trying to upload a file to the springboot application, that is previously uploaded to grails.
This is the code of my springboot controller:
#ResponseBody
#PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
def save(#Valid #ModelAttribute SaveRequest request) {
//Some logic
}
The saveRequest class contains some validations that need to be applied to the request
class SaveHotelAccreditationRequest {
//Other fields
#NotNull
MultipartFile image
}
I have set the properties in the application.properties file like so
spring.http.multipart.enabled = true
spring.http.multipart.location=/home/user/temp
I am using both commons-io and commons-fileupload libraries by Apache.
I have set the multipart resolver like this
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(2*1024*1024);
return multipartResolver;
}
This is the code in my grails application
CredentialsProvider credsProvider = new BasicCredentialsProvider()
credsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(userName, password))
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.build()
try {
HttpPost httpPost = new HttpPost(url)
MultipartEntityBuilder reqEntity = MultipartEntityBuilder.create()
reqEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
reqEntity.addBinaryBody('image', file, ContentType.MULTIPART_FORM_DATA, file.getName())
//Other fields
httpPost.setEntity(reqEntity.build())
System.out.println("Executing request " + httpPost.getRequestLine())
CloseableHttpResponse response = httpclient.execute(httpPost)
try {
System.out.println("----------------------------------------")
System.out.println(response.getStatusLine())
System.out.println(EntityUtils.toString(response.getEntity()))
} finally {
response.close()
}
} finally {
httpclient.close()
}
And when i run the request i get the following error message:
2020-05-02 02:37:24 | WARN | org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver - Resolved
exception caused by Handler execution: org.springframework.validation.BindException: org.springframework.validatio
n.BeanPropertyBindingResult: 1 errors
Field error in object 'saveHotelAccreditationRequest' on field 'image': rejected value [null]; codes [NotNull.saveRequest.image,NotNull.image,NotNull.org.springframework.web.multipart.MultipartFile,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [saveRequest.image,image]; arguments []; default message [image]]; default message [may not be null]
I have tried using postman and i get the same error message. I cannot figure out what i am missing here, any help will be appreciated.
In Spring-Boot REST for multipart APIs, I am writing my multi-part APIs like this
#PostMapping("/upload-doc")
#ApiImplicitParam(paramType = "body", name = "payload", dataType = "UploadDocRequest")
#ResponseStatus(HttpStatus.CREATED)
#ApiOperation("Upload User's documents")
public UploadDocRequest uploadDocRequest(
#RequestPart("payload") UploadDocRequest payload, #RequestPart("docs") MultipartFile[] docs) {
Spring Multipart config in yaml
spring:
servlet:
multipart:
max-file-size: 5MB
max-request-size: 5MB
And How to call it from PostMan-
While working with Multipart , the following has worked for me in Spring boot.
#PostMapping(value = "/uploadVideo")
public void uploadVideo(HttpServletRequest request, HttpServletResponse response,
#RequestParam("file") MultipartFile file) throws Exception {
// logic here
}
and the multipart resolver which you are using for max size can be configured in properties file like this
# Max file size.
spring.servlet.multipart.max-file-size=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
This I am calling from Angular App like this
var reqHeader=new HttpHeaders();
reqHeader.append('contentType','multipart/form-data')
let options = { headers: reqHeader, params: new HttpParams()
                    
             };
const formData = new FormData();
formData.append('file', file )
return this.http.post(this.url_toreadFile,formData,options)
Hope it might provide some help in your implementation.

Spring webflux WebClient post a file to a client

I'm trying to figure out how to write a method to simply send a file from a webflux controller to a 'regular' controller.
I'm constantly getting a common error back, but nothing I've tried has resolved it.
The method I'm sending the file from:
#GetMapping("process")
public Flux<String> process() throws MalformedURLException {
final UrlResource resource = new UrlResource("file:/tmp/document.pdf");
MultiValueMap<String, UrlResource> data = new LinkedMultiValueMap<>();
data.add("file", resource);
return webClient.post()
.uri(LAMBDA_ENDPOINT)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(data))
.exchange()
.flatMap(response -> response.bodyToMono(String.class))
.flux();
}
I'm consuming it in a AWS Lambda with the following endpoint:
#PostMapping(path = "/input", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<List<?>> input(#RequestParam("file") MultipartFile file) throws IOException {
final ByteBuffer byteBuffer = ByteBuffer.wrap(file.getBytes());
//[..]
return new ResponseEntity<>(result, HttpStatus.OK);
}
But I'm constantly just getting:
{
"timestamp":1549395273838,
"status":400,
"error":"Bad Request",
"message":"Required request part 'file' is not present",
"path":"/detect-face"
}
back from the lambda;
Have I just setup the sending of the file incorrectly, Or do I need to configure something on the API Gateway to allow the request parameters in?
This was a interesting one for me. As I'm using a lambda function on the receiving end, and making use of aws-serverless-java-container-spring I actually had to declare the MultipartResolver manually.
The code in my question worked correctly once I added
#Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
to my configuration.
Maybe someone will stumble on this and find it useful.

How to modify the parameter name of MultipartFile in uploading?

I need to upload a MultipartFile to a third-party service via my own backend service. The parameter in the multipart form is 'nameA' but the third-party service need its param name is 'nameB'.
Normally I can solve it in two ways:
Change the param name of frontend to 'nameB'
Change the param name of MultipartFile to 'nameB' in backend service.
But I cannot change the frontend now, so I want to finger out how to modify the param name of MultipartFile in backend service.
The controller of backend service is:
#PostMapping("/url")
public Response method(#RequestParam("nameA") MultipartFile file) {
return Service.method(file);
}
In Feign Client for uploading file to third-party service:
#PostMapping(value = "/url1/url2", consumes = MULTIPART_FORM_DATA_VALUE)
Response method(#RequestParam("nameB") MultipartFile file);
However the use of specify the param with 👆 #RequestParam doesn't work.
So does anyone know how to modify the param name of MultipartFile? Thanks a lot!
That is completely unrelated to your controllers spring annotations and instead depends on how you would upload that file to the 3rd party service. Since you mentioned uploading it, I assume you need to create a new HTTP multipart request in your backend service that would upload the file to the 3rd party service. When creating that request, you will be able to specify the name of the multipart part.
You can set a name of the MultipartFile in the FeignClient as you need, this is a sample from my project:
Сontroller API (receiving side):
#RestController
#RequestMapping("/files")
public class FilesController {
#PostMapping(path = "/upload")
#ResponseStatus(HttpStatus.CREATED)
public FileDescriptor upload(#RequestPart(value = "data") MultipartFile multipartFile) {
...
}
}
Feign client (sending side):
#FeignClient(value = "file-service", configuration = FeignConfig.class)
public interface ContentStorageFeign {
#ResponseBody
#PostMapping(value = "/files/upload", produces = MediaType.APPLICATION_JSON_VALUE)
FileDescriptor create(#RequestPart(value = "data") MultipartFile multipartFile);
}
And this is my FeignConfig:
#Configuration
public class FeignConfig {
#Bean
public Decoder decoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
#Bean
public Encoder encoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
But if you need to create a new request(from a file received from somewhere) and rename this file before sending, this is another problem.

Categories