React.js,如何向服务器发送multipart/form-data?

49

我们希望将图像文件以multipart/form的形式发送到后端,我们尝试使用html表单来获取文件并将文件作为formData发送,以下是代码

export default class Task extends React.Component {

  uploadAction() {
    var data = new FormData();
    var imagedata = document.querySelector('input[type="file"]').files[0];
    data.append("data", imagedata);

    fetch("http://localhost:8910/taskCreationController/createStoryTask", {
      mode: 'no-cors',
      method: "POST",
      headers: {
        "Content-Type": "multipart/form-data"
        "Accept": "application/json",
        "type": "formData"
      },
      body: data
    }).then(function (res) {
      if (res.ok) {
        alert("Perfect! ");
      } else if (res.status == 401) {
        alert("Oops! ");
      }
    }, function (e) {
      alert("Error submitting form!");
    });
  }

  render() {
    return (
        <form encType="multipart/form-data" action="">
          <input type="file" name="fileName" defaultValue="fileName"></input>
          <input type="button" value="upload" onClick={this.uploadAction.bind(this)}></input>
        </form>
    )
  }
}

后端错误为"nested exception is 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"

阅读此文后,我们尝试在fetch的headers中设置boundary:

fetch("http://localhost:8910/taskCreationController/createStoryTask", {
      mode: 'no-cors',
      method: "POST",
      headers: {
        "Content-Type": "multipart/form-data; boundary=AaB03x" +
        "--AaB03x" +
        "Content-Disposition: file" +
        "Content-Type: png" +
        "Content-Transfer-Encoding: binary" +
        "...data... " +
        "--AaB03x--",
        "Accept": "application/json",
        "type": "formData"
      },
      body: data
    }).then(function (res) {
      if (res.ok) {
        alert("Perfect! ");
      } else if (res.status == 401) {
        alert("Oops! ");
      }
    }, function (e) {
      alert("Error submitting form!");
    });
  }

这次,后端出现了错误:在路径为 [] 的上下文中的 [dispatcherServlet] 的 Servlet.service() 抛出异常 [请求处理失败;嵌套异常是 java.lang.NullPointerException],其根本原因为

我们是否正确地添加了多部分边界?应该放在哪里? 也许我们一开始就错了,因为我们没有正确获取多部分/form-data。如何正确获取它?

8个回答

30

这是我使用Axios实现图片上传预览的解决方案。

import React, { Component } from 'react';
import axios from "axios";

React组件类:

class FileUpload extends Component {

    // API Endpoints
    custom_file_upload_url = `YOUR_API_ENDPOINT_SHOULD_GOES_HERE`;


    constructor(props) {
        super(props);
        this.state = {
            image_file: null,
            image_preview: '',
        }
    }

    // Image Preview Handler
    handleImagePreview = (e) => {
        let image_as_base64 = URL.createObjectURL(e.target.files[0])
        let image_as_files = e.target.files[0];

        this.setState({
            image_preview: image_as_base64,
            image_file: image_as_files,
        })
    }

    // Image/File Submit Handler
    handleSubmitFile = () => {

        if (this.state.image_file !== null){

            let formData = new FormData();
            formData.append('customFile', this.state.image_file);
            // the image field name should be similar to your api endpoint field name
            // in my case here the field name is customFile

            axios.post(
                this.custom_file_upload_url,
                formData,
                {
                    headers: {
                        "Authorization": "YOUR_API_AUTHORIZATION_KEY_SHOULD_GOES_HERE_IF_HAVE",
                        "Content-type": "multipart/form-data",
                    },                    
                }
            )
            .then(res => {
                console.log(`Success` + res.data);
            })
            .catch(err => {
                console.log(err);
            })
        }
    }


    // render from here
    render() { 
        return (
            <div>
                {/* image preview */}
                <img src={this.state.image_preview} alt="image preview"/>

                {/* image input field */}
                <input
                    type="file"
                    onChange={this.handleImagePreview}
                />
                <label>Upload file</label>
                <input type="submit" onClick={this.handleSubmitFile} value="Submit"/>
            </div>
        );
    }
}

export default FileUpload;

1
直截了当!感谢您添加Base64。 - mrKindo
1
很高兴知道你喜欢它 @mrKindo - Fatema Tuz Zuhora
1
这里的base64是干什么用的?当我使用console.log时,我得到了以下我不理解的东西:blob:http://localhost:3000/7454bf5a-e458-4c30-bbcc-ac0674ade821 - Roby Cigar
1
@RobyCigar Base64用于在不丢失或修改内容本身的情况下传输数据。它是一种将二进制数据编码为ASCII字符集的方法。 - Fatema Tuz Zuhora
为了防止在选择图片之前出现损坏的图像(默认视图),预览图像应该是有条件的:{this.state.image_preview ? (<img src={this.state.image_preview} alt="image preview" />) : null},这样就不会显示没有src属性的<img/>标签。 - StefaDesign
显示剩余2条评论

27

我们只是尝试移除了标题,它起作用了!

fetch("http://localhost:8910/taskCreationController/createStoryTask", {
      mode: 'no-cors',
      method: "POST",
      body: data
    }).then(function (res) {
      if (res.ok) {
        alert("Perfect! ");
      } else if (res.status == 401) {
        alert("Oops! ");
      }
    }, function (e) {
      alert("Error submitting form!");
    });

