Post MultipartFile - Request Part not preset Error - spring

I try to send an image from a ionic Front-End application through a post method to Back-End services in Spring boot
I have done this method that makes the post to the backend url with the image inside a FormData object:
uploadImageService(url: string, image: any) {
console.log('post service: upload Image', + url);
// Initiates a FormData object to be sent to the server
const fd: FormData = new FormData();
fd.append('file', image);
const xhr = new XMLHttpRequest;
console.log('form data file: \n' + fd.get('file'));
xhr.open('POST', url);
// Send the FormData
xhr.send(fd);
console.log(xhr.response);
return xhr.responseText;
}
// call this method:
this.webapiService.uploadImageService(this.globalDataService.getUrlMedium() 'riskcontrol/subir-imagen', this.selectedImage);
This is the spring boot method that collects this post:
#RequestMapping(method = RequestMethod.POST, value = "/subir-imagen")
public ResponseEntity handleFileUpload(#RequestParam("file") MultipartFile file) {
LOGGER.log(Level.INFO, "/Post, handleFileUpload", file);
String associatedFileURL = fileManagerService.storageFile(file);
return ResponseEntity.ok(associatedFileURL);
}
When I do the post of the image I get this error:
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]
I have launched the petition through Postman and it has worked,
that's why I think the error is in the tyscript code.
The only difference I see between postman and the code, is that in the form-data, let mark the key as type file or type text, and I have chosen type file.
I tried to make the request post in another way:
const httpOptionsImages = {
headers: new HttpHeaders({
'Content-Type': 'multipart/form-data',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, PUT, POST, DELETE'
})
};
// function
uploadImageService(url: string, image: any): Observable<any> {
console.log('post service: upload Image', + url);
// Initiates a FormData object to be sent to the server
const fd: FormData = new FormData();
fd.append('file', image);
return this.http
.post(url, fd, httpOptionsImages);
}
// call to the function
this.webapiService.uploadImageService(this.globalDataService.getUrlMedium() + 'riskcontrol/subir-imagen', this.selectedImage)
.subscribe( result => {
console.log(result);
});
But in this way I got another error:
FileUploadException: the request was rejected because no multipart boundary was found
What am I doing wrong?
Is there any way to indicate to FormData that the key is of type file as in postman?

Add the image as a Blob follow Ionic tutorial
const imgBlob = new Blob([reader.result], {type: file.type});
formData.append('file', imgBlob, file.name);
In the readFile function the program utilizes the FileReader from the File API to read the file into an ArrayBuffer. The onloadend event is called as soon as the file is successfully read. The app then creates a FormData object, wraps the array buffer in a Blob and adds it to the FormData object with the name 'file'. This is the same name the server expects as request parameter.

Remove your 'Content-Type': 'multipart/form-data'.

Have you tried on using #RequestPart instead of #RequestParam for MultipartFile file.

Related

Expo & SpringBoot Multipart Upload Problem

