Sending file using multipart/form-data and SpringBoot on backend - spring

I'm trying to send file from vue.js using axios and receive it in spring-based backend.
Here's my frontend part:
uploadMap(context, map){
console.log('uploading map...')
const formData = new FormData();
formData.append("file", map);
axios.post("/info/map/upload", formData)
.then(function (result) {
console.log(result);
}, function (error) {
console.log(error);
});
},
and on backend:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/info/uploadFile")
public void uploadFile(#RequestParam("file") MultipartFile file) throws IOException {
System.out.println("file uploaded");
String basePath = "/Users/admin/software/app1/uploads/";
String filePath = basePath + file.getOriginalFilename();
File dest = new File(filePath);
file.transferTo(dest);
}
Backend part works when using Insomnia/Postman for testing. But when I want to invoke my frontend code, I get on backned:
2020-02-23 17:38:52.038 WARN 61799 --- [nio-5000-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundary2cAmVUpGnPfkQax3' not supported]
How should I deal with that?
Thanks in advance 🤓

Add dependency to your Maven config:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
Then register new bean with special name:
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}

Related

How to use MockMVC test the controller which use org.apache.commons.fileupload?

My Controller use " org.apache.commons.fileupload " realized the file UPload.
see it:
#PostMapping("/upload")
public String upload2(HttpServletRequest request) throws Exception {
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(request);
boolean uploaded = false;
while (iter.hasNext() && !uploaded) {
FileItemStream item = iter.next();
if (item.isFormField()) {
item.openStream().close();
} else {
String fieldName = item.getFieldName();
if (!"file".equals(fieldName)) {
item.openStream().close();
} else {
InputStream stream = item.openStream();
// dosomething here.
uploaded = true;
}
}
}
if (uploaded) {
return "ok";
} else {
throw new BaseResponseException(HttpStatus.BAD_REQUEST, "400", "no file field or data file is empty.");
}
}
and my MockMvc code is
public void upload() throws Exception {
File file = new File("/Users/jianxiaowen/Documents/a.txt");
MockMultipartFile multipartFile = new MockMultipartFile("file", new FileInputStream(file));
HashMap<String, String> contentTypeParams = new HashMap<String, String>();
contentTypeParams.put("boundary", "----WebKitFormBoundaryaDEFKSFMY18ehkjt");
MediaType mediaType = new MediaType("multipart", "form-data", contentTypeParams);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(baseUrl+"/upload")
.content(multipartFile.getBytes())
.contentType(mediaType)
.header(Origin,OriginValue)
.cookie(cookie))
.andReturn();
logResult(mvcResult);
}
my controller is right , it has successed in my web project,
but I want to test it use MvcMock, it has some mistake, see :
can someOne can help me?
"status":"400","msg":"no file field or data file is empty.","data":null
I don't know why it says my file is empty.
my English is poor, thank you very much if someone can help me.
The MockMvc can be used for integration testing for controllers using Apache Commons Fileupload too!
Import the org.apache.httpcomponents:httpmime into your pom.xml or gradle.properties
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
Update the code to use MultipartEntityBuilder to build the multipart request on the client, and then serialize the entity into bytes, which is then set in the request content
public void upload() throws Exception {
File file = new File("/Users/jianxiaowen/Documents/a.txt");
String boundary = "----WebKitFormBoundaryaDEFKSFMY18ehkjt";
// create 'Content-Type' header for multipart along with boundary
HashMap<String, String> contentTypeParams = new HashMap<String, String>();
contentTypeParams.put("boundary", boundary); // set boundary in the header
MediaType mediaType = new MediaType("multipart", "form-data", contentTypeParams);
// create a multipart entity builder, and add parts (file/form data)
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpEntity multipartEntity = MultipartEntityBuilder.create()
.addPart("file", new FileBody(file, ContentType.create("text/plain"), file.getName())) // add file
// .addTextBody("param1", "value1") // optionally add form data
.setBoundary(boundary) // set boundary to be used
.build();
multipartEntity.writeTo(outputStream); // or getContent() to get content stream
byte[] content = outputStream.toByteArray(); // serialize the content to bytes
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders.post(baseUrl + "/upload")
.contentType(mediaType)
.content(content) // finally set the content
.header(Origin,OriginValue)
.cookie(cookie)
).andReturn();
logResult(mvcResult);
}
Can you try the below?
mockMvc.perform(
MockMvcRequestBuilders.multipart(baseUrl+"/upload")
.file(multiPartFile)
).andReturn();
Update:
You need to update the controller to handle the MultipartFile:
#PostMapping("/upload")
public String upload2(#RequestParam(name="nameOfRequestParamWhichContainsFileData")
MultipartFile uploadedFile, HttpServletRequest request) throws Exception {
//the uploaded file gets copied to uploadedFile object.
}
You need not use another library for managing file uploads. You can use the file upload capabilities provided by Spring MVC.

How to fix multipart/form-data MediaType not being set with Jackson Spring MVC

