Получение бинарных данных в Google Closure Library
Четверг, 30 августа 2012

Делюсь с вами кусочком из своей практики, который может кому-нибудь пригодиться. Лично я был бы рад найти подобный пост на просторах интернета, когда столкнулся с этой проблемой.

Итак, задача: с помощью XmlHttpRequest получать бинарные данные с сервера. Ну как-то так получилось, что нужно скачивать сжатый с помощью gzip xml-файл с сервера и выбирать из него некоторый набор данных.

Первая важная вещь, которая понадобится, — это метод overrideMimeType у объекта XMLHttpRequest. Этот метод позволяет переопределить тип получаемых с сервера данных. И даже существует специальная кодировка, которая специально была задумана для приема бинарных данных — x-user-defined (мы помним, что XMLHttpRequest автоматически преобразует все принятые данные в юникод). Поэтому простое добавление следующей строчки должно сделать большую часть работы.

xhr.overrideMimeType('text/plain; charset=x-user-defined');

Мозг, натренированный jQuery, выдает кусок кода практически сразу:

$.ajax({
    url: url,
    beforeSend: function(xhr) {
        xhr.overrideMimeType(
            'text/plain; charset=x-user-defined'
        );
    },
    success: successHandler,
    error: errorHandler
});

А вот с Google Closure Library не всё так просто. Там нет никаких beforeSend, и если посмотреть в документацию, то нужная функция выглядит следующим образом:

goog.net.XhrIo.send(url, opt_callback, opt_method, opt_content, opt_headers, opt_timeoutInterval)

Только один callback, который срабатывает на complete, данные, заголовки, таймаут. Не густо. Но если посмотреть в код этой функции, то можно обнаружить, что на самом деле это прокси для объекта goog.net.XhrIo. Но goog.net.XhrIo тоже не конструирует XMLHttpRequest. Вместо этого использует фабрику для создания запросов, что, кстати, логично, но больше Java-way чем JS-way. Ну да ладно. Получается, чтобы вклинить вызов overrideMimeType, нужно сделать свою фабрику с блекджеком, например, вот так:

my.net.BinaryXmlHttpFactory = function () {
    goog.base(this);
};
goog.inherits(my.net.BinaryXmlHttpFactory, goog.net.DefaultXmlHttpFactory);

my.net.BinaryXmlHttpFactory.prototype.createInstance = function () {
    var xhr = goog.base(this, "createInstance");
    xhr.overrideMimeType('text/plain; charset=x-user-defined');
    return xhr;
};

Теперь дело осталось за малым: инициализировать goog.net.XhrIo c новой фабрикой и выполнить запрос:

var xhrIo = new goog.net.XhrIo(new my.net.BinaryXmlHttpFactory());
goog.events.listen(xhrIo, goog.net.EventType.SUCCESS, successHandler);
goog.events.listen(xhrIo, [goog.net.EventType.ERROR, goog.net.EventType.ABORT], errorHandler);
xhrIo.send(url);

Вот и всё. Сразу оговорюсь, что всё это не будет работать в Internet Explorer, т.к. там нужна своя особая магия для получения бинарных данных, в отличие от остальных, где остаток действий выглядит довольно просто:

var res = '';
for (var i = 0; i < data.length; ++i) {
    var code = data.charCodeAt(i);
    res += String.fromCharCode(code & 0xff);
}

Надеюсь, что кому-то этот пост поможет и направит мысли в правильную сторону.

← Бесполезные знания по JavaScriptIT_Ground #19 →

Хочется что-то добавить или сказать? Я всегда рад обсудить. Пишите на me@dikmax.name.