I have been trying for hours now, to upload a file and a JSON using multipart file upload. I use Expo React Native as the client and SpringBoot as the server.
I already tried many different versions. After reading into this a lot, this is how it should work:
In my Expo app I have this:
const formData = new FormData();
formData.append(
'document',
new Blob([JSON.stringify(json)], {
type: 'application/json'
}));
formData.append('file', {
uri: url,
type: data.type,
name
});
const xhr = new XMLHttpRequest();
xhr.open('POST', API_URL);
xhr.setRequestHeader('Authorization', 'Bearer ' + jwt);
xhr.onload = () => {
const response = JSON.parse(xhr.response);
console.log(response);
// ... do something with the successful response
};
xhr.onerror = e => {
console.log(e, 'upload failed');
};
xhr.ontimeout = e => {
console.log(e, 'upload timeout');
};
xhr.send(formData);
In my SpringBoot Backend I have this:
#PostMapping(value = "/api/upload")
public ResponseEntity<Void> uploadDocument(
#RequestPart("document") DocumentDTO document,
#RequestPart("file") MultipartFile file) {
// ... my business logic
}
Now without the document it would work, but as soon as I add the document I get this error:
o.z.problem.spring.common.AdviceTraits : Bad Request: Required request part 'document' is not present
As a workaround I will upload files as base64 encoded strings for now ... But I really don't understand why this doesn't work, because it should.
Similar issue I think:
https://github.com/facebook/react-native/issues/30623
Any help would be greatly appreciated.
Try your backend with this approach. (May try removing Blob in FE, just leave it as
json string)
Create a class to wrap both document and file.
#Data
public class FormDataModel {
private MultipartFile file;
private DocumentDTO document;
public void setDocument(String document) {
ObjectMapper mapper = new ObjectMapper();
// this requires try-catch block in fact
this.document = mapper.readValue(document, DocumentDTO.class);
}
}
Use #ModelAttribute at Controller
#PostMapping(value = "/api/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Void> uploadDocument(#ModelAttribute FormDataModel wrapper) {
// ... my business logic
}

Can't upload image to a external API using axios

I've been trying to upload an image with axios to an external (laravel) api and it's been giving me nightmares.
resumed template:
<v-form>
<v-file-input
label="Logo*"
v-model="image"
accept="image/*"
#change="onFileSelected"
required
></v-file-input>
<v-btn color="blue darken-1" text #click="createProvider">Create Provider</v-btn>
</v-form>
Methods
methods: {
onFileSelected (event) {
this.selectedImage = event;
},
createProvider() {
let formData = new FormData();
formData.append("image", this.selectedImage, this.selectedImage.name);
const config = {
headers: {
Authorization: this.token,
'content-type': 'multipart/form-data'
}
};
let imageData = {
'image': formData,
'name': 'Provider Image', // Required Field
}
axios.post('http://fake_external_url.com/api/images', imageData, config) // laravel API
.then(console.log)
.catch(console.log)
},
}
The error that I get in return is:
Error: Request failed with status code 422
Request Response:
[HTTP/1.1 422 Unprocessable Entity 1374ms]
{"image":{},"name":"Image Provider"}
I see that the image is not receiving anything.
If I console.log this.selectedImage I get:
File {
name: "happy.jpg",
lastModified: 1596711013544,
webkitRelativePath: "",
size: 41292,
type: "image/jpeg"
}
If I console.log FormData I get crap
FormData
​<prototype>: FormDataPrototype
​​append: function append()
​​constructor: function ()
​​delete: function delete()
​​entries: function entries()
​​forEach: function forEach()
​​get: function get()
​​getAll: function getAll()
​​has: function has()
​​keys: function keys()
​​set: function set()
​​values: function values()
​​Symbol(Symbol.toStringTag): "FormData"
​​Symbol(Symbol.iterator): function entries()
​​<prototype>: Object { … }
My environment: localhost on a XAMPP server (php artisan serve as well). Laravel, VueJS, Vuetify latest versions.
I think my problem likes in my FormData, but it may be from the variables that it's receiving from the event click. I am out of ideas.
[EDIT] Note: I am able to upload image when using POSTMAN.
The reason why I am using event, and not the classic event.target.files[0] it's because there is no target in the response from the console.log.
You have two problems.
FormData
You need to send a FormData object, only a FormData object, and nothing but a FormData object.
If you want to pass additional data, then append it to the FormData object.
Wrapping the FormData object in another object and passing it to (for example) a JSON serializer will just break it.
Content-Type
The multipart/form-data MIME type has a mandatory boundary parameter.
You have omitted it, but you can't know what it is anyway.
Do not set the Content-Type header manually. The underlying XHR object will read it from the FormData object.

How to send multipart request in angular 6?

