"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
к таким ответам.