swoft-cloud / swoft

🚀 PHP Microservice Full Coroutine Framework
https://swoft.org
Apache License 2.0
5.58k stars 786 forks source link

WS头信息KEY的大小写被强制替换,导致很多三方WS客户端连接失败问题 #1093

Closed tanpeng closed 4 years ago

tanpeng commented 5 years ago
Q A
Bug report? yes
Swoft version 2.0.6
Swoole version 4.4.4
PHP version 7.2.20

在使用一些第三方的WS客户端库时候,发现swoft2的ws连接失败,使用其他框架可以正常连接。 抓包发现唯一差异在WS头信息KEY的大小写不同上,(Sec-WebSocket-Accept,Sec-WebSocket-Version,Sec-WebSocket-Protocol等)。 研究了下代码发现header方法的第三个参数$ucwords无效,默认null时还是默认做了KEY转换处理。 否则ucwords在true、false、null三种情况,为null时默认原始请求头。

public function header(string $key, $value, $ucwords = null){}

quickSend返还时header默认ucwords = null

public function quickSend(Response $response = null): void
    {
        $response = $response ?: $this;
        // Ensure coResponse is right
        $coResponse = $response->getCoResponse();
        // Write Headers to co response
        foreach ($response->getHeaders() as $key => $value) {
            $headerLine = implode(';', $value);
            if ($key === ContentType::KEY) {
                $headerLine .= '; charset=' . $response->getCharset();
                $coResponse->header($key, $headerLine);
            } else {
                $coResponse->header($key, $headerLine);
            }
        }
        // Write cookies
        foreach ($response->cookies as $n => $c) {
            $coResponse->cookie($n, $c['value'], $c['expires'], $c['path'], $c['domain'], $c['secure'], $c['httpOnly']);
        }
        // Set status code
        $coResponse->status($response->getStatusCode());
        // Set body
        $content = $response->getBody()->getContents();
        $coResponse->end($content);
        // Ensure sent
        $this->sent = true;
    }
tanpeng commented 5 years ago

看了下代码不是swoft的问题,是swoole里header方法第三个参数默认true导致,WebSocket 被转成 Websocket了。 swoft在send之前,已经对header信息做了处理,是否在调用$coResponse->header($key, $headerLine)的时候,第三个参数可以默认false了。这样避免在输出后又发生一些改变,很难排查。

inhere commented 5 years ago

@tanpeng 你到源码里改下 $coResponse->header($key, $headerLine) 加上 false. 确认下是这里导致的问题吗? 是的话下个版本调整下

tanpeng commented 5 years ago
if ($key === ContentType::KEY) {
    $headerLine .= '; charset=' . $response->getCharset();
    $coResponse->header($key, $headerLine);
} else {
    $coResponse->header($key, $headerLine, false);
}

设为false,客户端可以正常连接了。 现在可以确定的是第三个参数默认true时, 把含有 WebSocket => Websocket 转换导致。 WS这边主要涉及Sec-WebSocket-Accept,Sec-WebSocket-Version,Sec-WebSocket-Protocol。 我不确定 $coResponse->header($key, $headerLine, false); 默认为false,其他模块是否会受到影响。

inhere commented 5 years ago

ok @tanpeng 谢谢。 我这考虑下怎么改