From 367991232454000e83312462419396f0da099378 Mon Sep 17 00:00:00 2001 From: Marc Roberts Date: Mon, 14 Jun 2021 15:59:00 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Throw=20client=20read=20timeout=20earlier?= =?UTF-8?q?=20and=20don=E2=80=99t=20close=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Base.php | 7 +++- tests/ClientTest.php | 12 ++++++ tests/scripts/receive-broken-read.json | 17 +++++++- tests/scripts/receive-client-timeout.json | 50 +++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/scripts/receive-client-timeout.json diff --git a/lib/Base.php b/lib/Base.php index 27e9113..5e63103 100644 --- a/lib/Base.php +++ b/lib/Base.php @@ -443,6 +443,12 @@ protected function read(string $length): string while (strlen($data) < $length) { $buffer = @fread($this->socket, $length - strlen($data)); if ($buffer === false) { + $meta = stream_get_meta_data($this->socket); + if (!empty($meta['timed_out'])) { + $message = 'Client read timeout'; + $this->logger->error($message, $meta); + throw new TimeoutException($message, ConnectionException::TIMED_OUT, $meta); + } $read = strlen($data); $this->throwException("Broken frame, read {$read} of stated {$length} bytes."); } @@ -464,7 +470,6 @@ protected function throwException(string $message, int $code = 0): void fclose($this->socket); $this->socket = null; } - $json_meta = json_encode($meta); if (!empty($meta['timed_out'])) { $this->logger->error($message, $meta); throw new TimeoutException($message, ConnectionException::TIMED_OUT, $meta); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9d62e12..c7bdc16 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -353,6 +353,18 @@ public function testBrokenRead(): void $client->receive(); } + public function testReadTimeout(): void + { + MockSocket::initialize('client.connect', $this); + $client = new Client('ws://localhost:8000/my/mock/path'); + $client->send('Connect'); + MockSocket::initialize('receive-client-timeout', $this); + $this->expectException('WebSocket\TimeoutException'); + $this->expectExceptionCode(1024); + $this->expectExceptionMessage('Client read timeout'); + $client->receive(); + } + public function testEmptyRead(): void { MockSocket::initialize('client.connect', $this); diff --git a/tests/scripts/receive-broken-read.json b/tests/scripts/receive-broken-read.json index e33f23b..15e3fa9 100644 --- a/tests/scripts/receive-broken-read.json +++ b/tests/scripts/receive-broken-read.json @@ -11,6 +11,21 @@ "params": [], "return": false }, + { + "function": "stream_get_meta_data", + "params": [ + "@mock-stream" + ], + "return": { + "timed_out": false, + "blocked": true, + "eof": true, + "stream_type": "tcp_socket\/ssl", + "mode": "r+", + "unread_bytes": 2, + "seekable": false + } + }, { "function": "get_resource_type", "params": [ @@ -40,4 +55,4 @@ ], "return":true } -] \ No newline at end of file +] diff --git a/tests/scripts/receive-client-timeout.json b/tests/scripts/receive-client-timeout.json new file mode 100644 index 0000000..5a258b0 --- /dev/null +++ b/tests/scripts/receive-client-timeout.json @@ -0,0 +1,50 @@ +[ + { + "function": "get_resource_type", + "params": [ + "@mock-stream" + ], + "return": "stream" + }, + { + "function": "fread", + "params": [], + "return": false + }, + { + "function": "stream_get_meta_data", + "params": [ + "@mock-stream" + ], + "return": { + "timed_out": true, + "blocked": true, + "eof": false, + "stream_type": "tcp_socket\/ssl", + "mode": "r+", + "unread_bytes": 0, + "seekable": false + } + }, + { + "function": "get_resource_type", + "params": [ + "@mock-stream" + ], + "return": "stream" + }, + { + "function": "get_resource_type", + "params": [ + "@mock-stream" + ], + "return": "stream" + }, + { + "function": "fclose", + "params": [ + "@mock-stream" + ], + "return":true + } +] From 953a5c6e5ff1228b69af75b430ff9bae28c400d0 Mon Sep 17 00:00:00 2001 From: Marc Roberts Date: Tue, 29 Jun 2021 10:06:13 +0100 Subject: [PATCH 2/2] Check for client timeout on falsey result of `fread()` --- lib/Base.php | 5 ++++- tests/scripts/receive-empty-read.json | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Base.php b/lib/Base.php index 5e63103..f460289 100644 --- a/lib/Base.php +++ b/lib/Base.php @@ -442,13 +442,16 @@ protected function read(string $length): string $data = ''; while (strlen($data) < $length) { $buffer = @fread($this->socket, $length - strlen($data)); - if ($buffer === false) { + + if (!$buffer) { $meta = stream_get_meta_data($this->socket); if (!empty($meta['timed_out'])) { $message = 'Client read timeout'; $this->logger->error($message, $meta); throw new TimeoutException($message, ConnectionException::TIMED_OUT, $meta); } + } + if ($buffer === false) { $read = strlen($data); $this->throwException("Broken frame, read {$read} of stated {$length} bytes."); } diff --git a/tests/scripts/receive-empty-read.json b/tests/scripts/receive-empty-read.json index f428726..f752017 100644 --- a/tests/scripts/receive-empty-read.json +++ b/tests/scripts/receive-empty-read.json @@ -11,6 +11,21 @@ "params": [], "return": "" }, + { + "function": "stream_get_meta_data", + "params": [ + "@mock-stream" + ], + "return": { + "timed_out": false, + "blocked": true, + "eof": false, + "stream_type": "tcp_socket\/ssl", + "mode": "r+", + "unread_bytes": 0, + "seekable": false + } + }, { "function": "get_resource_type", "params": [