使用Angular 6向Flask(Python)Web服务上传文件

时间:2018-05-13 15:51:18

标签: angular flask

我创建了一个带有烧瓶的web服务来保存文件,这强烈关注正式烧瓶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""

当我在webapp中使用表单时,服务完美运行(文件将存储在服务器上)

<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>

当尝试通过Angular HttpClient处理上传时,flask会检测到请求中没有文件(即print('no file in request')将被执行)

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);
    });

后端-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)
}

有任何建议/提示吗?

2 个答案:

答案 0 :(得分:2)

删除httpOptions;例如,明确设置的http头,解决了问题。比较来自angular webapp和来自html表单的HTTP post请求,显示在http标头Content-Type中尽管有multipart/form-data'边界,但是当显式设置http标头时,不需要使用angular设置边界。从http帖子中删除它解决了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);
}

答案 1 :(得分:1)

如果您使用multipart/*内容类型之一,则实际上需要在boundary标头中指定Content-Type参数,否则需要服务器(对于HTTP请求) )将无法解析有效负载。

来自RFC2046

多部分实体的Content-Type字段需要一个参数, “边界”。然后将边界定界线定义为一条线 完全由两个连字符组成(“-”,十进制值45) 然后是Content-Type标头中的边界参数值 字段,可选的线性空格和终止CRLF。

边界分隔符不得出现在封装材料中, 且长度不能超过70个字符,不包括两个前导 连字符。

最后一个正文部分之后的边界定界线是 区分清楚的定界符,指示不再有其他身体部位 跟随。这样的定界符行与先前的定界符相同 行,在边界后再添加两个连字符 参数值。

因此,您的请求应如下所示

Content-Type: multipart/form-data; --unique-boundary-1
Content-Disposition: form-data; name="file"; filename="download.jpg"

--unique-boundary-1

在下图中查看Content-Type和Form数据

enter image description here

最好的实现此目标的方法是在Content-Type中不包括HttpHeaders。让浏览器根据您的内容为您添加它。

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)">

打字稿

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);
        });
  });
}