Create a WSS Service

Question:

How does Workerman create a WSS service so that clients can connect and communicate via the WSS protocol, such as connecting to a server in a WeChat Mini Program?

Answer:

The WSS protocol is essentially websocket + SSL, which adds an SSL layer on top of the websocket protocol, similar to HTTPS (which is HTTP + SSL). Therefore, you only need to enable SSL on top of the websocket protocol to support WSS.

Method 1: Using Nginx/Apache to Proxy SSL (Recommended)

Recommendations:

  • It allows reusing port 443, so clients do not need to specify the port upon connection.
  • Centralized management of SSL certificates, allowing reuse of website configuration.
  • Load balancing capabilities.
  • Built-in logging and monitoring.
  • Better compatibility.

Communication Principles and Flow:

  1. The client initiates a WSS connection to Nginx/Apache.
  2. Nginx/Apache converts WSS protocol data to WS protocol data and forwards it to Workerman's websocket protocol port.
  3. Workerman processes the data and performs business logic.
  4. When Workerman sends a message back to the client, the reverse process occurs, with data converted by Nginx/Apache to WSS protocol before being sent to the client.

Nginx Configuration Reference

Prerequisites and Preparations:

  1. Nginx installed, with a version of at least 1.3.
  2. Assume Workerman listens on port 8282 (websocket protocol).
  3. A certificate has been obtained (pem/crt files and the key file) and is assumed to be placed in /etc/nginx/conf.d/ssl.
  4. Planning to use Nginx to enable port 443 to provide WSS proxy service (the port can be modified as needed).
  5. Nginx typically runs as a web server servicing other services, so to avoid affecting the original site, this setup uses the address 域名.com/wss as the WSS proxy entry. Hence, the client connection address will be wss://域名.com/wss.

Nginx configuration similar to the following:

server {
  listen 443;
  # Domain configuration omitted...

  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 / {} Other configurations for the site...
}

Testing

// The certificate will check the domain, please connect using the domain. Note that no port is specified.
ws = new WebSocket("wss://域名.com/wss");

ws.onopen = function() {
    alert("Connection successful");
    ws.send('tom');
    alert("Sent a string to the server: tom");
};
ws.onmessage = function(e) {
    alert("Received message from the server: " + e.data);
};

Using Apache to Proxy WSS

Apache can also be used as a WSS proxy forwarding to Workerman.

Preparations:

  1. GatewayWorker listens on port 8282 (websocket protocol).
  2. A SSL certificate has been obtained and is assumed to be placed in /server/httpd/cert/.
  3. Apache forwards port 443 to the specified port 8282.
  4. httpd-ssl.conf has been loaded.
  5. OpenSSL has been installed.

Enable the proxy_wstunnel_module module

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

Configure SSL and Proxy

#extra/httpd-ssl.conf
DocumentRoot "/网站/目录"
ServerName 域名

# Proxy Config
SSLProxyEngine on

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

# Add SSL protocol support and remove insecure protocols
SSLProtocol all -SSLv2 -SSLv3
# Change the cipher suite as follows
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# Certificate public key configuration
SSLCertificateFile /server/httpd/cert/your.pem
# Certificate private key configuration
SSLCertificateKeyFile /server/httpd/cert/your.key
# Certificate chain configuration
SSLCertificateChainFile /server/httpd/cert/chain.pem

Testing

// The certificate will check the domain, please connect using the domain. Note that there is no port specified.
ws = new WebSocket("wss://域名.com/wss");

ws.onopen = function() {
    alert("Connection successful");
    ws.send('tom');
    alert("Sent a string to the server: tom");
};
ws.onmessage = function(e) {
    alert("Received message from the server: " + e.data);
};

Method 2: Directly Use Workerman to Enable SSL (Not Recommended)

Note
You can only choose either Nginx/Apache to proxy SSL or to set SSL in Workerman; both cannot be enabled simultaneously.

Preparations:

  1. Workerman version >= 3.3.7.
  2. PHP has the OpenSSL extension installed.
  3. A certificate has been obtained (pem/crt files and key files) and placed in any directory on the disk.

Code:

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

// It is best to use a certificate that has been applied for
$context = array(
    // More SSL options can be referenced in the manual http://php.net/manual/zh/context.ssl.php
    'ssl' => array(
        // Please use absolute paths
        'local_cert'        => '磁盘路径/server.pem', // Can also be a crt file
        'local_pk'          => '磁盘路径/server.key',
        'verify_peer'       => false,
        'allow_self_signed' => true, // This option needs to be enabled if it's a self-signed certificate
    )
);
// Here the websocket protocol is set (port can be any, but it must not be occupied by other programs)
$worker = new Worker('websocket://0.0.0.0:8282', $context);
// Set transport to enable SSL, websocket + SSL means WSS
$worker->transport = 'ssl';
$worker->onMessage = function(TcpConnection $con, $msg) {
    $con->send('ok');
};

Worker::runAll();

With the above code, Workerman listens for the WSS protocol, and clients can connect to Workerman via the WSS protocol for secure instant communication.

Testing

Open the Chrome browser, press F12 to open the debugging console, and enter in the Console section (or put the following code into an HTML page to run with JS)

// The certificate will check the domain, please connect using the domain, note that there is a port number here
ws = new WebSocket("wss://域名.com:8282");
ws.onopen = function() {
    alert("Connection successful");
    ws.send('tom');
    alert("Sent a string to the server: tom");
};
ws.onmessage = function(e) {
    alert("Received message from the server: " + e.data);
};

Common Errors
SSL routines:SSL23_GET_CLIENT_HELLO:http request
The reason is that the client accessed via ws://域名.com, and the correct access address should start with wss://域名.com. This problem often occurs when the port was originally WS and suddenly changed to WSS, with some client pages not refreshed and still accessing with WS, leading to the error. This error can be ignored; it does not affect normal WSS connections.

Note:

  1. If you must use port 443, please use the first method of Nginx/Apache proxy for WSS.
  2. WSS ports can only be accessed via the WSS protocol; WS cannot access WSS ports.
  3. Certificates are generally bound to the domain, so when testing, clients please connect using the domain and not the IP.
  4. If there are access issues, please check the server firewall.
  5. This method requires PHP version >= 5.6 because WeChat Mini Program requires TLS 1.2, which is not supported in versions below PHP 5.6.

Related Articles:
Get Client Real IP through Proxy
Workerman's SSL Context Option Reference