WebSocket Application Messaging Protocol (WAMP)

wamp-logo-smallВ этом посте я обозрею, так сказать, протокол WebSocket Application Messaging Protocol или WAMP (не путать с аббривеатурой стека Windows Apache Mysql Php). Что же это такой за зверь, для чего он нужен, и вообще, с чего вдруг я начал им заниматься вы узнаете в этом посте.
Понадобилось мне для одного сервиса наладить реал-тайм взаимодействие, да не просто реал-тайм, а с поддержкой асинхронного паттерна Publisher-Subscriber (или Pub-Sub). Некторое время я изучал материалы, смотрел реализации и выбирал подходящий протокол, т.к. писать самому его не хотелось (да и времени не было). И тут ну глаза мне попался сайтик от авторов протокола (wamp.ws). Я был приятно удивлён, но это оказалось то, что мне нужно.

Что за зверь WAMP?

WAMP – это протокол прикладного уровня модели OSI для разработки интероперабельных приложений. Работает поверх протокола TCP-IP, и базируется на следующих технологиях.

  • Websockets
  • JSON
  • URI

Сейчас актуальной является версия 1 протокола WAMP и существуют множество её реализаций на JavaScript, Java, Python, PHP.

Поддерживаемые паттерны асинхронной обработки

  • Асинхронные сообщения
  • Publisher-Subscriber
  • Remote Procedure Calls (RPC)

Транспорт

В качестве транспорта для протокола используется Websocket соединение. Имхо самое прогрессивное на данный момент. А уже в качестве транспорта для вебсокетов можно использовать как нативные ws в браузерах, так и флеш-сокеты и другие извращения типа long-polling (правда эти извращения плохо работают с проксями). Например, библиотечка socket.io позволяет использтвать вот такие транспорты для веб-сокетов:

  • WebSocket (нативные)
  • Adobe® Flash® Socket
  • AJAX long polling
  • AJAX multipart streaming
  • Forever Iframe
  • JSONP Polling

Имхо, кроме первых двух, остальное – костыли.

Сериализация

Полезная нагрузка протокола (payload) сериализуется в формат JSON и поддерживает соответственно сериализацию чисел, строк, массивов и всего того, чему можно сделать json_encode. Тут есть важное замечание и, так сказать, дружеский совет. Перед сериализацией, пропускайте ваши строки через

иначе вы рискуете получить ошибочку

Уникальные идентификаторы (или IDшки)

В протоколе используется реалиация GUID (Globally Unique Identifier) c помощью URI. Ну и правильно, нечего велосипеды изобретать.

Формат полезной нагрузки (payload)

Все сообщения, передающиеся по WebSocket, должны быть текстовые (ну или приведенные к тексту). Т.е. теоретически можно даже  BinaryJS впилить. Кодировка – UTF-8! см. предыдущий абзац про сериализацию. А формат – наш любимый JSON.

Типы сообщений

Протокол определяет сообщения, передающиеся между двумя точками (клиентом и сервером), всего есть 9 типов сообщений.

Тип N типа Направление Категория
WELCOME 0 Server-to-client Служебное
PREFIX 1 Client-to-server Служебное
CALL 2 Client-to-server RPC
CALLRESULT 3 Server-to-client RPC
CALLERROR 4 Server-to-client RPC
SUBSCRIBE 5 Client-to-server PubSub
UNSUBSCRIBE 6 Client-to-server PubSub
PUBLISH 7 Client-to-server PubSub
EVENT 8 Server-to-client PubSub

URI и CURIE

В протоколе есть поддержка не только нормального URI, но и формата CURIE (как в википедии). В общем-то смысл использовать этот формат только один – сократить сетевой трафик.

Служебные сообщения

К служебным сообщениям относятся сообщения WELCOME и PREFIX.

WELCOME

Это первое сообщение, которое получает клиент при коннекте к серверу.

sessionId – номер wamp-сессии. Случайная строка, уникальная в пределах сервера. Номер сессии может быть использован в нескольких случаях.

  • для безопасности при аутентификации и авторизации
  • для составления черных (exclude) или белых (eligible) списков.

protocolVersion – версия протокола wamp (сейчас = 1).

serverIdent – строка-идентификатор сервера (аналог UserAgent).

Пример сообщения:

PREFIX

Процедуры RPC (как и возврат ошибок) в RPC паттерне и PubSub идентифицируются через URI/CURIE. Когда требуется сократить URI, то можно использовать этот самый CURIE. Это сообщение как раз и задаёт соответствие между URI -> CURIE перед его использованием. Например, такой адрес:

преобразуется в такой

если предварительно было задан префикс для calc:

с помощью этого сообщения.

prefix – префикс 🙂

URI – URI. Ваш К.О.

Примеры:

Важно заметить, что соглашение по CURIE действуют в рамках коннекта. Т.е. если например сервер отвалился и произошел реконнект, то PREFIX придётся задать заново.

RPC паттерн

Доступны три типа сообщений.

  • CALL
  • CALLRESULT
  • CALLERROR

