Reputation: 4188
I created a webservice with flask to save files, which strongly follows the officially flask example:
@app.route('/parse_table', methods=['POST'])
def upload_file():
print(request.files)
# check if the post request has the file part
if 'file' not in request.files:
print('no file in request')
return""
file = request.files['file']
if file.filename == '':
print('no selected file')
return""
if file and allowed_file(file.filename):
print("hello")
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return ""
print("end")
return""
When I simply using forms in my webapp the service works perfectly (file will be stored on server)
<form action = "http://127.0.0.1:5000/parse_table" method = "POST"
enctype = "multipart/form-data">
<input type = "file" name = "file" /
<input type = "submit"/>
</form>
When trying to handle the upload via Angular HttpClient
, flask detects no file in request (i.e. print('no file in request')
will be executed)
component.html
<input #fileInput name="file" type="file" (change)="handleFileInput($event.target.files)">
<button mat-raised-button class="standard-button" (click)="uploadFile()">Upload file</button>
component.ts
@ViewChild('fileInput') fileInput;
uploadFile() {
const files: FileList = this.fileInput.nativeElement.files;
if (files.length === 0) {
return;
};
this.backendCommunicationService.parseTable(files).subscribe((data: any) => {
console.log(data);
});
backend-communication.service.ts
parseTable(files) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'multipart/form-data',
})
};
const formData: FormData = new FormData();
formData.append('file', files[0], files[0].name);
return this.http.post(this.backendAddress + '/parse_table', formData, httpOptions)
}
Any suggestions/ hints?
Upvotes: 1
Views: 7769
Reputation: 1194
If you are using one of multipart/*
content types, you are actually required to specify the boundary
parameter in the Content-Type
header, otherwise the server (in the case of an HTTP request) will not be able to parse the payload.
From RFC2046:
The Content-Type field for multipart entities requires one parameter, "boundary". The boundary delimiter line is then defined as a line consisting entirely of two hyphen characters ("-", decimal value 45) followed by the boundary parameter value from the Content-Type header field, optional linear whitespace, and a terminating CRLF.
Boundary delimiters must not appear within the encapsulated material, and must be no longer than 70 characters, not counting the two leading hyphens.
The boundary delimiter line following the last body part is a distinguished delimiter that indicates that no further body parts will follow. Such a delimiter line is identical to the previous delimiter lines, with the addition of two more hyphens after the boundary parameter value.
Hence, your request should look like
Content-Type: multipart/form-data; --unique-boundary-1
Content-Disposition: form-data; name="file"; filename="download.jpg"
--unique-boundary-1
Look at the Content-Type and Form Data in the image below
In angular, best way to achieve this is by not including Content-Type
in HttpHeaders
. Let the browser add it for you based on your content.
httpOptions = {
headers: new HttpHeaders({
// 'Content-Type': 'multipart/form-data', // comment this out
Authorization: this.userAuthContent
})
};
HTML
<input type="file" placeholder="Select images" accept="image/*" multiple
(change)="changeInFileInput($event.target.files)">
Typescript
changeInFileInput(files: any): void {
if (files) {
// iterate over all the files
Array.from(files).forEach((fileData: File) => {
// upload using promise
this.uploadImage(fileData.file)
.then((response: any) => {
// ...
})
.catch((error: any) => {
// ...
});
});
}
}
uploadImage(fileToUpload: File): Promise < any > {
return new Promise((resolve, reject) => {
// create form data
const formData: FormData = new FormData();
// append image file to it
formData.append('file', fileToUpload, fileToUpload.name);
// attach formData (just created above) and httpOptions (created earlier) to post request
this.httpClient.post(environment.bapiurls.uploadRefImage, formData, httpOptions)
.subscribe(
(response: any) => {
resolve(response);
},
(error: any) => {
reject(error);
});
});
}
Upvotes: 1
Reputation: 4188
Removing the httpOptions; e.g., the explicitly set http header, solves the problem. Comparing the HTTP post requests from angular webapp and from the html form showed, that in the http header Content-Type despite multipart/form-data'
a boundary is needed, which will be not set by angular, when the http header is explicitly set. Removing this from the http post solves the isuue:
parseTable(files) {
const formData: FormData = new FormData();
formData.append('file', files[0], files[0].name);
return this.http.post(this.backendAddress + '/parse_table', formData);
}
Upvotes: 4