- Общие принципы работы с локальными проектами
- План проекта веб-сервиса копирования сделок и сигналов
- Веб-сервер на основе nodejs
- Теоретические основы протокола WebSockets
- Серверная часть веб-сервисов на базе WebSocket-протокола
- Протокол WebSocket-ов на MQL5
- Клиентские программы эхо и чат-сервисов на MQL5
- Сервис торговых сигналов и тестовая веб-страница
- Клиентская программа сигнального сервиса на MQL5
Сервис торговых сигналов и тестовая веб-страница
Сервис торговых сигналов технически идентичен чат-сервису, однако его пользователи (а точнее клиентские соединения) должны выполнять одну из двух ролей:
- поставщик сообщений;
- потребитель сообщений;
Кроме того, информация должна быть доступна не всем, а по некоторой схеме подписки.
Чтобы обеспечить это, при подключении к сервису пользователи должны будут указать определенную идентификационную информацию, различающуюся в зависимости от роли.
Поставщик должен указать публичный идентификатор сигнала (PUB_ID), уникальный среди всех сигналов. В принципе, одно и то же лицо может потенциально генерировать более одного сигнала и, соответственно, должно иметь возможность получить несколько идентификаторов. В этом смысле мы не станем усложнять сервис, вводя раздельные идентификаторы поставщика (как конкретного лица) и идентификаторы его сигналов. Вместо этого будут поддерживаться только идентификаторы сигналов. Для реального сервиса сигналов этот вопрос нужно проработать, вместе с авторизацией, которую мы оставили за кадром.
Идентификатор потребуется, чтобы рекламировать его или просто передавать лицам, заинтересованным в подписке на данный сигнал. Но "каждый встречный" не должен получить доступ к сигналу, зная только публичный идентификатор. В простейшем случае, для открытого мониторинга счета это было бы приемлемо, но мы продемонстрируем вариант ограничения доступа именно в разрезе сигналов.
Для этой цели поставщик должен предоставить серверу секретный ключ (PUB_KEY), известный только ему, но не общественности. Этот ключ потребуется для генерации ключа доступа конкретного подписчика.
Потребитель (подписчик) также должен иметь уникальный идентификатор (SUB_ID, и здесь также обойдемся без авторизации). Чтобы подписаться на интересующий его сигнал, пользователь должен сообщить поставщику сигнала свой идентификатор (на практике подразумевается, что на этом же этапе нужно подтвердить оплату, и обычно это все автоматизируется сервером). Поставщик формирует тем или иным способом слепок, состоящий из своего идентификатора, идентификатора подписчика и своего секретного ключа. В нашем сервисе это будет делаться вычислением хэша SHA256 от строки PUB_ID:PUB_KEY:SUB_ID, после чего полученные байты переводятся в строку шестнадцатеричного формата. Это и будет ключ доступа (SUB_KEY или ACCESS_KEY) к сигналу конкретного поставщика для конкретного подписчика. Поставщик (а в реальных системах — сам сервер автоматически) пересылает этот ключ подписчику.
Таким образом, подписчик при подключении к сервису должен будет указать свой идентификатор (SUB_ID), идентификатор желаемого сигнала (PUB_ID) и ключ доступа (SUB_KEY). Поскольку сервер знает секретный ключ поставщика, он может повторно рассчитать ключ доступа для данного сочетания PUB_ID и SUB_ID, и сравнить с предоставленным SUB_KEY. Совпадение означает продолжение нормального процесса с обменом сообщениями. Различие приведет к сообщению об ошибке и отключению псевдо-подписчика от сервиса.
Важно отметить, что в нашем демо, в угоду простоте, отсутствует нормальная регистрация пользователей и сигналов, и потому выбор идентификаторов — произвольный. Для нас лишь важно отслеживать уникальность идентификаторов, чтобы знать, кому и от кого посылать информацию онлайн. Так что, наш сервис не гарантирует, что идентификатор, например, "Super Trend" принадлежит вчера, сегодня и завтра одному и тому же пользователю. Резервирование имен производится по принципу: кто первый встал, того и тапки. Пока некий поставщик беспрерывно подключен под данным идентификатором, сигнал исходит от него. Если он отсоединится, то идентификатор станет доступен для выбора в любом следующем подключении.
Единственный идентификатор, который будет всегда "занят" — это "Server": его сервер использует для рассылки своих сообщений о статусах подключений.
Для генерации ключей доступа в папке сервера имеется простой JavaScript access.js. При его запуске в командной строке нужно единственным параметром передать строку указанного выше вида PUB_ID:PUB_KEY:SUB_ID (идентификаторы и секретный ключ между ними, соединенные символом ':')
Если параметр не указать, скрипт генерирует ключ доступа для неких демонстрационных идентификаторов (PUB_ID_001, SUB_ID_100) и секрета (PUB_KEY_FFF).
// JavaScript
|
Запустив скрипт командой:
node access.js PUB_ID_001:PUB_KEY_FFF:SUB_ID_100 |
мы получим такой результат:
fd3f7a105eae8c2d9afce0a7a4e11bf267a40f04b7c216dd01cf78c7165a2a5a |
Между прочим, вы можете проверить и повторить данный алгоритм на чистом MQL5 с помощью функции CryptEncode.
Разобрав концептуальную часть, приступим к практической реализации.
Серверный скрипт сигнального сервиса разместим в файле MQL5/Experts/MQL5Book/p7/Web/wspubsub.js. Настройка серверов в нем совпадает с тем, что мы уже делали ранее. Но дополнительно потребуется подключить тот же модуль "crypto", который был использован в access.js. Начальная страница будет называться wspubsub.htm.
// JavaScript
|
Вместо одной карты подключившихся клиентов определим две карты — раздельно под поставщиков и потребителей сигналов.
// JavaScript
|
В обеих картах ключом выступает идентификатор поставщика, однако в первой хранятся объекты самих поставщиков, а во второй — объекты подписанных на каждого поставщика подписчиков (массивы объектов).
Для передачи идентификаторов и ключей во время "рукопожатия" будем использовать специальный заголовок, разрешенный спецификацией WebSocket-ов, а именно Sec-Websocket-Protocol. Договоримся, что идентификаторы и ключи будут склеены символом '-': в случае поставщика ожидается строка вида X-MQL5-publisher-PUB_ID-PUB_KEY, а в случае подписчика — X-MQL5-subscriber-SUB_ID-PUB_ID-SUB_KEY.
Любые попытки подсоединиться к нашему сервису без заголовка Sec-Websocket-Protocol: X-MQL5-... будут пресекаться немедленным закрытием.
В объекте нового клиента (в параметре обработчика события "connection" — onConnect(client)) данный заголовок легко извлечь из свойства client.protocol.
Покажем процедуру регистрации и рассылки сообщений поставщика сигнала в упрощенном виде, без обработки ошибок (полный код прилагается). Важно отметить, что текст сообщений формируется в формате JSON (о котором мы более подробно поговорим в следующем разделе). В частности, отправитель сообщения передается в свойстве "origin" (причем, когда сообщение посылает сам сервис, в этом поле — строка "Server"), а прикладные данные от поставщика помещаются в свойство "msg", и это может быть не просто текст, но и вложенная структура любого содержания.
// JavaScript
|
Половина алгоритма для подписчиков похожа, но здесь добавилось вычисление ключа доступа и его сличение с тем, что передал подключающийся клиент.
// JavaScript
|
Пользовательский интерфейс на клиентской странице wspubsub.htm просто предлагает перейти по ссылке на одну из двух страниц с формами для поставщиков (wspublisher.htm + wspublisher_client.js) или подписчиков (wssubscriber.htm + wssubscriber_client.js).
Веб-страницы тестовых клиентов сигнального сервиса
Их реализация наследует черты предыдущих рассмотренных JavaScript-клиентов, но с учетом кастомизации заголовка Sec-Websocket-Protocol: X-MQL5- и еще одного нюанса.
До сих пор мы обменивались простыми текстовыми сообщениями. Но для сигнального сервиса потребуется передавать много структурированной информации, а для этого лучше подойдет JSON. Поэтому клиенты умеют парсить JSON, хотя и не используют его по прямому назначению, потому что даже если в JSON-е обнаружится команда покупать или продавать конкретный тикер заданным объемом, браузер не умеет этого делать.
Поддержку JSON нам потребуется добавить и в свой клиент сигнального сервиса на MQL5. Но пока можно запустить на сервере wspubsub.js и протестировать избирательное подключение поставщиков и потребителей сигналов в соответствии с указанными ими реквизитами. Проделайте это самостоятельно.