XMLHttpRequest: Multipart / Related POST с XML и изображением в качестве полезной нагрузки - PullRequest
12 голосов
/ 25 ноября 2011

Я пытаюсь отправить изображение (с метаданными) на веб-страницы Picasa из Chrome-Extension.Обратите внимание, что обычный пост с Content-Type image / xyz работает, как я описал здесь .Однако я хочу включить описание / ключевые слова, а спецификация протокола описывает многочастный / связанный формат с XML и частью данных.

Я получаюДанные через 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 просто генерирует multipart из входных значений ишаблон XML и производит точно такой же вывод , что и спецификация (кроме .. двоичных данных изображения ..), но для полноты, вот оно:

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, но я не могу исправить это.

Обратите внимание, что из-за старой ошибки в Picasa , base64 нерешение.

1 Ответ

20 голосов
/ 09 апреля 2012

В спецификации XMLHttpRequest указано, что данные, отправленные с использованием метода .send(), преобразуются в Unicode и кодируются как UTF-8.

Рекомендуемый способ загрузки двоичных данных - через API FormData. Однако, поскольку вы не просто загружаете файл, но и оборачиваете двоичные данные в XML, этот параметр бесполезен.

Решение можно найти в исходном коде FormData для веб-работников Polyfill , который я написал, когда столкнулся с подобной проблемой. Чтобы предотвратить преобразование Unicode, все данные добавляются в массив и, наконец, передаются как ArrayBuffer. Последовательности байтов не затрагиваются при передаче, согласно спецификации .

Приведенный ниже код является специфическим производным, основанным на FormData для веб-работников 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!
}
...