Создание wss сервиса

Вопрос:

Как создать wss сервис в Workerman, чтобы клиенты могли подключаться и общаться через wss, например, в приложении WeChat Mini Program?

Ответ:

Протокол wss на самом деле является websocket+SSL, то есть это добавление слоя SSL к протоколу websocket, аналогично https (http+SSL). Поэтому достаточно включить SSL на основе протокола websocket, чтобы поддерживать протокол wss.

Метод 1. Использование nginx/apache для проксирования SSL (рекомендуется)

Причины для рекомендации

  • Можно использовать 443 порт, клиентам не нужно указывать порт при подключении
  • Централизованное управление SSL сертификатами, возможность повторного использования конфигурации сайта
  • Возможность балансировки нагрузки
  • Встроенное логирование и мониторинг
  • Лучшая совместимость

Принципы и процесс связи

  1. Клиент инициирует wss соединение с nginx/apache

  2. nginx/apache преобразует данные протокола wss в данные протокола ws и пересылает их на порт websocket-протокола Workerman

  3. Workerman получает данные и обрабатывает бизнес-логику

  4. Когда Workerman отправляет сообщения клиенту, происходит обратный процесс, данные преобразуются через nginx/apache в протокол wss и затем отправляются клиенту

Пример конфигурации nginx

Предварительные условия и подготовка:

  1. Установлен nginx, версия не ниже 1.3

  2. Предполагаем, что Workerman слушает на порту 8282 (протокол websocket)

  3. SSL сертификат (файлы pem/crt и key) предположительно находятся в каталоге /etc/nginx/conf.d/ssl

  4. Планируется использовать nginx для предоставления wss прокси на порту 443 (порт можно изменить по необходимости)

  5. Обычно nginx работает как веб-сервер для других услуг, чтобы не повлиять на работу существующего сайта, здесь используется адрес домен.com/wss в качестве прокси для wss. То есть адрес подключения клиента будет wss://домен.com/wss

Конфигурация nginx будет выглядеть примерно так:

server {
  listen 443;
  # Конфигурация домена опущена...

  ssl on;
  ssl_certificate /etc/ssl/server.pem;
  ssl_certificate_key /etc/ssl/server.key;
  ssl_session_timeout 5m;
  ssl_session_cache shared:SSL:50m;
  ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;

  location /wss
  {
    proxy_pass http://127.0.0.1:8282;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Real-IP $remote_addr;
  }

  # location / {} Другие конфигурации сайта...
}

Тест

// Сертификат проверяет доменное имя, пожалуйста, используйте домен для подключения. Обратите внимание, что порт не указан
ws = new WebSocket("wss://домен.com/wss");

ws.onopen = function() {
    alert("Подключение успешно");
    ws.send('tom');
    alert("Отправка строки 'tom' на сервер");
};
ws.onmessage = function(e) {
    alert("Получено сообщение с сервера: " + e.data);
};

Использование apache для проксирования wss

Также можно использовать apache в качестве прокси для wss, передавая запросы на Workerman.

Подготовка:

  1. GatewayWorker слушает порт 8282 (протокол websocket)

  2. SSL сертификат уже получен, предположительно находится в /server/httpd/cert/

  3. Использование apache для перенаправления порта 443 на указанный порт 8282

  4. httpd-ssl.conf загружен

  5. Установлен openssl

Включение модуля proxy_wstunnel_module

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

Настройка SSL и прокси

#extra/httpd-ssl.conf
DocumentRoot "/каталог/вашего/сайта"
ServerName домен

# Настройки прокси
SSLProxyEngine on

ProxyRequests Off
ProxyPass /wss ws://127.0.0.1:8282/wss
ProxyPassReverse /wss ws://127.0.0.1:8282/wss

# Добавить поддержку SSL протоколов, убрать небезопасные протоколы
SSLProtocol all -SSLv2 -SSLv3
# Изменить шифры как следует
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# Настройка открытого ключа сертификата
SSLCertificateFile /server/httpd/cert/your.pem
# Настройка закрытого ключа сертификата
SSLCertificateKeyFile /server/httpd/cert/your.key
# Настройка цепочки сертификатов
SSLCertificateChainFile /server/httpd/cert/chain.pem

