Criar serviço wss

Pergunta:

Como criar um serviço wss com Workerman, para que o cliente possa se conectar e se comunicar através do protocolo wss, por exemplo, conectar o servidor em um mini programa WeChat.

Resposta:

O protocolo wss na verdade é websocket+SSL, que adiciona uma camada SSL ao protocolo websocket, semelhante ao https (http+SSL). Portanto, tudo o que você precisa fazer é ativar o SSL com base no protocolo websocket para suportar o protocolo wss.

Método 1: Usando nginx/apache para proxy SSL (recomendado)

Razões para recomendação

  • Pode reutilizar a porta 443, o cliente não precisa especificar a porta ao conectar
  • Gerenciamento centralizado de certificados SSL, pode reutilizar a configuração do site
  • Pode realizar balanceamento de carga
  • Possui monitoramento de logs integrado
  • Melhor compatibilidade

Princípio e fluxo de comunicação

  1. O cliente inicia a conexão wss para o nginx/apache.
  2. O nginx/apache converte os dados do protocolo wss em dados do protocolo ws e os encaminha para a porta do protocolo websocket do Workerman.
  3. O Workerman processa os dados recebidos conforme a lógica de negócios.
  4. Quando o Workerman envia uma mensagem ao cliente, o processo é o inverso: os dados são convertidos pelo nginx/apache para o protocolo wss e enviados ao cliente.

Exemplo de configuração do nginx

Pré-requisitos e preparação:

  1. O nginx já está instalado, versão não inferior a 1.3.
  2. Suponha que o Workerman está ouvindo a porta 8282 (protocolo websocket).
  3. Um certificado já foi solicitado (arquivo pem/crt e arquivo key), e supõe-se que está localizado em /etc/nginx/conf.d/ssl.
  4. Pretende-se usar o nginx para ativar a porta 443 para fornecer um serviço de proxy wss (a porta pode ser modificada conforme necessário).
  5. O nginx geralmente funciona como servidor de sites com outros serviços, para não afetar o uso do site original, aqui é utilizada a URL dominio.com/wss como ingresso para o proxy wss. Ou seja, o endereço de conexão do cliente é wss://dominio.com/wss.

A configuração do nginx é semelhante ao seguinte:

server {
  listen 443;
  # configuração do domínio omitida...

  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 / {} outras configurações do site...
}

Teste

// O certificado irá verificar o domínio, por favor, use o domínio para se conectar. Note que aqui não se escreve a porta
ws = new WebSocket("wss://dominio.com/wss");

ws.onopen = function() {
    alert("Conexão bem-sucedida");
    ws.send('tom');
    alert("Enviando uma string para o servidor: tom");
};
ws.onmessage = function(e) {
    alert("Mensagem recebida do servidor: " + e.data);
};

Usando apache para proxy wss

Você também pode usar o apache como um proxy wss para encaminhar para o Workerman.

Preparação:

  1. GatewayWorker ouvindo a porta 8282 (protocolo websocket).
  2. Um certificado ssl já foi solicitado, supõe-se que esteja localizado em /server/httpd/cert/.
  3. O apache irá encaminhar a porta 443 para a porta 8282 especificada.
  4. httpd-ssl.conf já foi carregado.
  5. openssl já está instalado.

Habilitar o módulo proxy_wstunnel_module

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

Configuração SSL e proxy

#extra/httpd-ssl.conf
DocumentRoot "/diretório/do/site"
ServerName dominio

# Configuração do Proxy
SSLProxyEngine on

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

# Adicione suporte para protocolos SSL, remova protocolos inseguros
SSLProtocol all -SSLv2 -SSLv3
# Modifique a suíte de criptografia como segue
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# Configuração da chave pública do certificado
SSLCertificateFile /server/httpd/cert/your.pem
# Configuração da chave privada do certificado
SSLCertificateKeyFile /server/httpd/cert/your.key
# Configuração da cadeia de certificados,
SSLCertificateChainFile /server/httpd/cert/chain.pem

