Книга: Выразительный JavaScript

Поддержка длинных запросов

Поддержка длинных запросов

Самый интересный аспект сервера – часть, которая поддерживает длинные запросы. Когда на адрес /talks поступает запрос GET, это может быть простой запрос всех тем, или запрос на обновления с параметром changesSince.

Есть много различных ситуаций, в которых нам нужно отправить клиенту список тем, поэтому мы сначала определим вспомогательную функцию, присоединяющую поле serverTime к таким ответам.

function sendTalks(talks, response) {
  respondJSON(response, 200, {
    serverTime: Date.now(),
    talks: talks
  });
}

Обработчик должен посмотреть на все параметры запроса в его URL, чтобы проверить, не задан ли параметр changesSince. Если дать функции parse модуля “url” второй аргумент значения true, он также распарсит вторую часть URL – query, часть запроса. У возвращаемого объекта будет свойство query, в котором будет ещё один объект, с именами и значениями параметров.

router.add("GET", /^/talks$/, function(request, response) {
  var query = require("url").parse(request.url, true).query;
  if (query.changesSince == null) {
    var list = [];
    for (var title in talks)
      list.push(talks[title]);
    sendTalks(list, response);
  } else {
    var since = Number(query.changesSince);
    if (isNaN(since)) {
      respond(response, 400, "Invalid parameter");
    } else {
      var changed = getChangedTalks(since);
      if (changed.length > 0)
         sendTalks(changed, response);
      else
        waitForChanges(since, response);
    }
  }
});

При отсутствии параметра changesSince обработчик просто строит список всех тем и возвращает его.

Иначе, сперва надо проверить параметр changeSince на предмет того, что это число. Функция getChangedTalks, которую мы вскоре определим, возвращает массив изменённых тем с некоего заданного времени. Если она возвращает пустой массив, то серверу нечего возвращать клиенту, так что он сохраняет объект response (при помощи waitForChanges), чтобы ответить попозже.

var waiting = [];
function waitForChanges(since, response) {
  var waiter = {since: since, response: response};
  waiting.push(waiter);
  setTimeout(function() {
    var found = waiting.indexOf(waiter);
    if (found > -1) {
      waiting.splice(found, 1);
      sendTalks([], response);
    }
  }, 90 * 1000);
}

Метод splice используется для вырезания куска массива. Ему задаётся индекс и количество элементов, и он изменяет массив, удаляя это количество элементов после заданного индекса. В этом случае мы удаляем один элемент – объект, ждущий ответ, чей индекс мы узнали через indexOf. Если вы передадите дополнительные аргументы в splice, их значения будут вставлены в массив на заданной позиции, и заместят удалённые элементы.

Когда объект response сохранён в массиве waiting, задаётся таймаут. После 90 секунд он проверяет, ждёт ли ещё запрос, и если да – отправляет пустой ответ и удаляет его из массива waiting.

Чтобы найти именно те темы, которые сменились после заданного времени, нам надо отслеживать историю изменений. Регистрация изменения при помощи registerChange запомнит это изменение, вместе с текущим временем, в массиве changes. Когда случается изменение, это значит – есть новые данные, поэтому всем ждущим запросам можно немедленно ответить.

var changes = [];
function registerChange(title) {
  changes.push({title: title, time: Date.now()});
  waiting.forEach(function(waiter) {
    sendTalks(getChangedTalks(waiter.since), waiter.response);
  });
  waiting = [];
}

Наконец, getChangedTalks использует массив changes, чтобы построить массив изменившихся тем, включая объекты со свойством deleted для тем, которых уже не существует. При построении массива getChangedTalks должна убедиться, что одна и та же тема не включается дважды, так как тема могла измениться несколько раз с заданного момента времени.

function getChangedTalks(since) {
  var found = [];
  function alreadySeen(title) {
    return found.some(function(f) {return f.title == title;});
  }
  for (var i = changes.length - 1; i >= 0; i--) {
    var change = changes[i];
    if (change.time <= since)
      break;
    else if (alreadySeen(change.title))
      continue;
    else if (change.title in talks)
      found.push(talks[change.title]);
    else
      found.push({title: change.title, deleted: true});
  }
  return found;
}

Вот и всё с кодом сервера. Запуск написанного кода даст вам сервер, работающий на порту 8000, который выдаёт файлы из публичной поддиректории и управляет интерфейсом тем по адресу /talks.

Оглавление книги


Генерация: 1.263. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз