如何手动创建 multipart/form-data?

9
我们可以使用 .formData() Body mixin 来返回在 Chromium (Chrome) 60+ 和 Firefox 39+ 上的数据的 FormData 表示形式。
相关规范: 勘误

相关

如何在客户端和服务器端使用JavaScript手动创建multipart/form-data,以将multipart/form-data作为响应提供?


Body.formData() 方法是为 ServiceWorker 设计的,它可以在用户请求发送到服务器之前拦截。要手动创建 FormData,您可以使用 FormData 构造函数。 - Kaiido
https://developer.mozilla.org/en-US/docs/Web/API/Body/formData - Kaiido
@Kaiido 我们可以获取原始的multipart/form-data https://dev59.com/f5vga4cB1Zd3GeqP6bsI,但是如何手动创建数据呢? - guest271314
@Kaiido 规范没有说明使用.formData()的原因。该过程不涉及服务器。 - guest271314
2
是的,“我知道MDN由用户驱动”并且确实存在错误=> https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter$history https://developer.mozilla.org/en-US/docs/Web/API/Request/mode$history - Kaiido
显示剩余6条评论
2个回答

16

您可以像以下示例那样使用XMLHttpRequest手动创建multipart/form-data

function multiPost(method, url, formHash){
    var boundary = "nVenJ7H4puv"
    var body = ""
    for(var key in formHash){
        body += "--" + boundary
             + "\r\nContent-Disposition: form-data; name=" + formHash[key].name
             + "\r\nContent-type: " + formHash[key].type
             + "\r\n\r\n" + formHash[key].value + "\r\n"
    }
    body += "--" + boundary + "--\r\n"

    var xml = new XMLHttpRequest();
    xml.open(method, url)
    xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary)
    xml.setRequestHeader("Content-Length", body.length)
    xml.send(body)
}

是的,如果它是文件类型会更好。 - sapics
2
Answer中的代码没有正确处理<input type="file" name="abc"><input type="file" name="abc" multiple>,对吗? - guest271314
2
你的答案中的代码无法处理<input type="file">元素。 - guest271314
2
我们需要一个输入类型为文件的工作示例。另外,你代码中的“formHash”是什么? - trogne
@trogne,我遇到了和你一样的问题,你解决了吗? - Kay
显示剩余2条评论

3
您可以编写自己的FormData polyfill,或者在Google中搜索“FormData polyfill”)))。您也可以在Chrome、FireFox、Opera、Safari、IE(10+)和Edge浏览器中使用常规的FormData。
FormData polyfill仅对旧版IE和workers有用,但对于workers,最好使用此链接
有关更多信息,请参见维基百科中的MIME标准formdata not of body你需要做什么?你想在js中发送表单数据还是接收它? 您可以尝试使用我的polyfill,但我尚未测试过它。
示例:
var data = new RawFormData();
data.append("key","value")
data.append("key", new Blob("test"), "my file.txt");
data.getOutputDeferred().then(function(formData){
    var xml = new XMLHttpRequest();
    xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + data.getBoundry());
    xml.setRequestHeader("Content-Length", formData.length);
    xml.open(method, url);
    xml.send(formData);
});

代码:

/**
* @constructor
*/
RawFormData = function () {
    this._promises = [];
    this._boundry = this.makeBoundary();
};

/**
* @return {string}
*/
RawFormData.prototype.getBoundary = function () {
    return this._boundry;
}

/**
* @return {string}
*/
RawFormData.prototype.makeBoundary = function () {
    return 'MyBoundary' + window.btoa(Math.random().toString()).substr(0, 12);
};

/**
* @param {string} name
* @param {string|number|File|Blob|boolean|null|undefined} val
* @param {string=} filename
*/
RawFormData.prototype.append = function (name, val, filename) {
    var prom = null;

    if(val instanceof File || val instanceof Blob){
        prom = this.readAsBinaryString(val).then(function(base64){
            var contentType = val.type || 'application/octet-stream';
            var result = '--' + this._boundry + '\r\n' +
                'Content-Disposition: form-data; ' +
                'name="' + name + '"; filename="' + this.encode_utf8(filename || "blob") + '"\r\n' +
                'Content-Type: ' + contentType + '\r\n\r\n' +
                base64 + '\r\n';
            return result;
        }.bind(this))
    }else{
        prom = new Promise(function(resolve){
            return '--' + this._boundry + '\r\n' +
                'Content-Disposition: form-data; ' +
                'name="' + this.encode_utf8(name) + '"\r\n\r\n' +
                this.encode_utf8(val) + '\r\n'
        }.bind(this));
    }

    this._promises.push(prom);

    return prom;
};
/**
* @return {File|Blob} blob
* @return {Promise<string>}
*/
RawFormData.prototype.readAsBinaryString = function (blob) {
        var reader = new FileReader();
        return new Promise(function(resolve,reject){
            var binStringCallback = function (e) {
                resolve(e.target.result);
            };

            var arrBufferCallback = function (e) {
                var binary = "";
                var bytes = new Uint8Array(e.target.result);
                var length = bytes.byteLength;
                for (var i = 0; i < length; i++) {
                    binary += String.fromCharCode(bytes[i]);
                }
                resolve(binary);
            };

            reader.onerror = reader.onabort = function () {
                resolve(null);
            };

            if (typeof reader.readAsBinaryString != "undefined") {
                reader.onload = binStringCallback;
                reader.readAsBinaryString(blob);
            } else {
                reader.onload = arrBufferCallback;
                reader.readAsArrayBuffer(blob);
            }
        });
};

RawFormData.prototype.encode_utf8 = function( s ){
   return unescape( encodeURIComponent( s ) );
}

RawFormData.prototype.getOutputDeferred = function () {
    return Promise.all(this._promises).then(function (rows) {
        var output = '--' + this._boundry + '\r\n';
        rows.forEach(function(row) {
            output += row;
        });
        output += '--' + this._boundry + '\r\n';
        return output;
    }.bind(this));
};

我们正在寻找一个简单明了的答案,以便在不使用FormData API的情况下发送文件。也就是说,模仿FormData API的工作方式,即创建与FormData API创建的对象相同的对象。 - trogne
@trogne,您可以将文件作为base64发送,并像字符串一样附加到请求中。在现代浏览器中,您可以使用WebSocket和fetch API。 - Alex Nikulin
我知道我可以发送base64,但那不是我想要的。我想模仿FormData而不改变服务器端代码。 - trogne

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