Выбрать главу

          "application/json");

}

Темы как ресурсы

Сервер хранит предложенные темы в объекте talks, у которого именами свойств являются названия тем. Они будут выглядеть как ресурсы HTTP по адресу /talks/[title], поэтому нам нужно добавить в маршрутизатор обработчиков, реализующих различные методы, которые клиенты могут использовать для работы с ними.

Обработчик для запросов GET одной темы должен найти её и либо вернуть данные в JSON, либо выдать ошибку 404.

var talks = Object.create(null);

router.add("GET", /^\/talks\/([^\/]+)$/,

           function(request, response, title) {

  if (title in talks)

    respondJSON(response, 200, talks[title]);

  else

    respond(response, 404, "No talk '" + title + "' found");

});

Удаление темы делается удалением из объекта talks.

router.add("DELETE", /^\/talks\/([^\/]+)$/,

           function(request, response, title) {

  if (title in talks) {

    delete talks[title];

    registerChange(title);

  }

  respond(response, 204, null);

});

Функция registerChange, которую мы определим позже, уведомляет длинные запросы об изменениях.

Чтобы было просто получать контент тел запросов, закодированных при помощи JSON, мы определяем функцию readStreamAsJSON, которая читает всё содержимое потока, разбирает его по правилам JSON и затем делает обратный вызов.

function readStreamAsJSON(stream, callback) {

  var data = "";

  stream.on("data", function(chunk) {

    data += chunk;

  });

  stream.on("end", function() {

    var result, error;

    try { result = JSON.parse(data); }

    catch (e) { error = e; }

    callback(error, result);

  });

  stream.on("error", function(error) {

    callback(error);

  });

}

Один из обработчиков, которому нужно читать ответы в JSON – это обработчик PUT, который используется для создания новых тем. Он должен проверить, есть ли у данных свойства presenter и summary, которые должны быть строками. Данные, приходящие снаружи, всегда могут оказаться мусором, и мы не хотим, чтобы из-за плохого запроса была сломана наша система.

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

router.add("PUT", /^\/talks\/([^\/]+)$/,

           function(request, response, title) {

  readStreamAsJSON(request, function(error, talk) {

    if (error) {

      respond(response, 400, error.toString());

    } else if (!talk ||

               typeof talk.presenter != "string" ||

               typeof talk.summary != "string") {

      respond(response, 400, "Bad talk data");

    } else {

      talks[title] = {title: title,

                      presenter: talk.presenter,

                      summary: talk.summary,

                      comments: []};

      registerChange(title);

      respond(response, 204, null);

    }

  });

});

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

router.add("POST", /^\/talks\/([^\/]+)\/comments$/,

           function(request, response, title) {

  readStreamAsJSON(request, function(error, comment) {

    if (error) {

      respond(response, 400, error.toString());

    } else if (!comment ||

               typeof comment.author != "string" ||

               typeof comment.message != "string") {

      respond(response, 400, "Bad comment data");

    } else if (title in talks) {

      talks[title].comments.push(comment);

      registerChange(title);

      respond(response, 204, null);

    } else {

      respond(response, 404, "No talk '" + title + "' found");

    }

  });

});

Попытка добавить комментарий к несуществующей теме должна возвращать ошибку 404.

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

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

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