I'm trying to send a Product and product images from Angular 7 frontend to a SpringMVC backend.
To add support for Multipart files I've added this bean inside my AppConfig.
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
Since I want to receive the Product object separately inside the controller I'm using #RequestPart to fetch both separately like this:
#RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
On the frontend I'm adding the image to FormData like this:
let formData = new FormData();
formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);
this.http.post(this.appService.getApiUrl() + "api/product/save/", product);
The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type.
I tried debugging this issue by setting breakpoints inside CommonsMultipartResolver class and after tracing the request through the code I've found that when the getSupportedMediaTypes() is called it returns only two media types:
application/json
application/*+json
Inside the following method in AbstractHttpMessageConverter:
protected boolean canRead(#Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
Finding this I tried adding MediaType.MULTIPART_FORM_DATA like this inside AppConfig:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
List<MediaType> types = new ArrayList<>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_JSON_UTF8);
types.add(MediaType.MULTIPART_FORM_DATA);
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(types);
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(hibernate5Module);
}
}
}
But it still wouldn't work. When the app starts up, I do see the constructor of AbstractJackson2HttpMessageConverter being called with my MediaTypes but they get overwritten by more calls to the same constructor after it.
Is there any way I can get the MediaType to persist? I might be looking in the wrong direction so any insight will be helpful.
The Jackson library is required on the classpath. Spring does not declare this by default. Make sure that at least com.fasterxml.jackson.core:jackson-databind is available in the classpath of the Spring MVC application. Example for Apache Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Example for the jackson.version value: 2.9.0
1) You need to give input data supported at server end. Since you are sending File, means server is consuming the Multipart Data.
For multipart we need to set consumes = "multipart/form-data"
#RequestMapping(value = "save", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
2) Since form is sending multipart data we need to set content-type at front end too in http header in post call.
content-type: multipart/form-data"

Upload file with JSON data in Angular5 and Spring Boot

I am stuck in getting the request at my REST Controller.
Below is my Controller method :
#RestController
#CrossOrigin(origins = "http://localhost:4200", allowedHeaders = "*")
public class UserController {
#Autowired
public UserService userService;
#PostMapping(value = "/fileUploadApi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public #ResponseBody StatusUpload fileUpload(#RequestPart("uploadfile") MultipartFile file, #RequestPart("user") User userDto) throws Exception{
StatusUpload status= new StatusUpload();
status = userService.callUserServiceForFileRead(file, userDto);
return status;
}
In ApplicationConfig.java, I added the following :
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(500000000);
return multipartResolver;
}
Below is the code I have written for file upload at Angular5 end:
HTML:
<!-- Other form elements -->
<!-- Used PrimeNG Custom Upload Handler to get the File Object https://www.primefaces.org/primeng/#/fileupload -->
<label>Upload template </label>
<p-fileUpload (uploadHandler)="getFile($event)" auto="true" customUpload="true" accept=".xlsx,application/msexcel" previewWidth="0" [showUploadButton]="false" [showCancelButton]="false">
</p-fileUpload>
<!-- Form Submit Button -->
<button type="button" (click)="submit()">SUBMIT</button>
COMPONENT :
user: User = new User;
// Set other form element data in user object.
uploadFile: File;
getFile(event) {
this.uploadfile= event.files[0];
}
submit() {
this.userService.saveUserData(this.user,this.uploadFile);
}
SERVICE :
options = {
headers: new HttpHeaders({ 'Content-Type': '' })
};
constructor(private http: HttpClient) { }
saveUserData(user:User, uploadFile:File){
var formData: FormData = new FormData();
formData.append('user',JSON.stringify(user));
formData.append('uploadfile',uploadFile);
return this.http.post<StatusUpload>(baseUrl + "/fileUploadApi", formData, this.options);
}
When I use the above Service, it gives me no response/no Exception don't know why. The request even didn't reach my RESTController.
I read a different approach in some post & used it as below :
saveUserData(user: User, uploadFile: File) {
var formData: FormData = new FormData();
Observable.fromPromise(new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
formData.append('user', new Blob([JSON.stringify(user)],
{
type: "application/json"
}));
formData.append('uploadfile', uploadFile);
xhr.open("POST", baseUrl + "/fileUploadApi", true);
xhr.send(formData);
}));
}
With above, I get org.springframework.web.multipart.support.MissingServletRequestPartException.
Can anyone please help to get this code working. I need to use the http.post() in my Angular service, not the XMLHttpRequest.send() one.
You need to subscribe to the observable.
An HttpClient method does not begin its HTTP request until you call subscribe() on the observable returned by that method. This is true for all HttpClient methods.
Docs

Angular2 does not pass parameters in POST request

I am trying to pass a parameter using Angular POST request to Tomcat server, Spring Framework. Somehow I see that the parameter is there when it is sent, but it somehow does not arrive/properly retrieved on the backend. Here is the Angular2 code:
addCompany() {
console.log("addCompany button clicked!");
console.log("company name: " + this.input);
let nameId = this.input;
let body = JSON.stringify({ input: nameId });
let headers = new Headers({ 'Content-Type': 'application/json', 'X-CSRF-TOKEN':this.getToken() });
console.log("csrf token: " + this.getToken());
let options = new RequestOptions({ headers: headers });
this.http.post('http://localhost:8080/views/addcompany', body, options).toPromise()
.then(() => {
console.log("company added!");
this.reloadList();
});;
}
When I am trying to get it in Spring I am getting null for the parameter:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(HttpServletRequest request,
HttpServletResponse response) {
String nameId = request.getParameter("input");
eventService.addCompany(nameId);
}
I tried also this way:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(Model model, #RequestParam("input") String nameId) {
eventService.addCompany(nameId);
}
And in Angular code I have been trying to change commas everywhere, like:
let nameId = this.input;
let body = JSON.stringify({ 'input': nameId });
etc.
I tried this one: Angular2 - Http POST request parameters
Following the suggestion JB Nizet I tried to create POJO:
public class Company {
public String input;
public Company() {
this.input = "";
}
public String getInput() {
return this.input;
}
public void setInput(String input) {
this.input = input;
}
}
Register it in my #Configuration file:
#Bean
public Company getCompany(){
return new Company();
}
And changed the request method to the following:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(Company company) {
eventService.addCompany(company.input);
}
After that I am getting Company object in the method with input=null.
Then I tried to deregister the Company #Bean from #Configuration and change the request method to the following:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(#RequestBody Company company) {
eventService.addCompany(company.input);
}
But after that I am getting 415 (Unsupported Media Type) error.
In pom.xml I have the following jackson import:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.10</version>
</dependency>
Substituting it for second jackson version solved the issue:
<properties>
...
<jackson.version>2.7.5</jackson.version>
</properties>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
You're sending a JSON object as body, and you expect to get a request parameter containing an attribute of this JSON object.
That won't happen. You need a POJO that matches with the structure of the JSON sent, take that POJO as argument and annotate it with #RequestBody. Jackson will unmarshal the JSON to the POJO and the POJO will be passed to your method.
Request parameters can be used if the request contains an application/x-www-form-urlencoded payload: the kind of payload you send when submitting a HTML form, without doing any JavaScript.
Instead of
let body = JSON.stringify({ input: nameId });
try
let body = { input: nameId };
Try to use :
let body:string='input='+nameId;
And use this header
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN':this.getToken() });
Only for other readers if you want to send more than 1 parameter. use something & fro separating parameter . like below code
let body :string='username='+username+'&password='+password;

need to return json data from spring mvc controller

I am trying to return a Map from spring mvc controller making an ajax call but i am not getting correct resposne.
I have used mvc annotation tag in my config file and also included the jackson jar file in my library.
The requirement for me is to return Map to success of my Ajax call so i can modify a table row in html.
Code for the controller :
#RequestMapping(value="/pricingrecall.do", method=RequestMethod.POST)
#ResponseBody
public Map<Integer,String> pricingUpdate(#RequestParam(value = "opp_Code", required = false) String opp_Code,
#RequestParam(value = "ref_id", required = false) String ref_id,
ModelMap model,
HttpServletRequest request, HttpServletResponse response) throws SQLException, Exception{
String User="fe0777";
List<CrossListViewBean>updatedRow = new ArrayList<CrossListViewBean>();
//String message="";
logger.info(methodLocation+"|"+"Calling pricing recall ....");
Map<String, Object> result = new HashMap<String, Object>();
updatedRow=crossCampService.getupdatedrowListview(opp_Code, ref_id, user);
Map<Integer,String> lbean= new HashMap<Integer,String>();
lbean=crossCampService.getUpdatedDataPosition(updatedRow.get(0));
return lbean;
}
Call from Ajax:
jQuery.ajax( {
url : '/Web/pricingrecall.do',
type: "POST",
cache : false,
timeout : 60000,
data : {
opp_Code :CampId ,
ref_id : index
},
success : function(result, textStatus, request) {
if(result)
{
alert(result);
//jQuery(".note"+index).html(data);
}else
{
alert("The user session has timed out. Please log back in to the service.");
window.location.replace("logout.do");
}
},
error : function(request, textStatus, errorThrown) {
alert("The system has encountered an unexpected error or is currently unavailable. Please contact the support number above if you have any questions.");
}
});
Here in the ajax sucess i am always getting error ,it gets diverted tot he error string.
How can i get the Json from MAp in the ajax sucess
Please help
I'm using flexjson to get the json output correct. I'm attaching a sample code of mine, using flexjson. You can use this as a reference and restructure your controller method to output the correcct json. This link will help you in how to serialize a map.
#RequestMapping(value = "/{id}", headers = "Accept=application/json")
#ResponseBody
public ResponseEntity<String> findUser(#PathVariable("id") Long id) {
User user = userService.find(id);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<String>(user.toJson(), headers, HttpStatus.OK);
}
#Entity
public class AppUser {
#NotNull
private String firstName;
#NotNull
private String lastName;
//Getter Setter goes here
public String AppUser.toJson() {
return new JSONSerializer().exclude("*.class").serialize(this);
}
}
I used jackson-databind and with #ResponseBody annotation on controller methods, it automatically converted returned data to json successfully. If you use maven, add these dependencies to your pom.xml (jackson.version is 2.4.0 for me).
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Otherwise, you can add the jar files to your classpath.

Resources