2
你如何在后端处理这些数据?假设使用 PHP @PeterJiang - Denny Rachmadi
我们没有使用Php,而是Java。 - Yuehai

10

文件也可在事件中获取:

e.target.files[0]

(消除了需要使用 document.querySelector('input[type="file"]').files[0]; 的必要性)

uploadAction(e) {
  const data = new FormData();
  const imagedata = e.target.files[0];
  data.append('inputname', imagedata);
  ...

注意:console.log(data.get('inputname')) 进行调试,console.log(data) 不会显示追加的数据。


1
感谢使用console.log(data.get('inputname'))。但是为什么console.log(data)返回为空?对于多个文件,我正在使用console.log(data.getAll('inputname'))。 - Komal

7

https://muffinman.io/uploading-files-using-fetch-multipart-form-data/ 对我来说效果最好。它使用formData。

import React from "react";
import logo from "./logo.svg";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import Button from "react-bootstrap/Button";

const ReactDOM = require("react-dom");


export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.test = this.test.bind(this);
    this.state = {
      fileUploadOngoing: false
    };
  }

  test() {
    console.log(
      "Test this.state.fileUploadOngoing=" + this.state.fileUploadOngoing
    );
    this.setState({
      fileUploadOngoing: true
    });

    const fileInput = document.querySelector("#fileInput");
    const formData = new FormData();

    formData.append("file", fileInput.files[0]);
    formData.append("test", "StringValueTest");

    const options = {
      method: "POST",
      body: formData
      // If you add this, upload won't work
      // headers: {
      //   'Content-Type': 'multipart/form-data',
      // }
    };
    fetch("http://localhost:5000/ui/upload/file", options);
  }
  render() {
    console.log("this.state.fileUploadOngoing=" + this.state.fileUploadOngoing);
    return (
      <div>
        <input id="fileInput" type="file" name="file" />
        <Button onClick={this.test} variant="primary">
          Primary
        </Button>

        {this.state.fileUploadOngoing && (
          <div>
            <h1> File upload ongoing abc 123</h1>
            {console.log(
              "Why is it printing this.state.fileUploadOngoing=" +
                this.state.fileUploadOngoing
            )}
          </div>
        )}

      </div>
    );
  }
}

4

React文件上传组件

import { Component } from 'react';

class Upload extends Component {
  constructor() {
    super();
    this.state = {
      image: '',
    }
  }

  handleFileChange = e => {
    this.setState({
      [e.target.name]: e.target.files[0],
    })
  }

  handleSubmit = async e => {
    e.preventDefault();

    const formData = new FormData();
    for (let name in this.state) {
      formData.append(name, this.state[name]);
    }

    await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    });

    alert('done');
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input 
          name="image" 
          type="file"
          onChange={this.handleFileChange}>
        </input>
        <input type="submit"></input>
      </form>
    )
  }
}

export default Upload;

1

我正在使用云进行工作,无法推送视频,与上述问题相同,下面添加了代码以获取详细信息,并且对我而言运行良好。 files [0]是useState用于存储文件并将数据附加为数组

var formData = new FormData()
formData.append('files', files[0])
for (var [key, value] of formData.entries()) {
  console.log(key, value)
}
fetch('url', {
  method: 'POST',
  body: formData,
  redirect: 'follow',
})
.then(res => console.log(res))

添加重定向解决了我的问题。希望这可以帮到你。


0
“请求被拒绝,因为未找到多部分边界。”
当您发送“multipart/form-data”时,边界会自动添加到请求头的内容类型中。您需要告诉服务器参数何时结束,以符合边界规则。您需要像这样设置“Content-type”。
"Content-Type": `multipart/form-data: boundary=add-random-characters`

本文将指导您:https://roytuts.com/boundary-in-multipart-form-data/

边界用于分隔multipart/form-data中的名称/值对。边界参数充当multipart/form-data中每个名称和值对的标记。边界参数会自动添加到HTTP(超文本传输协议)请求头中的Content-Type中。


-1

如果要发送 multipart/formdata,需要避免使用 contentType,因为浏览器会自动分配 boundaryContent-Type

在您的情况下,即使避免使用 Content-Type,它也会设置为默认值 text/plain。因此,请尝试使用 jQuery 的 ajax,如果将其设置为 false,则会删除 contentType

这是可行的代码

var data = new FormData();
var imagedata = document.querySelector('input[type="file"]').files[0];
data.append("data", imagedata);
$.ajax({
    method: "POST",
    url: fullUrl,
    data: data,
    dataType: 'json',
    cache: false,
    processData: false,
    contentType: false
}).done((data) => {
    //resolve(data);
}).fail((err) => {
    //console.log("errorrr for file upload", err);
    //reject(err);
});

9
在jQuery中提供给ReactJS问题的示例并没有抓住重点。 - lilly
@lilly 你可以使用 import $ from 'jquery'React 中使用 jQuery ajax。希望这样说得通。 - Jyothi Babu Araja
1
我认为他不想在这个问题上使用jQuery @JyothiBabuAraja - Delali
你只是试图通过这些建议来解决CORS问题,但并不理想。 - DevDonkey
@DevDonkey,抱歉我只是提供了一种通过XHR发送多部分表单数据的方法。我在这里没有解决CORS问题。如果您有任何建议,我随时可以进行编辑。 - Jyothi Babu Araja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接