Перейти к содержимому

Оперируем двоичными данными в Node.js. Часть 2: получение ответа

12/01/2010

В предыдущей статье мы собрали и отправили серверу запрос, и получили двоичную строку в ответ. Самое время эту строку препарировать 🙂

Разбираем ответ сервера

Структура ответа в Sphinx довольно проста, и понятна даже из кода PHP-коннектора (собственно, так я его и портировал). Сначала 8 байт заголовка: статус-код (int16), версия (int16), длина оставшейся части заголовка (int32). Последнее нам нужно разве что для проверки, т.к. tcp-модуль node всё равно забирает ответ Sphinx целиком и отдаёт его в callback уже склеенным.

Заголовок пакета

Вообще, разбор бинарных данных с bits.js выглядит так же элегантно как сборка. Получаем заголовок:

var output = {};
var response = new bits.Decoder(data);
var data_length = data.length;

output.status = response.shift_int16();
output.version = response.shift_int16();

output.length = response.shift_int32();

Проверяем, что мы вообще получили:

if ( output.length != data.length - 8 ) {
    sys.puts("failed to read searchd response (status=" + output.status + ", ver=" + output.version + ", len=" + output.length + ", read=" + ( data.length - 8 ) + ")");
}

if (output.version < search_command) {
    sys.puts("searchd command older than client's version, some options might not work");
}

if (output.status == Sphinx.statusCode.WARNING) {
    sys.puts("WARNING: ");
}

Когда мы делаем var response = new bits.Decoder(data), мы получаем копию двоичной строки для дальнейшей разделки. Так что надо отрезать уже разобранные 8 байт от оригинала и передать данные дальше.

Тело пакета

Разбор ответа я организовал про тому же принципу что и в PHP-коннекторе: первая функция отрезает и разбирает заголовок, проверяет целостность пакета и код статуса. Вторая парсит оставшуюся часть.
Пакет данных начинается опять-таки с кода статуса (теперь уже int32). За ним идёт счётчик количества полей (я пока не понял зачем он нужен) и сами поля. Формат поля простой: длина названия (int32) и само название. Разбираем их в цикле:

var output = {};
var response = new bits.Decoder(data);
var i;

output.status = response.shift_int32();
output.num_fields = response.shift_int32();

output.fields = [];
output.attributes = [];
output.matches = [];

// Get fields
for (i = 0; i < output.num_fields; i++) {
    var field = {};
    field.length = response.shift_int32();
    field.name = response.shift_raw_string(field.length);
    output.fields.push(field);
}

Дальше идут атрибуты примерно в таком же виде, как поля. Счётчик, длина имени, имя, тип атрибута (int32). Разбираем, складываем в массив:

output.num_attrs = response.shift_int32();

// Get attributes
for (i = 0; i < output.num_attrs; i++) {
    var attribute = {};
    attribute.length = response.shift_int32();
    attribute.name = response.shift_raw_string(attribute.length);
    attribute.type = response.shift_int32();
    output.attributes.push(attribute);
}

Остались сами ответы и немного сопроводительной информации. Получаем счётчик ответов, потом сами ответы. Вместе с ответом приходит ID документа в виде int64, но пока мы его отбросим. формат должен быть понятен из названий полей. Здесь используется список атрибутов, который мы получили ранее:

output.match_count = response.shift_int32();
output.id64 = response.shift_int32();

// Get matches
for (i = 0; i < output.match_count; i++) {
    var match = {};

    if (output.id64 == 1) {
        // here we must fetch int64 document id
        // and immediately throw half of it away ))
        var id64 = response.shift_int32();
        match.doc = response.shift_int32();

        match.weight = response.shift_int32();
    } else {
        match.doc = response.shift_int32();

        match.weight = response.shift_int32();
    }

    var attrvals = [];
    match.attrs = {};

    //
    var attr_value;

    for (attribute in output.attributes) {
        // BIGINT size attributes
        if (attribute.type == Sphinx.attribute.BIGINT) {
            attr_value = response.shift_int32();
            attr_value = response.shift_int32();
            match.attrs[attribute.name] = attr_value;
            continue;
        }

        // FLOAT size attributes (32 bits)
        if (attribute.type == Sphinx.attribute.FLOAT) {
            attr = response.shift_int32();
            match.attrs[attribute.name] = attr_value;
            continue;
        }

        // We don't need this branch right now,
        // as it is covered by previous `if`
        // @todo: implement MULTI attribute type
        attr_value = response.shift_int32();
        match.attrs[attribute.name] = attr_value;
    }

    output.matches.push(match);

}

Id64 на второй строке — как раз флаг использования длинных ID для документов. Если он выставлен в 1, ID документа является 64-битным длинным целым, иначе — 32-битным integer.
В пакете теперь осталась только служебная информация вроде времени выполнения запроса. Её тоже берём.

output.total = response.shift_int32();
output.total_found = response.shift_int32();
output.msecs = response.shift_int32();
output.words = response.shift_int32();

Результат

Теперь можно вывести полученный объект output через JSON.stringify() в консоль, проверить что мы получили:

{ 
  "status":0,
  "num_fields":2,
  "fields":[
    {"length":5,"name":"title"},
    {
      "length":7,
      "name":"content"
    }],
  "attributes":[
    {
      "length":8,
      "name":"group_id",
      "type":1
    },
    {
      "length":10,
      "name":"date_added",
      "type":2
    }
  ],
  "matches":[
    {
      "doc":1,
      "weight":2,
      "attrs":
        {
          "undefined":1262017923
        }
    },
    {
      "doc":2,
      "weight":2,
      "attrs":
        {
          "undefined":1262017923
        }
    },
    {
      "doc":4,
      "weight":1,
      "attrs":
        {
          "undefined":1262017923
        }
    }
  ],
  "num_attrs":2,
  "match_count":3,
  "id64":0,
  "total":3,
  "total_found":3,
  "msecs":2,
  "words":1
}

Это уже можно использовать. Как видно из дампа, 64-битные ID не использовались. Стало быть, мы получили настоящие id найденных документов: 1, 2 и 4.

Ссылки по теме

Limestone.js на Github

Оставьте комментарий