У занятого сервера, использующего длинные запросы, могут висеть открытыми тысячи запросов, а, следовательно, и TCP-соединений. Node хорошо подходит для такой системы, потому, что он позволяет с лёгкостью управлять многими соединениями без создания отдельных потоков.
Интерфейс HTTP
Перед тем, как мы начнём делать сервер или клиент, подумаем об их точке соприкосновения: интерфейсе HTTP, через который они взаимодействуют.
Интерфейс будет основан на JSON, и, как и в файловом сервере в главе 20, мы будем с выгодой использовать методы HTTP. Интерфейс сосредоточен вокруг пути /talks
. Пути, которые не начинаются с /talks
, будут использоваться для отдачи статичных файлов – HTML и JavaScript, определяющих клиентскую часть.
Запрос GET
к /talks
возвращает документ JSON типа этого:
{"serverTime": 1405438911833,
"talks": [{"title": "Unituning ",
"presenter": "Васисуалий",
"summary": "Украшаем свой моноцикл",
"comment": []}]}
Поле serverTime
используется для надёжности длинных запросов. Вернёмся к нему позже.
Создание новой темы происходит через запрос PUT
к URL вида /talks/Unituning
, где часть после второго слэша – название темы. Тело запроса PUT
должно содержать объект JSON, в котором описаны свойства presenter
и summary
.
Поскольку заголовки тем могут содержать пробелы и другие символы, которые нельзя вставлять в URL, при создании URL их надо закодировать при помощи функции encodeURIComponent
.
console.log("/talks/" + encodeURIComponent("How to Idle"));
// → /talks/How%20to%20Idle
Запрос на создание темы может выглядеть так:
PUT /talks/How%20to%20Idle HTTP/1.1
Content-Type: application/json
Content-Length: 92
{«presenter»: «Даша»,
«summary»: «Неподвижно стоим на моноцикле»}
Такие URL поддерживают запросы GET
для получения JSON-представления темы и DELETE
для удаления темы.
Добавление комментария происходит через POST
-запрос к URL вида /talks/Unituning/comments
, с объектом JSON, содержащим свойства author
и message
в теле запроса.
POST /talks/Unituning/comments HTTP/1.1
Content-Type: application/json
Content-Length: 72
{«author»: «Alice»,
«message»: «Will you talk about raising a cycle?»}
Для поддержки длинных запросов, запросы GET
к /talks
могут включать параметр под именем changesSince
, показывающий, что клиенту нужны обновления, случившиеся после заданной точки во времени. Когда обновления появляются, они сразу же возвращаются. Когда их нет, запрос задерживается, пока что-нибудь не случится, или пока не пройдёт заданный период времени (мы зададим 90 секунд).
Время используется в формате количества миллисекунд с начала 1970 года, в том же формате, что возвращает Date.now()
. Чтобы удостовериться, что клиент получает все обновления, и не получает одно и то же обновление дважды, клиент должен передать время, в которое он в последний раз получил информацию с сервера. Часы сервера могут не совпадать с клиентом, и даже если б они совпадали, клиент не мог бы знать точное время, в которое сервер отправлял ответ, потому что передача данных по сети занимает время.
Поэтому в ответах на запросы GET
к /talks
и существует свойство serverTime
. Оно сообщает клиенту точное время по часам сервера, когда были созданы передаваемые данные. Клиент просто сохраняет время и передаёт его вместе со следующим запросом, чтобы убедиться, что он получает только те обновления, которых ещё не получал.
GET /talks?changesSince=1405438911833 HTTP/1.1
(прошло время)
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 95
{«serverTime»: 1405438913401,
«talks»: [{«title»: «Unituning»,
«deleted»: true}]}
Когда тема меняется, создаётся или комментируется, в ответ на следующий запрос включается полная информация о теме. Когда тема удаляется, включаются только название и свойство deleted
. Клиент может добавлять темы с заголовками, которые он ещё не видел, к странице, обновлять темы, которые он уже показывает, и удалять темы, которые были удалены.
Протокол, описываемый в этой главе, не осуществляет контроль доступа. Каждый может комментировать, менять и удалять темы. Так как интернет полон хулиганов, размещение такой системы в онлайне без защиты, скорее всего, закончится плохо.
Простым решением было бы разместить систему за обратным прокси – это HTTP-сервер, которая принимает соединения снаружи системы и перенаправляет их на серверы HTTP, работающие локально. Такой прокси можно настроить, чтобы он спрашивал имя и пароль пользователя, и вы могли бы устроить так, чтобы пароль был только у членов вашей группы.