Тест

// Сертификат проверяет доменное имя, пожалуйста, используйте домен для подключения. Обратите внимание, что порт не указан
ws = new WebSocket("wss://домен.com/wss");

ws.onopen = function() {
    alert("Подключение успешно");
    ws.send('tom');
    alert("Отправка строки 'tom' на сервер");
};
ws.onmessage = function(e) {
    alert("Получено сообщение с сервера: " + e.data);
};

Метод 2. Прямое использование Workerman для установки SSL (не рекомендуется)

Внимание
Выберите либо прокси SSL с помощью nginx/apache, либо настройку SSL в Workerman, не используйте их одновременно.

Подготовка:

  1. Версия Workerman >= 3.3.7

  2. PHP установлен с расширением openssl

  3. Сертификат уже получен (файлы pem/crt и key) находятся в любом каталоге на диске

Код:

<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// Лучше использовать полученный сертификат
$context = array(
    // Для получения дополнительных параметров ssl обратитесь к документации http://php.net/manual/zh/context.ssl.php
    'ssl' => array(
        // Пожалуйста, используйте абсолютный путь
        'local_cert'        => 'путь_на_диске/server.pem', // Это также может быть crt файл
        'local_pk'          => 'путь_на_диске/server.key',
        'verify_peer'       => false,
        'allow_self_signed' => true, // Если это самоподписанный сертификат, необходимо открыть эту опцию
    )
);
// Здесь устанавливается websocket протокол (порт любой, но необходимо, чтобы он не был занят другими программами)
$worker = new Worker('websocket://0.0.0.0:8282', $context);
// Устанавливаем транспорт для включения ssl, websocket+ssl это wss
$worker->transport = 'ssl';
$worker->onMessage = function(TcpConnection $con, $msg) {
    $con->send('ok');
};

Worker::runAll();

С помощью этого кода, Workerman будет слушать протокол wss, клиент сможет подключаться к Workerman через wss для безопасного мгновенного общения.

Тест

Откройте браузер Chrome, нажмите F12 для открытия консоли разработчиков, введите в разделе Console (или вставьте следующий код в HTML и выполните его с помощью js)

// Сертификат проверяет доменное имя, пожалуйста, используйте домен для подключения, обратите внимание, что здесь указан порт
ws = new WebSocket("wss://домен.com:8282");
ws.onopen = function() {
    alert("Подключение успешно");
    ws.send('tom');
    alert("Отправка строки 'tom' на сервер");
};
ws.onmessage = function(e) {
    alert("Получено сообщение с сервера: " + e.data);
};

Распространенные ошибки
SSL routines:SSL23_GET_CLIENT_HELLO:http request
Причина заключается в том, что клиент пытается получить доступ с использованием ws://домен.com, правильный адрес доступа должен быть wss://домен.com, т.е. доступ с префиксом wss. Эта проблема часто возникает, когда порт изначально был ws, а затем внезапно был изменен на wss, так как некоторые клиентские страницы не обновились и продолжают использовать ws, что приводит к ошибке.
Эта ошибка подлежит игнорированию и не влияет на нормальное соединение wss.

Внимание:

  1. Если необходимо использовать порт 443, пожалуйста, используйте первый вариант с прокси nginx/apache для реализации wss.

  2. Порт wss может быть доступен только через протокол wss, нельзя получить доступ к wss порту через ws.

  3. Сертификаты обычно связаны с доменом, поэтому тестируя, пожалуйста, используйте домен для подключения, не используйте IP.

  4. Если возникли проблемы с доступом, пожалуйста, проверьте настройки брандмауэра сервера.

  5. Этот метод требует, чтобы версия PHP была >= 5.6, так как приложения WeChat Mini Program требуют tls1.2, а версии PHP ниже 5.6 не поддерживают tls1.2.

Связанные статьи:
Получение реального IP клиента через прокси
Справочные параметры SSL контекста Workerman