rpc

CALL

Инициация вызова удалённой процедуры на сервере.

callID – рандомный ID процедуры, генерируемый на клиенте. Потом возвращается в сообщении CALLRESULT/CALLERROR, т.е. один клиент может параллельно инициировать много разных процедур.

procURI – адрес процедуры (название) на сервере URI/CURIE.

Примеры:

CALL вызов RPC без аргументов

CALL вызов RPC с двумя аргументами, с использованием CURIE

CALL вызов RPC с одним аргументом в виде объекта (имхо лучший способ)

CALL вызов RPC с одинм аргументом NULL

CALL вызов RPC с одним аргументом в виде массива, используя CURIE

CALL вызов RPC with 1 argument, value being a list of integers, using CURIE

Когда выполнение RPC-процедуры завершилось, сервер уведомляет клиента при помощи сообщений CALLRESULT или CALLERROR, Важно помнить, что выполнение процедур – асинхронное, и не стоит ждать возврата немедленно после CALL.

CALLRESULT

Если процедура выполнилась успешно, то сервер возвращает это сообщение клиенту, инициировавшему CALL.

callID – уникальный ID процедуры (клиент должен запомниьт её в момент отправки CALL).
result – результат, строка или JSON.

Примеры:

Результат – NULL

Результат – строковой

Результат – объект

CALLERROR

Когда выполнение удалённой процедуры завершилось ошибкой или процедуру невозможно выполнить, то сервер возвращает сообщение этого типа.

или

callID – уникальный номер процедуры

errorURI – адрес процедуры на сервере

errorDesc – сообщение об ошибке. Присутствует всегда, но может быть пустой строкой (хотя это и не желательно).

errorDetails – более подробное описание ошибки (нпример стек). Если есть, то должно быть не NULL.

Примеры:

CALLERROR c ошибкой общего вида

CALLERROR с ошибкой и аргументом в errorDetails

CALLERROR с ошибкой и списком аргументов в errorDetails

Publisher & Subscribe

В протокол встроено описание реализации паттерна PubSub. Схематично это выглядит вот так:

pubsub_concept

Доступно 4 вида сообщений:

  • SUBSCRIBE
  • UNSUBSCRIBE
  • PUBLISH
  • EVENT

После отправки сообщение SUBSCRIBE клиент будет асинхронно получать от сервера сообщения EVENT, в ответ на оптравку другими клиентами сообщений PUBLISH. Подписка длится всё Wamp(Websocket) сессию, и может быть отключена с помощью отправки сообщения USUBSCRIBE.

warningВ протоколе Wamp v.1 нет такой важной вещи, как уведомление о статусе подписки после SUBSCRIBE. Т.е. например, в случае неудачно подписки или, когда подписка на определённый канал запрещена (а также в случае неверного адреса подписки) клиент об этом не узнает, и неудачный SUBSCRIBE просто игнорируется сервером. Думаю в версии Wamp v.2 это испрваят.

SUBSCRIBE

Собственно подписка на топик.

topicURI – адрес топика (URI/CURIE).

Посе подписки клиент начнёт получать уведомления, отправленные в канал с именем topicURI. Запрос асинхронный, сервер ничего не возвращает в ответ.

Примеры:

SUBSCRIBE с URI

SUBSCRIBE с CURIE

UNSUBSCRIBE

Отписка от канала.

topicURI – адрес топика (URI/CURIE).

Примеры:

UNSUBSCRIBE с полным URI

UNSUBSCRIBE с CURIE

PUBLISH

Отправка сообщения в канал.

topicURI – адрес топика (URI/CURIE).

event – строка (или JSON) с полезной нагрузкой. Сразу рекомендую вам придумать форматы event-ов для вашего приложения, и следовать им.

excludeMe – флаг, отправить сообщение всем подписчикам, кроме меня (если я подписан) если значение TRUE (в JSON)

exclude – черный список, номера wamp-сессий в массиве, которым не надо отправлять сообщение.

eligible – белый список, номера wamp-сессий, кому разрешено отправлять сообщения.

Примеры:

PUBLISH со payload в виде строки

PUBLISH с NULL

PUBLISH с объектом в виде payload

PUBLISH с черным списком

PUBLISH с белым списком

  EVENT

Подписчики получают это сообщение при возникновении события в канале.

topicURI – адрес топика (URI).

event – строка (или JSON) с полезной нагрузкой. Всегда присутствует.

Примеры:

EVENT с payload в виде строки

EVENT с payload – null

EVENT с  полезной нагрузкой в виде объекта

Вот как-то так. Имхо протокол очень перспективный, всем приятного кодинга.

Ссылки

WAMP v.1 Specification
The WebSocket Protocol
UTF-8, a transformation format of ISO 10646
The application/json Media Type for JavaScript Object Notation (JSON)
Uniform Resource Identifier (URI): Generic Syntax, RFC 3986
CURIE Syntax 1.0
Autobahn.JS client library
Ratchet WebSocket for PHP Documentation

 

Leave a Comment