I need to send a multipart request.
When I am submitting the form I am getting below error from backend,
Resolved exception caused by Handler execution: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
I am able to hit from Advanced rest client, but facing issue with angular.
On backend side below is The REST endpoint.
#PostMapping("/createCIF")
public Response < Map < String, Object >> createCIF(
#RequestPart("actDocs") List < MultipartFile > actDocs,
#Valid #RequestPart("createCIFReq") CreateCIFReq createCIFReq,
HttpServletRequest request) throws URISyntaxException {
}
Below is the angular side code in component.ts file.
let formData = new FormData();
formData.append('actDocs', this.userInfoService.mulitPartFileArray);
formData.append('createCIFReq', JSON.stringify(this.userInfo));
this.userInfoService.createCif(formData)
.pipe(first())
.subscribe(
data => {
}
}
Angular side Service level code
createCif(formData): any {
return this.http.post<any>(this.url + 'createCIF',
formData)
.pipe(map(cif => {
return cif;
}));
}
I got stuck on this issue an entire day.
Angular seems to fail to set a correct content-type to the JSON part.
I managed to solve this by creating a Blob :
let formData = new FormData();
formData.append('actDocs', this.userInfoService.mulitPartFileArray);
formData.append(
'createCIFReq',
new Blob([JSON.stringify(this.userInfo)], {type: 'application/json'})
);
Hope it helps.

react can't get restController response