Teste

// O certificado irá verificar o domínio, por favor, use o domínio para se conectar. Note que não há porta
ws = new WebSocket("wss://dominio.com/wss");

ws.onopen = function() {
    alert("Conexão bem-sucedida");
    ws.send('tom');
    alert("Enviando uma string para o servidor: tom");
};
ws.onmessage = function(e) {
    alert("Mensagem recebida do servidor: " + e.data);
};

Método 2: Usando diretamente o Workerman para ativar SSL (não recomendado)

Atenção
Use o proxy SSL nginx/apache ou a configuração SSL do Workerman, não ambos ao mesmo tempo.

Preparação:

  1. Versão do Workerman >= 3.3.7.
  2. O PHP tem a extensão openssl instalada.
  3. Um certificado já foi solicitado (arquivo pem/crt e arquivo key) e está em qualquer diretório no disco.

Código:

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

// O certificado deve ser um certificado solicitado
$context = array(
    // Mais opções ssl podem ser encontradas na documentação http://php.net/manual/zh/context.ssl.php
    'ssl' => array(
        // Por favor, use o caminho absoluto
        'local_cert'        => 'caminho/do/disco/server.pem', // também pode ser um arquivo crt
        'local_pk'          => 'caminho/do/disco/server.key',
        'verify_peer'       => false,
        'allow_self_signed' => true, // se for um certificado autoassinado, esta opção precisa ser ativada
    )
);
// Aqui é configurado o protocolo websocket (a porta pode ser qualquer um, mas deve garantir que não esteja ocupada por outros programas)
$worker = new Worker('websocket://0.0.0.0:8282', $context);
// Definindo o transport como ssl, websocket + ssl ou wss
$worker->transport = 'ssl';
$worker->onMessage = function(TcpConnection $con, $msg) {
    $con->send('ok');
};

Worker::runAll();

Com o código acima, o Workerman estará ouvindo o protocolo wss, e o cliente poderá se conectar ao Workerman via wss para realizar comunicação segura e em tempo real.

Teste

Abra o navegador Chrome, pressione F12 para abrir a console de depuração e digite (ou coloque o código abaixo em uma página HTML para executá-lo com js)

// O certificado irá verificar o domínio, por favor, use o domínio para se conectar, note que aqui tem um número de porta
ws = new WebSocket("wss://dominio.com:8282");
ws.onopen = function() {
    alert("Conexão bem-sucedida");
    ws.send('tom');
    alert("Enviando uma string para o servidor: tom");
};
ws.onmessage = function(e) {
    alert("Mensagem recebida do servidor: " + e.data);
};

Erros comuns
SSL routines:SSL23_GET_CLIENT_HELLO:http request
A causa é que o cliente está tentando acessar ws://dominio.com, a URL correta deveria ser wss://dominio.com, ou seja, deve começar com wss. A maior parte dos casos em que isso ocorre é que a porta originalmente era ws, e repentinamente se tornou wss, certos clientes em algumas páginas não foram atualizados e ainda acessam via ws, resultando em erro.
Esse erro pode ser ignorado, não afeta a conexão normal wss.

Atenção:

  1. Se for necessário usar a porta 443, use o primeiro método de proxy com nginx/apache para implementar wss.
  2. A porta wss só pode ser acessada através do protocolo wss, ws não pode acessar a porta wss.
  3. O certificado geralmente é vinculado ao domínio, portanto, ao testar, o cliente deve usar o domínio, não deve usar o IP.
  4. Se houver problemas de acesso, verifique o firewall do servidor.
  5. Este método requer PHP versão >= 5.6, porque o mini programa WeChat requer tls1.2, e versões do PHP abaixo de 5.6 não suportam tls1.2.

Artigos relacionados:
Obter o IP real do cliente através do proxy
Referência das opções de contexto SSL do Workerman