walkor/workerman · error · RuntimeException

Invalid protocol scheme '$scheme'

Error message

Invalid protocol scheme '$scheme'

What it means

When the scheme is not one of the built-in transports (tcp, udp, unix, ssl, sslv2, sslv3, tls), Workerman treats it as an application-layer protocol class name and must build a PHP class name from it. Before resolving '\Protocols\<Scheme>' it validates the scheme against /^[a-zA-Z][a-zA-Z0-9]*$/ and throws when the scheme contains characters unsafe for class-name resolution (hyphens, dots, digits-first, symbols).

Source

Thrown at src/Connection/AsyncTcpConnection.php:215

            }
            $this->remoteHost = $addressInfo['host'];
            $this->remotePort = $addressInfo['port'];
            $this->remoteURI = "{$addressInfo['path']}{$addressInfo['query']}";
            $scheme = $addressInfo['scheme'] ?? 'tcp';
            $this->remoteAddress = 'unix' === strtolower($scheme)
                ? substr($remoteAddress, strpos($remoteAddress, '/') + 2)
                : $this->remoteHost . ':' . $this->remotePort;
        }

        $this->id = $this->realId = self::$idRecorder++;
        if (PHP_INT_MAX === self::$idRecorder) {
            self::$idRecorder = 0;
        }
        // Check application layer protocol class.
        if (!isset(self::BUILD_IN_TRANSPORTS[$scheme])) {
            // Validate scheme contains only safe characters for class name resolution.
            if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $scheme)) {
                throw new RuntimeException("Invalid protocol scheme '$scheme'");
            }
            $scheme = ucfirst($scheme);
            $this->protocol = '\\Protocols\\' . $scheme;
            if (!class_exists($this->protocol)) {
                $this->protocol = "\\Workerman\\Protocols\\$scheme";
                if (!class_exists($this->protocol)) {
                    throw new RuntimeException("class \\Protocols\\$scheme not exist");
                }
            }
        } else {
            $this->transport = self::BUILD_IN_TRANSPORTS[$scheme];
        }

        // For statistics.
        ++self::$statistics['connection_count'];
        $this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
        $this->maxPackageSize = self::$defaultMaxPackageSize;
        $this->socketContext = $socketContext;

View on GitHub (pinned to 1391112a61)

Solutions

  1. Rename the custom protocol scheme to letters/digits only starting with a letter, e.g. 'myproto://host:1234', and name the class Protocols\Myproto
  2. Use a built-in transport scheme (tcp, udp, unix, ssl, tls) when no custom application protocol is needed
  3. Fix the address typo so the scheme section is a clean identifier

Example fix

// before
$conn = new AsyncTcpConnection('my-proto://127.0.0.1:9000'); // throws Invalid protocol scheme 'my-proto'

// after
$conn = new AsyncTcpConnection('myproto://127.0.0.1:9000'); // resolves Protocols\Myproto
Defensive patterns

Strategy: validation

Validate before calling

$scheme = strtolower(explode('://', (string)$address, 2)[0] ?? '');
if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $scheme)) {
    throw new InvalidArgumentException("Bad protocol scheme in address: $address");
}
$conn = new AsyncTcpConnection($address);

Prevention

When it happens

Trigger: new AsyncTcpConnection('my-proto://host:1234') (hyphen in scheme), a scheme starting with a digit such as '2fa://host', a scheme with a dot or slash like 'app.v2://host', or a malformed raw address whose text before the first ':' contains junk after parse_url() failed.

Common situations: Teams naming custom protocols with dashes (my-protocol://) instead of alphanumeric PascalCase; copy-pasting a URL with credentials or a subdomain-like scheme; typos like 'tcp-ssl://'; addresses built by string concatenation that leave stray characters before ':'.

Related errors


AI-assisted analysis of walkor/workerman@1391112a61 (2026-08-21). Data as JSON: /api/errors/62b29581ce9e0d1d. Report an issue: GitHub.