I have tried to use restController generate file byte array but when i return it to react , react didn't get the byte array. front-end is using react , back-end is using spring restController and i use Http to communication both front and back. is it any wrong in my code? Thank you for your helping.
restController:
String fileName = DateUtility.dateToStr(new Date(), DateUtility.YYYYMMDD_HHMMSS) + " - "
+ reportNmaeByType.get(exportParam.getReportType()) + ".xls";
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(excelByte, HttpStatus.OK);
react:
createExcelFile(){
var params = {
reportResultList: this.state.reportResult,
reportType: getReportSelector().state.selectedReportType,
selectColumnMap: this.state.selectColumn,
selectCusColumnMap: this.state.selectCusColumn
}
fetch("http://localhost:8080/mark-web/file/createExcel", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
}).then(res => {
if (res.ok) {
console.log(res)
console.log(this)
console.log('create excel success!!')
} else {
console.log('create excel Fail!!')
}
})
}
response:
enter image description here
Update 2018/09/16:
I have added some code in react function and it finally could download excel file but the file is broken. i have checked the blob object in response. it shows blob is json object. is it because i didn't decode to the blob object?
React:
}).then(res => {
if(!res.ok){
console.log("Failed To Download File")
}else{
return res.blob()
}
}).then(blob => {
console.log(blob)
let url = URL.createObjectURL(blob)
console.log(url)
var downloadAnchorNode = document.createElement('a')
downloadAnchorNode.setAttribute("href", url)
downloadAnchorNode.setAttribute("download", "excel" + ".xls")
downloadAnchorNode.click()
downloadAnchorNode.remove()
})
response:
enter image description here
So, from your network graph, it looks like your request is completing as expected, but you are just unable to derive the ByteArray from the response.
With normal requests which return a JSON or XML for e.x. you can read them in one go, as they are part of the body. In your case however, your body contains a Stream. You will thus have to handle reading that stream on your own.
You can do that with response.blob() :
The blob() method reads the stream to completion and returns a Blob object. You can then use this blob object to embed an image or download the file. For all intent and purposes, I would recommend using this. Unless you are dealing with huge files (>500 MB), it should suffice your needs.
For example:
fetch("http://localhost:8080/mark-web/file/createExcel", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
}).then(res => {
if (!res.ok) {
throw new Error(res.statusText);
} else {
return res.blob()
}
}).then(blob => {// do your thing})
.catch(err => console.log(error))
Or
You can use the experimental ReadableStream interface for a more granular control over what you want to do with it.

Required request part 'file' is not present - Angular2 Post request

I am trying to get my file upload functionality done using Angular2 and SpringBoot. I can certify that my java code for the file uploading working fine since I have tested it successfully using Postman.
However, when it comes to sending the file from Angular2 front end, I am getting the HTTP 400 response saying Required request part 'file' is not present.
This is how I send the POST request from Angular2.
savePhoto(photoToSave: File) {
let formData: FormData = new FormData();
formData.append('file', photoToSave);
// this will be used to add headers to the requests conditionally later using Custom Request Options
this._globals.setRequestFrom("save-photo");
let savedPath = this._http
.post(this._endpointUrl + "save-photo", formData)
.map(
res => {
return res.json();
}
)
.catch(handleError);
return savedPath;
}
Note that I have written a CustomRequestOptions class which extends BaseRequestOptions in order to append Authorization header and Content Type header. Content Type header will be added conditionally.
Following is the code for that.
#Injectable()
export class CustomRequestOptions extends BaseRequestOptions {
constructor(private _globals: Globals) {
super();
this.headers.set('X-Requested-By', 'Angular 2');
this.headers.append('virglk', "vigamage");
}
merge(options?: RequestOptionsArgs): RequestOptions {
var newOptions = super.merge(options);
let hdr = this._globals.getAuthorization();
newOptions.headers.set("Authorization", hdr);
if(this._globals.getRequestFrom() != "save-photo"){
newOptions.headers.set('Content-Type', 'application/json');
}else{
//request coming from save photo
console.log("request coming from save photo");
}
return newOptions;
}
}
This conditional header appending is working fine. The purpose of doing that is if I add 'Content-Type', 'application/json' header to every request, file upload method in Spring controller will not accept it. (Returns http 415)
Everything seems to be fine. But I get Required request part 'file' is not present error response. Why is that? I am adding that param to the form Data.
let formData: FormData = new FormData();
formData.append('file', photoToSave);
This is the Spring Controller method for your reference.
#RequestMapping(method = RequestMethod.POST, value = "/tender/save-new/save-photo", consumes = {"multipart/form-data"})
public ResponseEntity<?> uploadPhoto(#RequestParam("file") MultipartFile file){
if (file.isEmpty()) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage("DEBUG: Attached file is empty");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
String returnPath = null;
try {
// upload stuff
} catch (IOException e) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage(e.getMessage());
return new ResponseEntity<ErrorResponse> (errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<String>(returnPath, HttpStatus.OK);
}
EDIT - Adding the payload of the request captured by the browser
As you can see, the param "file" is available there.
Try to add
headers: {
'Content-Type': 'multipart/form-data'
},
to your
.post(this._endpointUrl + "save-photo", formData)
Change formData.append('file', photoToSave);
to              formData.append('file', this.photoToSave, this.photoToSave.name); and also add headers specifying the type of data you are passing to API, in your case it will be 'Content-Type': 'multipart/form-data'. Post the output here if it fails even after changing this.
Is there a chance that you're using zuul in a secondary app that is forwarding the request? I saw this with an update where the headers were stripped while forwarding a multi-part upload. I have a gatekeeper app which forwards requests using zuul to the actual service via a looking from eureka. I fixed it by modifying the url like this:
http://myserver.com/service/upload
to
http://myserver.com/zuul/service/upload
Suddenly the 'file' part of the upload header was no longer stripped away and discarded.
The cause, I suspect was a re-try mechanism which cached requests. On failure, it would re-submit the requests, but somehow for file uploads, it wasn't working properly.
To upload a file to the server, send your file inside a FormData and set content type as multipart/form-data.
export const uploadFile = (url, file, onUploadProgress) => {
let formData = new FormData();
formData.append("file", file);
return axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=UTF-8',
// other headers
},
onUploadProgress,
})
};
To handle file object, be careful with consumes attribute and #RequestPart annotation here.
#PostMapping(value = "/your-upload-path", consumes = "multipart/form-data")
public ResponseEntity<Object> uploadFile(#RequestPart("file") #Valid #NotNull #NotBlank MultipartFile file) {
// .. your service call or logic here
}

Resources