XMLHttpRequest:以 XML 和图像作为有效负载的多部分/相关 POST

Posted

技术标签:

【中文标题】XMLHttpRequest:以 XML 和图像作为有效负载的多部分/相关 POST【英文标题】:XMLHttpRequest: Multipart/Related POST with XML and image as payload 【发布时间】:2012-01-05 22:19:24 【问题描述】:

我正在尝试从 Chrome 扩展程序中将图像(带有元数据)发布到 Picasa 网络相册。请注意,正如我所描述的 here,使用 Content-Type image/xyz 的常规帖子有效。但是,我希望包含描述/关键字,protocol specification 描述了带有 XML 和数据部分的 multipart/related format。

我通过 html5 FileReader 和用户文件输入获取数据。我检索一个二进制文件 使用

的字符串
FileReader.readAsBinaryString(file);

假设这是我在 FileReader 加载字符串后的回调代码:

function upload_to_album(binaryString, filetype, albumid) 

    var method = 'POST';
    var url = 'http://picasaweb.google.com/data/feed/api/user/default/albumid/' + albumid;
    var request = gen_multipart('Title', 'Description', binaryString, filetype);
    var xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    xhr.setRequestHeader("GData-Version", '3.0');
    xhr.setRequestHeader("Content-Type",  'multipart/related; boundary="END_OF_PART"');
    xhr.setRequestHeader("MIME-version", "1.0");
    // Add OAuth Token
    xhr.setRequestHeader("Authorization", oauth.getAuthorizationHeader(url, method, ''));
    xhr.onreadystatechange = function(data) 
        if (xhr.readyState == 4) 
            // .. handle response
        
    ;
    xhr.send(request);
   

gen_multipart 函数只是从输入值和 XML 模板生成多部分,并产生完全相同的输出 as the specification(除了 ..binary 图像数据..),但为了完整起见,这里是:

function gen_multipart(title, description, image, mimetype) 
    var multipart = ['Media multipart posting', "   \n", '--END_OF_PART', "\n",
    'Content-Type: application/atom+xml',"\n","\n",
    "<entry xmlns='http://www.w3.org/2005/Atom'>", '<title>', title, '</title>',
    '<summary>', description, '</summary>',
    '<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/photos/2007#photo" />',
    '</entry>', "\n", '--END_OF_PART', "\n",
    'Content-Type:', mimetype, "\n\n",
    image, "\n", '--END_OF_PART--'];
    return multipart.join("");

问题是,POST 有效负载与原始图像数据不同,因此会导致错误请求(Picasa 不会接受图像),尽管在使用时它工作正常

xhr.send(file) // With content-type set to file.type

我的问题是,如何让 real 二进制图像包含在多部分中?我认为它只是通过将它附加到 xml 字符串而被破坏,但我似乎无法修复它。

请注意,由于old bug in Picasa,base64 不是解决方案。

【问题讨论】:

您是否尝试过不使用元数据上传? code.google.com/apis/picasaweb/docs/2.0/… 正如我在帖子中所说的那样,直接上传图片而不使用元数据可以正常工作。我明确要求提供一种解决方案来发送 with 元数据。 【参考方案1】:

XMLHttpRequest specification 表示使用.send() 方法发送的数据将转换为 unicode,并编码为 UTF-8。

推荐的二进制数据上传方式是通过FormData API。但是,由于您不只是上传文件,而是将二进制数据包装在 XML 中,因此此选项没有用。

解决方法可以在FormData for Web Workers Polyfill的源码中找到,我遇到类似问题的时候写的。为了防止 Unicode 转换,所有数据都添加到一个数组中,最后以ArrayBuffer 的形式传输。传输时不涉及字节序列,per specification。

下面的代码是一个具体的衍生,基于FormData for Web Workers Polyfill:

function gen_multipart(title, description, image, mimetype) 
    var multipart = [ "..." ].join(''); // See question for the source
    var uint8array = new Uint8Array(multipart.length);
    for (var i=0; i<multipart.length; i++) 
        uint8array[i] = multipart.charCodeAt(i) & 0xff;
    
    return uint8array.buffer; // <-- This is an ArrayBuffer object!

当您使用.readAsArrayBuffer 而不是.readAsBinaryString 时,脚本会变得更高效:

function gen_multipart(title, description, image, mimetype) 
    image = new Uint8Array(image); // Wrap in view to get data

    var before = ['Media ... ', 'Content-Type:', mimetype, "\n\n"].join('');
    var after = '\n--END_OF_PART--';
    var size = before.length + image.byteLength + after.length;
    var uint8array = new Uint8Array(size);
    var i = 0;

    // Append the string.
    for (; i<before.length; i++) 
        uint8array[i] = before.charCodeAt(i) & 0xff;
    

    // Append the binary data.
    for (var j=0; j<image.byteLength; i++, j++) 
        uint8array[i] = image[j];
    

    // Append the remaining string
    for (var j=0; j<after.length; i++, j++) 
        uint8array[i] = after.charCodeAt(j) & 0xff;
    
    return uint8array.buffer; // <-- This is an ArrayBuffer object!

【讨论】:

很棒的发现!感谢您的详细分析和解决方案:) 请注意 XMLHttpRequest.send(ArrayBuffer) 已被弃用。您应该使用 XMLHttpRequest.send(ArrayBufferView)(使用 .buffer)或即将推出的 XMLHttpRequest.sendAsBinary()。见developer.mozilla.org/en-US/docs/Web/API/… @pduncan 使用return uint8array; 而不是return uint8array.buffer; @RobW 非常感谢您的深入回答:)

以上是关于XMLHttpRequest:以 XML 和图像作为有效负载的多部分/相关 POST的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Javascript 和 XMLHttpRequest 加载二进制图像数据?

如何使用 Javascript 和 XMLHttpRequest 加载二进制图像数据?

异步处理XML异步数据——以原生的JavaScript与jQuery中的$.ajax()为例

为啥叫 XMLHttpRequest?

XMLHttpRequest对象

XMLHttpRequest对象