Сервер
Начнём с написания серверной части программы. Код работает на Node.js
Маршрутизация
Для запуска сервера будет использоваться http.createServer
. В функции, обрабатывающей новый запрос, мы должны различать запросы (определяемые методом и путём), которые мы поддерживаем. Это можно сделать через длинную цепочку if
/ else
, но можно и красивее.
Маршрутизатор – компонент, помогающий распределить запрос к функции, которая может его обработать. Можно сказать маршрутизатору, что запросы PUT
с путём, совпадающим с регуляркой /^\/talks\/(\/+)$/
(что совпадает с /talks/
, за которым идёт название темы), могут быть обработаны заданной функцией. Кроме того, он может помочь извлечь осмысленные части пути, в нашем случае – название темы, заключённое в кавычки, и передать их вспомогательной функции.
В NPM есть много хороших модулей маршрутизации, но тут мы сами себе такой напишем, чтобы продемонстрировать принцип его работы.
Вот файл router.js
, который будет запрашиваться через require
из модуля сервера:
var Router = module.exports = function() {
this.routes = [];
};
Router.prototype.add = function(method, url, handler) {
this.routes.push({method: method,
urclass="underline" url,
handler: handler});
};
Router.prototype.resolve = function(request, response) {
var path = require("url").parse(request.url).pathname;
return this.routes.some(function(route) {
var match = route.url.exec(path);
if (!match || route.method != request.method)
return false;
var urlParts = match.slice(1).map(decodeURIComponent);
route.handler.apply(null, [request, response]
.concat(urlParts));
return true;
});
};
Модуль экспортирует конструктор Router
. Объект router
позволяет регистрировать новые обработчики с методом add
, и распределять запросы методом resolve
.
Последний вернёт булевское значение, показывающее, был ли найден обработчик. Метод some
массива путей будет пробовать их по очереди (в порядке, в каком они были заданы), и остановится с возвратом true
, если путь найден.
Функции обработчиков вызываются с объектами request
и response
. Когда регулярка, проверяющая URL, возвращает группы, то представляющие их строки передаются в обработчик в качестве дополнительных аргументов. Эти строчки надо декодировать из URL-стиля %20
.
Выдача файлов
Когда тип запроса не совпадает ни с одним из типов, которые обрабатывает маршрутизатор, сервер должен интерпретировать его как запрос файла из общей директории. Можно было бы использовать файловый сервер из главы 20 для выдачи этих файлов, но нам не нужна поддержка PUT
и DELETE
, зато нам нужны дополнительные функции типа поддержки кеширования. Поэтому, давайте использовать проверенный и протестированный файловый сервер из NPM.
Я выбрал ecstatic
. Это не единственный сервер на NPM, но он хорошо работает и удовлетворяет нашим требованиям. Модуль ecstatic
экспортирует функцию, которую можно вызвать с объектом конфигурации, чтобы она выдала функцию обработчика. Мы используем опцию root
, чтобы сообщить серверу, где нужно искать файлы. Обработчик принимает параметры request
и response
, и его можно передать напрямую в createServer
, чтобы создать сервер, который отдаёт только файлы. Но сначала нам нужно проверить те запросы, которые мы обрабатываем особо – поэтому мы обёртываем его в ещё одну функцию.
var http = require("http");
var Router = require("./router");
var ecstatic = require("ecstatic");
var fileServer = ecstatic({root: "./public"});
var router = new Router();
http.createServer(function(request, response) {
if (!router.resolve(request, response))
fileServer(request, response);
}).listen(8000);
Функции respond и respondJSON используются в коде сервера, чтобы можно было отправлять ответы одним вызовом функции.
function respond(response, status, data, type) {
response.writeHead(status, {
"Content-Type": type || "text/plain"
});
response.end(data);
}
function respondJSON(response, status, data) {
respond(response, status, JSON.stringify(data),