Files

4.9 KiB

Realtime WebSocket Broker

web-app-starter includes an optional WebSocket-to-PHP broker under server/realtime-broker/.

The broker is deliberately generic: it owns persistent WebSocket connections, while PHP decides app-specific behavior through a normal HTTP endpoint.

Files

  • server/realtime-broker/broker.js - broker daemon and importable RealtimeBroker class
  • server/realtime-broker/vendor/ws/ - vendored ws 8.14.2, MIT license
  • server/realtime-broker/test-broker.js - broker integration test
  • lib/realtime.class.php - PHP helper for backend-initiated commands
  • views/api/realtime.php - starter demo backend endpoint
  • views/realtime-demo.php - browser demo page
  • js/u-wsconnection.js - small browser connection helper

Start locally

cd /path/to/web-app-starter
REALTIME_HOST=127.0.0.1 \
REALTIME_PORT=8009 \
REALTIME_BACKEND_URL=http://127.0.0.1/web-app-starter/?api/realtime \
REALTIME_COMMAND_TOKEN=change-me \
node server/realtime-broker/broker.js

Then open /?realtime-demo and connect.

Browser URL

In production, proxy /live/ to the broker and let the browser connect to the same origin:

location /live/ {
    proxy_pass http://127.0.0.1:8009/live/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 3600s;
}

If the broker is elsewhere, set REALTIME_LIVE_URL or config/settings.php realtime/live_url.

PHP backend protocol

For every browser connect/message/disconnect, the broker POSTs URL-encoded fields to REALTIME_BACKEND_URL:

  • message: JSON message
  • connection: JSON connection/session info

Message types:

  • session-connect
  • session-disconnect
  • browser messages prefixed as client-* unless already prefixed

The PHP endpoint returns a JSON array of commands.

<?php
$message = json_decode($_POST['message'] ?? '{}', true);
$connection = json_decode($_POST['connection'] ?? '{}', true);

print(json_encode([
    ['type' => 'session', 'data' => ['room' => 'demo']],
    ['type' => 'send', 'match' => ['room' => 'demo'], 'message' => ['type' => 'notice', 'text' => 'hello']],
]));

Backend commands

session

Merge data into connection session info. Without match, applies to the current connection.

{"type":"session","data":{"room":"demo","user_id":123}}

send

Send a JSON message to the current connection or matching connections.

{"type":"send","match":{"room":"demo"},"message":{"type":"chat","text":"hello"}}

close

Close the current or matching connections.

{"type":"close","match":{"user_id":123}}

list

Return matching connection sessions to a command caller.

{"type":"list","match":{"room":"demo"}}

log

Log a message if broker logging is enabled.

PHP helper

Realtime wraps command POSTs to the broker:

Realtime::broadcast(['type' => 'notice', 'text' => 'hello'], ['room' => 'demo']);
Realtime::session(['room' => 'demo'], ['user_id' => 123]);
Realtime::connections(['room' => 'demo']);
Realtime::close(['user_id' => 123]);

Configure:

$GLOBALS['config']['realtime'] = [
    'broker_url' => 'http://127.0.0.1:8009/',
    'command_token' => getenv('REALTIME_COMMAND_TOKEN') ?: '',
];

Command security

If REALTIME_COMMAND_TOKEN is set, command POSTs must include one of:

  • token=<token> form field
  • Authorization: Bearer <token>
  • X-Realtime-Token: <token>

If no token is set, broker commands are accepted only from REALTIME_COMMAND_ALLOW IPs. Use a token for new deployments.

Broker status

curl http://127.0.0.1:8009/status

Returns JSON counters including active connections, message counts, bytes, backend errors, and denied command attempts.

systemd example

/etc/systemd/system/web-app-starter-realtime.service:

[Unit]
Description=web-app-starter realtime broker
After=network.target

[Service]
Type=simple
WorkingDirectory=/var/www/web-app-starter
Environment=REALTIME_HOST=127.0.0.1
Environment=REALTIME_PORT=8009
Environment=REALTIME_BACKEND_URL=http://127.0.0.1/web-app-starter/?api/realtime
Environment=REALTIME_COMMAND_TOKEN=change-me
ExecStart=/usr/bin/node server/realtime-broker/broker.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

Validation

node --check server/realtime-broker/broker.js
node --check server/realtime-broker/test-broker.js
node server/realtime-broker/test-broker.js
php -l lib/realtime.class.php
php tests/smoke.php

Notes

  • The broker is optional and not started by the PHP app.
  • Keep app policy in PHP; keep the broker generic.
  • Do not put secrets in messages sent to clients.
  • Tune REALTIME_MAX_MESSAGE_BYTES, REALTIME_MAX_BUFFERED_BYTES, and backend timeout for larger apps.