代码同步

This commit is contained in:
Ghost
2025-03-24 14:59:19 +08:00
parent f0fa19f89f
commit bd2e1e5386
716 changed files with 90318 additions and 26155 deletions

View File

@@ -0,0 +1,21 @@
---
name: Bug report
about: Use this if you believe there is a bug in this repo
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
Please provide a clear and concise description of the suspected issue.
**How to reproduce**
If possible, provide information - possibly including code snippets - on how to reproduce the issue.
**Logs**
If possible, provide logs that indicate the issue. See https://github.com/Textalk/websocket-php/blob/master/docs/Examples.md#echo-logger on how to use the EchoLog.
**Versions**
* Version of this library
* PHP version

View File

@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this library
title: ''
labels: feature request
assignees: ''
---
**Is it within the scope of this library?**
Consider and describe why the feature would be beneficial in this library, and not implemented as a separate project using this as a dependency.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.

View File

@@ -0,0 +1,10 @@
---
name: Other issue
about: Use this for other issues
title: ''
labels: ''
assignees: ''
---
**Describe your issue**

View File

@@ -0,0 +1,113 @@
name: Acceptance
on: [push, pull_request]
jobs:
test-7-2:
runs-on: ubuntu-latest
name: Test PHP 7.2
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 7.2
uses: shivammathur/setup-php@v2
with:
php-version: '7.2'
- name: Composer
run: make install
- name: Test
run: make test
test-7-3:
runs-on: ubuntu-latest
name: Test PHP 7.3
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 7.3
uses: shivammathur/setup-php@v2
with:
php-version: '7.3'
- name: Composer
run: make install
- name: Test
run: make test
test-7-4:
runs-on: ubuntu-latest
name: Test PHP 7.4
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 7.4
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
- name: Composer
run: make install
- name: Test
run: make test
test-8-0:
runs-on: ubuntu-latest
name: Test PHP 8.0
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 8.0
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
- name: Composer
run: make install
- name: Test
run: make test
test-8-1:
runs-on: ubuntu-latest
name: Test PHP 8.1
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 8.1
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- name: Composer
run: make install
- name: Test
run: make test
cs-check:
runs-on: ubuntu-latest
name: Code standard
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 8.0
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
- name: Composer
run: make install
- name: Code standard
run: make cs-check
coverage:
runs-on: ubuntu-latest
name: Code coverage
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP 8.0
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
extensions: xdebug
- name: Composer
run: make install
- name: Code coverage
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: make coverage

View File

@@ -0,0 +1,6 @@
.DS_Store
.phpunit.result.cache
build/
composer.lock
composer.phar
vendor/

View File

@@ -0,0 +1,16 @@
# Websocket: License
Websocket PHP is free software released under the following license:
[ISC License](http://en.wikipedia.org/wiki/ISC_license)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without
fee is hereby granted, provided that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@@ -0,0 +1,32 @@
install: composer.phar
./composer.phar install
update: composer.phar
./composer.phar self-update
./composer.phar update
test: composer.lock
./vendor/bin/phpunit
cs-check: composer.lock
./vendor/bin/phpcs --standard=codestandard.xml lib tests examples
coverage: composer.lock build
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml
./vendor/bin/php-coveralls -v
composer.phar:
curl -s http://getcomposer.org/installer | php
composer.lock: composer.phar
./composer.phar --no-interaction install
vendor/bin/phpunit: install
build:
mkdir build
clean:
rm composer.phar
rm -r vendor
rm -r build

View File

@@ -0,0 +1,67 @@
# Websocket Client and Server for PHP
[![Build Status](https://github.com/Textalk/websocket-php/actions/workflows/acceptance.yml/badge.svg)](https://github.com/Textalk/websocket-php/actions)
[![Coverage Status](https://coveralls.io/repos/github/Textalk/websocket-php/badge.svg?branch=master)](https://coveralls.io/github/Textalk/websocket-php)
This library contains WebSocket client and server for PHP.
The client and server provides methods for reading and writing to WebSocket streams.
It does not include convenience operations such as listeners and implicit error handling.
## Documentation
- [Client](docs/Client.md)
- [Server](docs/Server.md)
- [Message](docs/Message.md)
- [Examples](docs/Examples.md)
- [Changelog](docs/Changelog.md)
- [Contributing](docs/Contributing.md)
## Installing
Preferred way to install is with [Composer](https://getcomposer.org/).
```
composer require textalk/websocket
```
* Current version support PHP versions `^7.2|^8.0`.
* For PHP `7.1` support use version [`1.4`](https://github.com/Textalk/websocket-php/tree/1.4.0).
* For PHP `^5.4` and `7.0` support use version [`1.3`](https://github.com/Textalk/websocket-php/tree/1.3.0).
## Client
The [client](docs/Client.md) can read and write on a WebSocket stream.
It internally supports Upgrade handshake and implicit close and ping/pong operations.
```php
$client = new WebSocket\Client("ws://echo.websocket.org/");
$client->text("Hello WebSocket.org!");
echo $client->receive();
$client->close();
```
## Server
The library contains a rudimentary single stream/single thread [server](docs/Server.md).
It internally supports Upgrade handshake and implicit close and ping/pong operations.
Note that it does **not** support threading or automatic association ot continuous client requests.
If you require this kind of server behavior, you need to build it on top of provided server implementation.
```php
$server = new WebSocket\Server();
$server->accept();
$message = $server->receive();
$server->text($message);
$server->close();
```
### License and Contributors
[ISC License](COPYING.md)
Fredrik Liljegren, Armen Baghumian Sankbarani, Ruslan Bekenev,
Joshua Thijssen, Simon Lipp, Quentin Bellus, Patrick McCarren, swmcdonnell,
Ignas Bernotas, Mark Herhold, Andreas Palm, Sören Jensen, pmaasz, Alexey Stavrov,
Michael Slezak, Pierre Seznec, rmeisler, Nickolay V. Shmyrev, Christoph Kempen,
Marc Roberts, Antonio Mora, Simon Podlipsky.

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="PSR with exception">
<arg name="encoding" value="UTF-8"/>
<arg name="report" value="full"/>
<arg name="colors"/>
<rule ref="PSR1"/>
<rule ref="PSR12"/>
</ruleset>

View File

@@ -0,0 +1,34 @@
{
"name": "textalk/websocket",
"description": "WebSocket client and server",
"license": "ISC",
"type": "library",
"authors": [
{
"name": "Fredrik Liljegren"
},
{
"name": "Sören Jensen",
"email": "soren@abicart.se"
}
],
"autoload": {
"psr-4": {
"WebSocket\\": "lib"
}
},
"autoload-dev": {
"psr-4": {
"WebSocket\\": "tests/mock"
}
},
"require": {
"php": "^7.2 | ^8.0",
"psr/log": "^1 | ^2 | ^3"
},
"require-dev": {
"phpunit/phpunit": "^8.0|^9.0",
"php-coveralls/php-coveralls": "^2.0",
"squizlabs/php_codesniffer": "^3.5"
}
}

View File

@@ -0,0 +1,143 @@
[Client](Client.md) • [Server](Server.md) • [Message](Message.md) • [Examples](Examples.md) • Changelog • [Contributing](Contributing.md)
# Websocket: Changelog
## `v1.5`
> PHP version `^7.2|^8.0`
### `1.5.8`
* Handle read error during handshake (@sirn-se)
### `1.5.7`
* Large header block fix (@sirn-se)
### `1.5.6`
* Add test for PHP 8.1 (@sirn-se)
* Code standard (@sirn-se)
### `1.5.5`
* Support for psr/log v2 and v3 (@simPod)
* GitHub Actions replaces Travis (@sirn-se)
### `1.5.4`
* Keep open connection on read timeout (@marcroberts)
### `1.5.3`
* Fix for persistent connection (@sirn-se)
### `1.5.2`
* Fix for getName() method (@sirn-se)
### `1.5.1`
* Fix for persistent connections (@rmeisler)
### `1.5.0`
* Convenience send methods; text(), binary(), ping(), pong() (@sirn-se)
* Optional Message instance as receive() method return (@sirn-se)
* Opcode filter for receive() method (@sirn-se)
* Added PHP `8.0` support (@webpatser)
* Dropped PHP `7.1` support (@sirn-se)
* Fix for unordered fragmented messages (@sirn-se)
* Improved error handling on stream calls (@sirn-se)
* Various code re-write (@sirn-se)
## `v1.4`
> PHP version `^7.1`
#### `1.4.3`
* Solve stream closure/get meta conflict (@sirn-se)
* Examples and documentation overhaul (@sirn-se)
#### `1.4.2`
* Force stream close on read error (@sirn-se)
* Authorization headers line feed (@sirn-se)
* Documentation (@matias-pool, @sirn-se)
#### `1.4.1`
* Ping/Pong, handled internally to avoid breaking fragmented messages (@nshmyrev, @sirn-se)
* Fix for persistent connections (@rmeisler)
* Fix opcode bitmask (@peterjah)
#### `1.4.0`
* Dropped support of old PHP versions (@sirn-se)
* Added PSR-3 Logging support (@sirn-se)
* Persistent connection option (@slezakattack)
* TimeoutException on connection time out (@slezakattack)
## `v1.3`
> PHP version `^5.4` and `^7.0`
#### `1.3.1`
* Allow control messages without payload (@Logioniz)
* Error code in ConnectionException (@sirn-se)
#### `1.3.0`
* Implements ping/pong frames (@pmccarren @Logioniz)
* Close behaviour (@sirn-se)
* Various fixes concerning connection handling (@sirn-se)
* Overhaul of Composer, Travis and Coveralls setup, PSR code standard and unit tests (@sirn-se)
## `v1.2`
> PHP version `^5.4` and `^7.0`
#### `1.2.0`
* Adding stream context options (to set e.g. SSL `allow_self_signed`).
## `v1.1`
> PHP version `^5.4` and `^7.0`
#### `1.1.2`
* Fixed error message on broken frame.
#### `1.1.1`
* Adding license information.
#### `1.1.0`
* Supporting huge payloads.
## `v1.0`
> PHP version `^5.4` and `^7.0`
#### `1.0.3`
* Bugfix: Correcting address in error-message
#### `1.0.2`
* Bugfix: Add port in request-header.
#### `1.0.1`
* Fixing a bug from empty payloads.
#### `1.0.0`
* Release as production ready.
* Adding option to set/override headers.
* Supporting basic authentication from user:pass in URL.

View File

@@ -0,0 +1,137 @@
Client • [Server](Server.md) • [Message](Message.md) • [Examples](Examples.md) • [Changelog](Changelog.md) • [Contributing](Contributing.md)
# Websocket: Client
The client can read and write on a WebSocket stream.
It internally supports Upgrade handshake and implicit close and ping/pong operations.
## Class synopsis
```php
WebSocket\Client {
public __construct(string $uri, array $options = [])
public __destruct()
public __toString() : string
public text(string $payload) : void
public binary(string $payload) : void
public ping(string $payload = '') : void
public pong(string $payload = '') : void
public send(mixed $payload, string $opcode = 'text', bool $masked = true) : void
public receive() : mixed
public close(int $status = 1000, mixed $message = 'ttfn') : mixed
public getName() : string|null
public getPier() : string|null
public getLastOpcode() : string
public getCloseStatus() : int
public isConnected() : bool
public setTimeout(int $seconds) : void
public setFragmentSize(int $fragment_size) : self
public getFragmentSize() : int
public setLogger(Psr\Log\LoggerInterface $logger = null) : void
}
```
## Examples
### Simple send-receive operation
This example send a single message to a server, and output the response.
```php
$client = new WebSocket\Client("ws://echo.websocket.org/");
$client->text("Hello WebSocket.org!");
echo $client->receive();
$client->close();
```
### Listening to a server
To continuously listen to incoming messages, you need to put the receive operation within a loop.
Note that these functions **always** throw exception on any failure, including recoverable failures such as connection time out.
By consuming exceptions, the code will re-connect the socket in next loop iteration.
```php
$client = new WebSocket\Client("ws://echo.websocket.org/");
while (true) {
try {
$message = $client->receive();
// Act on received message
// Break while loop to stop listening
} catch (\WebSocket\ConnectionException $e) {
// Possibly log errors
}
}
$client->close();
```
### Filtering received messages
By default the `receive()` method return messages of 'text' and 'binary' opcode.
The filter option allows you to specify which message types to return.
```php
$client = new WebSocket\Client("ws://echo.websocket.org/", ['filter' => ['text']]);
$client->receive(); // Only return 'text' messages
$client = new WebSocket\Client("ws://echo.websocket.org/", ['filter' => ['text', 'binary', 'ping', 'pong', 'close']]);
$client->receive(); // Return all messages
```
### Sending messages
There are convenience methods to send messages with different opcodes.
```php
$client = new WebSocket\Client("ws://echo.websocket.org/");
// Convenience methods
$client->text('A plain text message'); // Send an opcode=text message
$client->binary($binary_string); // Send an opcode=binary message
$client->ping(); // Send an opcode=ping frame
$client->pong(); // Send an unsolicited opcode=pong frame
// Generic send method
$client->send($payload); // Sent as masked opcode=text
$client->send($payload, 'binary'); // Sent as masked opcode=binary
$client->send($payload, 'binary', false); // Sent as unmasked opcode=binary
```
## Constructor options
The `$options` parameter in constructor accepts an associative array of options.
* `context` - A stream context created using [stream_context_create](https://www.php.net/manual/en/function.stream-context-create).
* `filter` - Array of opcodes to return on receive, default `['text', 'binary']`
* `fragment_size` - Maximum payload size. Default 4096 chars.
* `headers` - Additional headers as associative array name => content.
* `logger` - A [PSR-3](https://www.php-fig.org/psr/psr-3/) compatible logger.
* `persistent` - Connection is re-used between requests until time out is reached. Default false.
* `return_obj` - Return a [Message](Message.md) instance on receive, default false
* `timeout` - Time out in seconds. Default 5 seconds.
```php
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'verify_peer', false);
stream_context_set_option($context, 'ssl', 'verify_peer_name', false);
$client = new WebSocket\Client("ws://echo.websocket.org/", [
'context' => $context, // Attach stream context created above
'filter' => ['text', 'binary', 'ping'], // Specify message types for receive() to return
'headers' => [ // Additional headers, used to specify subprotocol
'Sec-WebSocket-Protocol' => 'soap',
'origin' => 'localhost',
],
'logger' => $my_psr3_logger, // Attach a PSR3 compatible logger
'return_obj' => true, // Return Message instance rather than just text
'timeout' => 60, // 1 minute time out
]);
```
## Exceptions
* `WebSocket\BadOpcodeException` - Thrown if provided opcode is invalid.
* `WebSocket\BadUriException` - Thrown if provided URI is invalid.
* `WebSocket\ConnectionException` - Thrown on any socket I/O failure.
* `WebSocket\TimeoutException` - Thrown when the socket experiences a time out.

View File

@@ -0,0 +1,44 @@
[Client](Client.md) • [Server](Server.md) • [Message](Message.md) • [Examples](Examples.md) • [Changelog](Changelog.md) • Contributing
# Websocket: Contributing
Everyone is welcome to help out!
But to keep this project sustainable, please ensure your contribution respects the requirements below.
## PR Requirements
Requirements on pull requests;
* All tests **MUST** pass.
* Code coverage **MUST** remain at 100%.
* Code **MUST** adhere to PSR-1 and PSR-12 code standards.
## Dependency management
Install or update dependencies using [Composer](https://getcomposer.org/).
```
# Install dependencies
make install
# Update dependencies
make update
```
## Code standard
This project uses [PSR-1](https://www.php-fig.org/psr/psr-1/) and [PSR-12](https://www.php-fig.org/psr/psr-12/) code standards.
```
# Check code standard adherence
make cs-check
```
## Unit testing
Unit tests with [PHPUnit](https://phpunit.readthedocs.io/), coverage with [Coveralls](https://github.com/php-coveralls/php-coveralls)
```
# Run unit tests
make test
# Create coverage
make coverage
```

View File

@@ -0,0 +1,98 @@
[Client](Client.md) • [Server](Server.md) • [Message](Message.md) • Examples • [Changelog](Changelog.md) • [Contributing](Contributing.md)
# Websocket: Examples
Here are some examples on how to use the WebSocket library.
## Echo logger
In dev environment (as in having run composer to include dev dependencies) you have
access to a simple echo logger that print out information synchronously.
This is usable for debugging. For production, use a proper logger.
```php
namespace WebSocket;
$logger = new EchoLogger();
$client = new Client('ws://echo.websocket.org/');
$client->setLogger($logger);
$server = new Server();
$server->setLogger($logger);
```
An example of server output;
```
info | Server listening to port 8000 []
debug | Wrote 129 of 129 bytes. []
info | Server connected to port 8000 []
info | Received 'text' message []
debug | Wrote 9 of 9 bytes. []
info | Sent 'text' message []
debug | Received 'close', status: 1000. []
debug | Wrote 32 of 32 bytes. []
info | Sent 'close' message []
info | Received 'close' message []
```
## The `send` client
Source: [examples/send.php](../examples/send.php)
A simple, single send/receive client.
Example use:
```
php examples/send.php --opcode text "A text message" // Send a text message to localhost
php examples/send.php --opcode ping "ping it" // Send a ping message to localhost
php examples/send.php --uri ws://echo.websocket.org "A text message" // Send a text message to echo.websocket.org
php examples/send.php --opcode text --debug "A text message" // Use runtime debugging
```
## The `echoserver` server
Source: [examples/echoserver.php](../examples/echoserver.php)
A simple server that responds to recevied commands.
Example use:
```
php examples/echoserver.php // Run with default settings
php examples/echoserver.php --port 8080 // Listen on port 8080
php examples/echoserver.php --debug // Use runtime debugging
```
These strings can be sent as message to trigger server to perform actions;
* `exit` - Server will initiate close procedure
* `ping` - Server will send a ping message
* `headers` - Server will respond with all headers provided by client
* `auth` - Server will respond with auth header if provided by client
* For other sent strings, server will respond with the same strings
## The `random` client
Source: [examples/random_client.php](../examples/random_client.php)
The random client will use random options and continuously send/receive random messages.
Example use:
```
php examples/random_client.php --uri ws://echo.websocket.org // Connect to echo.websocket.org
php examples/random_client.php --timeout 5 --fragment_size 16 // Specify settings
php examples/random_client.php --debug // Use runtime debugging
```
## The `random` server
Source: [examples/random_server.php](../examples/random_server.php)
The random server will use random options and continuously send/receive random messages.
Example use:
```
php examples/random_server.php --port 8080 // // Listen on port 8080
php examples/random_server.php --timeout 5 --fragment_size 16 // Specify settings
php examples/random_server.php --debug // Use runtime debugging
```

View File

@@ -0,0 +1,60 @@
[Client](Client.md) • [Server](Server.md) • Message • [Examples](Examples.md) • [Changelog](Changelog.md) • [Contributing](Contributing.md)
# Websocket: Messages
If option `return_obj` is set to `true` on [client](Client.md) or [server](Server.md),
the `receive()` method will return a Message instance instead of a string.
Available classes correspond to opcode;
* WebSocket\Message\Text
* WebSocket\Message\Binary
* WebSocket\Message\Ping
* WebSocket\Message\Pong
* WebSocket\Message\Close
Additionally;
* WebSocket\Message\Message - abstract base class for all messages above
* WebSocket\Message\Factory - Factory class to create Msssage instances
## Message abstract class synopsis
```php
WebSocket\Message\Message {
public __construct(string $payload = '')
public __toString() : string
public getOpcode() : string
public getLength() : int
public getTimestamp() : DateTime
public getContent() : string
public setContent(string $payload = '') : void
public hasContent() : bool
}
```
## Factory class synopsis
```php
WebSocket\Message\Factory {
public create(string $opcode, string $payload = '') : Message
}
```
## Example
Receving a Message and echo some methods.
```php
$client = new WebSocket\Client('ws://echo.websocket.org/', ['return_obj' => true]);
$client->text('Hello WebSocket.org!');
// Echo return same message as sent
$message = $client->receive();
echo $message->getOpcode(); // -> "text"
echo $message->getLength(); // -> 20
echo $message->getContent(); // -> "Hello WebSocket.org!"
echo $message->hasContent(); // -> true
echo $message->getTimestamp()->format('H:i:s'); // -> 19:37:18
$client->close();
```

View File

@@ -0,0 +1,136 @@
[Client](Client.md) • Server • [Message](Message.md) • [Examples](Examples.md) • [Changelog](Changelog.md) • [Contributing](Contributing.md)
# Websocket: Server
The library contains a rudimentary single stream/single thread server.
It internally supports Upgrade handshake and implicit close and ping/pong operations.
Note that it does **not** support threading or automatic association ot continuous client requests.
If you require this kind of server behavior, you need to build it on top of provided server implementation.
## Class synopsis
```php
WebSocket\Server {
public __construct(array $options = [])
public __destruct()
public __toString() : string
public accept() : bool
public text(string $payload) : void
public binary(string $payload) : void
public ping(string $payload = '') : void
public pong(string $payload = '') : void
public send(mixed $payload, string $opcode = 'text', bool $masked = true) : void
public receive() : mixed
public close(int $status = 1000, mixed $message = 'ttfn') : mixed
public getPort() : int
public getPath() : string
public getRequest() : array
public getHeader(string $header_name) : string|null
public getName() : string|null
public getPier() : string|null
public getLastOpcode() : string
public getCloseStatus() : int
public isConnected() : bool
public setTimeout(int $seconds) : void
public setFragmentSize(int $fragment_size) : self
public getFragmentSize() : int
public setLogger(Psr\Log\LoggerInterface $logger = null) : void
}
```
## Examples
### Simple receive-send operation
This example reads a single message from a client, and respond with the same message.
```php
$server = new WebSocket\Server();
$server->accept();
$message = $server->receive();
$server->text($message);
$server->close();
```
### Listening to clients
To continuously listen to incoming messages, you need to put the receive operation within a loop.
Note that these functions **always** throw exception on any failure, including recoverable failures such as connection time out.
By consuming exceptions, the code will re-connect the socket in next loop iteration.
```php
$server = new WebSocket\Server();
while ($server->accept()) {
try {
$message = $server->receive();
// Act on received message
// Break while loop to stop listening
} catch (\WebSocket\ConnectionException $e) {
// Possibly log errors
}
}
$server->close();
```
### Filtering received messages
By default the `receive()` method return messages of 'text' and 'binary' opcode.
The filter option allows you to specify which message types to return.
```php
$server = new WebSocket\Server(['filter' => ['text']]);
$server->receive(); // only return 'text' messages
$server = new WebSocket\Server(['filter' => ['text', 'binary', 'ping', 'pong', 'close']]);
$server->receive(); // return all messages
```
### Sending messages
There are convenience methods to send messages with different opcodes.
```php
$server = new WebSocket\Server();
// Convenience methods
$server->text('A plain text message'); // Send an opcode=text message
$server->binary($binary_string); // Send an opcode=binary message
$server->ping(); // Send an opcode=ping frame
$server->pong(); // Send an unsolicited opcode=pong frame
// Generic send method
$server->send($payload); // Sent as masked opcode=text
$server->send($payload, 'binary'); // Sent as masked opcode=binary
$server->send($payload, 'binary', false); // Sent as unmasked opcode=binary
```
## Constructor options
The `$options` parameter in constructor accepts an associative array of options.
* `filter` - Array of opcodes to return on receive, default `['text', 'binary']`
* `fragment_size` - Maximum payload size. Default 4096 chars.
* `logger` - A [PSR-3](https://www.php-fig.org/psr/psr-3/) compatible logger.
* `port` - The server port to listen to. Default 8000.
* `return_obj` - Return a [Message](Message.md) instance on receive, default false
* `timeout` - Time out in seconds. Default 5 seconds.
```php
$server = new WebSocket\Server([
'filter' => ['text', 'binary', 'ping'], // Specify message types for receive() to return
'logger' => $my_psr3_logger, // Attach a PSR3 compatible logger
'port' => 9000, // Listening port
'return_obj' => true, // Return Message insatnce rather than just text
'timeout' => 60, // 1 minute time out
]);
```
## Exceptions
* `WebSocket\BadOpcodeException` - Thrown if provided opcode is invalid.
* `WebSocket\ConnectionException` - Thrown on any socket I/O failure.
* `WebSocket\TimeoutException` - Thrown when the socket experiences a time out.

View File

@@ -0,0 +1,87 @@
<?php
/**
* This file is used for the tests, but can also serve as an example of a WebSocket\Server.
* Run in console: php examples/echoserver.php
*
* Console options:
* --port <int> : The port to listen to, default 8000
* --timeout <int> : Timeout in seconds, default 200 seconds
* --debug : Output log data (if logger is available)
*/
namespace WebSocket;
require __DIR__ . '/../vendor/autoload.php';
error_reporting(-1);
echo "> Random server\n";
// Server options specified or random
$options = array_merge([
'port' => 8000,
'timeout' => 200,
'filter' => ['text', 'binary', 'ping', 'pong'],
], getopt('', ['port:', 'timeout:', 'debug']));
// If debug mode and logger is available
if (isset($options['debug']) && class_exists('WebSocket\EchoLog')) {
$logger = new EchoLog();
$options['logger'] = $logger;
echo "> Using logger\n";
}
// Setting timeout to 200 seconds to make time for all tests and manual runs.
try {
$server = new Server($options);
} catch (ConnectionException $e) {
echo "> ERROR: {$e->getMessage()}\n";
die();
}
echo "> Listening to port {$server->getPort()}\n";
// Force quit to close server
while (true) {
try {
while ($server->accept()) {
echo "> Accepted on port {$server->getPort()}\n";
while (true) {
$message = $server->receive();
$opcode = $server->getLastOpcode();
if (is_null($message)) {
echo "> Closing connection\n";
continue 2;
}
echo "> Got '{$message}' [opcode: {$opcode}]\n";
if (in_array($opcode, ['ping', 'pong'])) {
$server->send($message);
continue;
}
// Allow certain string to trigger server action
switch ($message) {
case 'exit':
echo "> Client told me to quit. Bye bye.\n";
$server->close();
echo "> Close status: {$server->getCloseStatus()}\n";
exit;
case 'headers':
$server->text(implode("\r\n", $server->getRequest()));
break;
case 'ping':
$server->ping($message);
break;
case 'auth':
$auth = $server->getHeader('Authorization');
$server->text("{$auth} - {$message}");
break;
default:
$server->text($message);
}
}
}
} catch (ConnectionException $e) {
echo "> ERROR: {$e->getMessage()}\n";
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* Websocket client that read/write random data.
* Run in console: php examples/random_client.php
*
* Console options:
* --uri <uri> : The URI to connect to, default ws://localhost:8000
* --timeout <int> : Timeout in seconds, random default
* --fragment_size <int> : Fragment size as bytes, random default
* --debug : Output log data (if logger is available)
*/
namespace WebSocket;
require __DIR__ . '/../vendor/autoload.php';
error_reporting(-1);
$randStr = function (int $maxlength = 4096) {
$string = '';
$length = rand(1, $maxlength);
for ($i = 0; $i < $length; $i++) {
$string .= chr(rand(33, 126));
}
return $string;
};
echo "> Random client\n";
// Server options specified or random
$options = array_merge([
'uri' => 'ws://localhost:8000',
'timeout' => rand(1, 60),
'fragment_size' => rand(1, 4096) * 8,
], getopt('', ['uri:', 'timeout:', 'fragment_size:', 'debug']));
// If debug mode and logger is available
if (isset($options['debug']) && class_exists('WebSocket\EchoLog')) {
$logger = new EchoLog();
$options['logger'] = $logger;
echo "> Using logger\n";
}
// Main loop
while (true) {
try {
$client = new Client($options['uri'], $options);
$info = json_encode([
'uri' => $options['uri'],
'timeout' => $options['timeout'],
'framgemt_size' => $client->getFragmentSize(),
]);
echo "> Creating client {$info}\n";
try {
while (true) {
// Random actions
switch (rand(1, 10)) {
case 1:
echo "> Sending text\n";
$client->text("Text message {$randStr()}");
break;
case 2:
echo "> Sending binary\n";
$client->binary("Binary message {$randStr()}");
break;
case 3:
echo "> Sending close\n";
$client->close(rand(1000, 2000), "Close message {$randStr(8)}");
break;
case 4:
echo "> Sending ping\n";
$client->ping("Ping message {$randStr(8)}");
break;
case 5:
echo "> Sending pong\n";
$client->pong("Pong message {$randStr(8)}");
break;
default:
echo "> Receiving\n";
$received = $client->receive();
echo "> Received {$client->getLastOpcode()}: {$received}\n";
}
sleep(rand(1, 5));
}
} catch (\Throwable $e) {
echo "ERROR I/O: {$e->getMessage()} [{$e->getCode()}]\n";
}
} catch (\Throwable $e) {
echo "ERROR: {$e->getMessage()} [{$e->getCode()}]\n";
}
sleep(rand(1, 5));
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Websocket server that read/write random data.
* Run in console: php examples/random_server.php
*
* Console options:
* --port <int> : The port to listen to, default 8000
* --timeout <int> : Timeout in seconds, random default
* --fragment_size <int> : Fragment size as bytes, random default
* --debug : Output log data (if logger is available)
*/
namespace WebSocket;
require __DIR__ . '/../vendor/autoload.php';
error_reporting(-1);
$randStr = function (int $maxlength = 4096) {
$string = '';
$length = rand(1, $maxlength);
for ($i = 0; $i < $length; $i++) {
$string .= chr(rand(33, 126));
}
return $string;
};
echo "> Random server\n";
// Server options specified or random
$options = array_merge([
'port' => 8000,
'timeout' => rand(1, 60),
'fragment_size' => rand(1, 4096) * 8,
], getopt('', ['port:', 'timeout:', 'fragment_size:', 'debug']));
// If debug mode and logger is available
if (isset($options['debug']) && class_exists('WebSocket\EchoLog')) {
$logger = new EchoLog();
$options['logger'] = $logger;
echo "> Using logger\n";
}
// Force quit to close server
while (true) {
try {
// Setup server
$server = new Server($options);
$info = json_encode([
'port' => $server->getPort(),
'timeout' => $options['timeout'],
'framgemt_size' => $server->getFragmentSize(),
]);
echo "> Creating server {$info}\n";
while ($server->accept()) {
while (true) {
// Random actions
switch (rand(1, 10)) {
case 1:
echo "> Sending text\n";
$server->text("Text message {$randStr()}");
break;
case 2:
echo "> Sending binary\n";
$server->binary("Binary message {$randStr()}");
break;
case 3:
echo "> Sending close\n";
$server->close(rand(1000, 2000), "Close message {$randStr(8)}");
break;
case 4:
echo "> Sending ping\n";
$server->ping("Ping message {$randStr(8)}");
break;
case 5:
echo "> Sending pong\n";
$server->pong("Pong message {$randStr(8)}");
break;
default:
echo "> Receiving\n";
$received = $server->receive();
echo "> Received {$server->getLastOpcode()}: {$received}\n";
}
sleep(rand(1, 5));
}
}
} catch (\Throwable $e) {
echo "ERROR: {$e->getMessage()} [{$e->getCode()}]\n";
}
sleep(rand(1, 5));
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Simple send & receive client for test purpose.
* Run in console: php examples/send.php <options> <message>
*
* Console options:
* --uri <uri> : The URI to connect to, default ws://localhost:8000
* --opcode <string> : Opcode to send, default 'text'
* --debug : Output log data (if logger is available)
*/
namespace WebSocket;
require __DIR__ . '/../vendor/autoload.php';
error_reporting(-1);
echo "> Send client\n";
// Server options specified or random
$options = array_merge([
'uri' => 'ws://localhost:8000',
'opcode' => 'text',
], getopt('', ['uri:', 'opcode:', 'debug']));
$message = array_pop($argv);
// If debug mode and logger is available
if (isset($options['debug']) && class_exists('WebSocket\EchoLog')) {
$logger = new EchoLog();
$options['logger'] = $logger;
echo "> Using logger\n";
}
try {
// Create client, send and recevie
$client = new Client($options['uri'], $options);
$client->send($message, $options['opcode']);
echo "> Sent '{$message}' [opcode: {$options['opcode']}]\n";
if (in_array($options['opcode'], ['text', 'binary'])) {
$message = $client->receive();
$opcode = $client->getLastOpcode();
if (!is_null($message)) {
echo "> Got '{$message}' [opcode: {$opcode}]\n";
}
}
$client->close();
echo "> Closing client\n";
} catch (\Throwable $e) {
echo "ERROR: {$e->getMessage()} [{$e->getCode()}]\n";
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WebSocket;
class BadOpcodeException extends Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WebSocket;
class BadUriException extends Exception
{
}

View File

@@ -0,0 +1,486 @@
<?php
/**
* Copyright (C) 2014-2020 Textalk/Abicart and contributors.
*
* This file is part of Websocket PHP and is free software under the ISC License.
* License text: https://raw.githubusercontent.com/Textalk/websocket-php/master/COPYING
*/
namespace WebSocket;
use Psr\Log\{LoggerAwareInterface, LoggerInterface, NullLogger};
use WebSocket\Message\Factory;
class Base implements LoggerAwareInterface
{
protected $socket;
protected $options = [];
protected $is_closing = false;
protected $last_opcode = null;
protected $close_status = null;
protected $logger;
private $read_buffer;
protected static $opcodes = [
'continuation' => 0,
'text' => 1,
'binary' => 2,
'close' => 8,
'ping' => 9,
'pong' => 10,
];
public function getLastOpcode(): ?string
{
return $this->last_opcode;
}
public function getCloseStatus(): ?int
{
return $this->close_status;
}
public function isConnected(): bool
{
return $this->socket &&
(get_resource_type($this->socket) == 'stream' ||
get_resource_type($this->socket) == 'persistent stream');
}
public function setTimeout(int $timeout): void
{
$this->options['timeout'] = $timeout;
if ($this->isConnected()) {
stream_set_timeout($this->socket, $timeout);
}
}
public function setFragmentSize(int $fragment_size): self
{
$this->options['fragment_size'] = $fragment_size;
return $this;
}
public function getFragmentSize(): int
{
return $this->options['fragment_size'];
}
public function setLogger(LoggerInterface $logger = null): void
{
$this->logger = $logger ?: new NullLogger();
}
public function send(string $payload, string $opcode = 'text', bool $masked = true): void
{
if (!$this->isConnected()) {
$this->connect();
}
if (!in_array($opcode, array_keys(self::$opcodes))) {
$warning = "Bad opcode '{$opcode}'. Try 'text' or 'binary'.";
$this->logger->warning($warning);
throw new BadOpcodeException($warning);
}
$payload_chunks = str_split($payload, $this->options['fragment_size']);
$frame_opcode = $opcode;
for ($index = 0; $index < count($payload_chunks); ++$index) {
$chunk = $payload_chunks[$index];
$final = $index == count($payload_chunks) - 1;
$this->sendFragment($final, $chunk, $frame_opcode, $masked);
// all fragments after the first will be marked a continuation
$frame_opcode = 'continuation';
}
$this->logger->info("Sent '{$opcode}' message", [
'opcode' => $opcode,
'content-length' => strlen($payload),
'frames' => count($payload_chunks),
]);
}
/**
* Convenience method to send text message
* @param string $payload Content as string
*/
public function text(string $payload): void
{
$this->send($payload);
}
/**
* Convenience method to send binary message
* @param string $payload Content as binary string
*/
public function binary(string $payload): void
{
$this->send($payload, 'binary');
}
/**
* Convenience method to send ping
* @param string $payload Optional text as string
*/
public function ping(string $payload = ''): void
{
$this->send($payload, 'ping');
}
/**
* Convenience method to send unsolicited pong
* @param string $payload Optional text as string
*/
public function pong(string $payload = ''): void
{
$this->send($payload, 'pong');
}
/**
* Get name of local socket, or null if not connected
* @return string|null
*/
public function getName(): ?string
{
return $this->isConnected() ? stream_socket_get_name($this->socket, false) : null;
}
/**
* Get name of remote socket, or null if not connected
* @return string|null
*/
public function getPier(): ?string
{
return $this->isConnected() ? stream_socket_get_name($this->socket, true) : null;
}
/**
* Get string representation of instance
* @return string String representation
*/
public function __toString(): string
{
return sprintf(
"%s(%s)",
get_class($this),
$this->getName() ?: 'closed'
);
}
/**
* Receive one message.
* Will continue reading until read message match filter settings.
* Return Message instance or string according to settings.
*/
protected function sendFragment(bool $final, string $payload, string $opcode, bool $masked): void
{
$data = '';
$byte_1 = $final ? 0b10000000 : 0b00000000; // Final fragment marker.
$byte_1 |= self::$opcodes[$opcode]; // Set opcode.
$data .= pack('C', $byte_1);
$byte_2 = $masked ? 0b10000000 : 0b00000000; // Masking bit marker.
// 7 bits of payload length...
$payload_length = strlen($payload);
if ($payload_length > 65535) {
$data .= pack('C', $byte_2 | 0b01111111);
$data .= pack('J', $payload_length);
} elseif ($payload_length > 125) {
$data .= pack('C', $byte_2 | 0b01111110);
$data .= pack('n', $payload_length);
} else {
$data .= pack('C', $byte_2 | $payload_length);
}
// Handle masking
if ($masked) {
// generate a random mask:
$mask = '';
for ($i = 0; $i < 4; $i++) {
$mask .= chr(rand(0, 255));
}
$data .= $mask;
// Append payload to frame:
for ($i = 0; $i < $payload_length; $i++) {
$data .= $payload[$i] ^ $mask[$i % 4];
}
} else {
$data .= $payload;
}
$this->write($data);
$this->logger->debug("Sent '{$opcode}' frame", [
'opcode' => $opcode,
'final' => $final,
'content-length' => strlen($payload),
]);
}
public function receive()
{
$filter = $this->options['filter'];
if (!$this->isConnected()) {
$this->connect();
}
do {
$response = $this->receiveFragment();
list ($payload, $final, $opcode) = $response;
// Continuation and factual opcode
$continuation = ($opcode == 'continuation');
$payload_opcode = $continuation ? $this->read_buffer['opcode'] : $opcode;
// Filter frames
if (!in_array($payload_opcode, $filter)) {
if ($payload_opcode == 'close') {
return null; // Always abort receive on close
}
$final = false;
continue; // Continue reading
}
// First continuation frame, create buffer
if (!$final && !$continuation) {
$this->read_buffer = ['opcode' => $opcode, 'payload' => $payload, 'frames' => 1];
continue; // Continue reading
}
// Subsequent continuation frames, add to buffer
if ($continuation) {
$this->read_buffer['payload'] .= $payload;
$this->read_buffer['frames']++;
}
} while (!$final);
// Final, return payload
$frames = 1;
if ($continuation) {
$payload = $this->read_buffer['payload'];
$frames = $this->read_buffer['frames'];
$this->read_buffer = null;
}
$this->logger->info("Received '{opcode}' message", [
'opcode' => $payload_opcode,
'content-length' => strlen($payload),
'frames' => $frames,
]);
$this->last_opcode = $payload_opcode;
$factory = new Factory();
return $this->options['return_obj']
? $factory->create($payload_opcode, $payload)
: $payload;
}
protected function receiveFragment(): array
{
// Read the fragment "header" first, two bytes.
$data = $this->read(2);
list ($byte_1, $byte_2) = array_values(unpack('C*', $data));
$final = (bool)($byte_1 & 0b10000000); // Final fragment marker.
$rsv = $byte_1 & 0b01110000; // Unused bits, ignore
// Parse opcode
$opcode_int = $byte_1 & 0b00001111;
$opcode_ints = array_flip(self::$opcodes);
if (!array_key_exists($opcode_int, $opcode_ints)) {
$warning = "Bad opcode in websocket frame: {$opcode_int}";
$this->logger->warning($warning);
throw new ConnectionException($warning, ConnectionException::BAD_OPCODE);
}
$opcode = $opcode_ints[$opcode_int];
// Masking bit
$mask = (bool)($byte_2 & 0b10000000);
$payload = '';
// Payload length
$payload_length = $byte_2 & 0b01111111;
if ($payload_length > 125) {
if ($payload_length === 126) {
$data = $this->read(2); // 126: Payload is a 16-bit unsigned int
$payload_length = current(unpack('n', $data));
} else {
$data = $this->read(8); // 127: Payload is a 64-bit unsigned int
$payload_length = current(unpack('J', $data));
}
}
// Get masking key.
if ($mask) {
$masking_key = $this->read(4);
}
// Get the actual payload, if any (might not be for e.g. close frames.
if ($payload_length > 0) {
$data = $this->read($payload_length);
if ($mask) {
// Unmask payload.
for ($i = 0; $i < $payload_length; $i++) {
$payload .= ($data[$i] ^ $masking_key[$i % 4]);
}
} else {
$payload = $data;
}
}
$this->logger->debug("Read '{opcode}' frame", [
'opcode' => $opcode,
'final' => $final,
'content-length' => strlen($payload),
]);
// if we received a ping, send a pong and wait for the next message
if ($opcode === 'ping') {
$this->logger->debug("Received 'ping', sending 'pong'.");
$this->send($payload, 'pong', true);
return [$payload, true, $opcode];
}
// if we received a pong, wait for the next message
if ($opcode === 'pong') {
$this->logger->debug("Received 'pong'.");
return [$payload, true, $opcode];
}
if ($opcode === 'close') {
$status_bin = '';
$status = '';
// Get the close status.
$status_bin = '';
$status = '';
if ($payload_length > 0) {
$status_bin = $payload[0] . $payload[1];
$status = current(unpack('n', $payload));
$this->close_status = $status;
}
// Get additional close message
if ($payload_length >= 2) {
$payload = substr($payload, 2);
}
$this->logger->debug("Received 'close', status: {$this->close_status}.");
if ($this->is_closing) {
$this->is_closing = false; // A close response, all done.
} else {
$this->send($status_bin . 'Close acknowledged: ' . $status, 'close', true); // Respond.
}
// Close the socket.
fclose($this->socket);
// Closing should not return message.
return [$payload, true, $opcode];
}
return [$payload, $final, $opcode];
}
/**
* Tell the socket to close.
*
* @param integer $status http://tools.ietf.org/html/rfc6455#section-7.4
* @param string $message A closing message, max 125 bytes.
*/
public function close(int $status = 1000, string $message = 'ttfn'): void
{
if (!$this->isConnected()) {
return;
}
$status_binstr = sprintf('%016b', $status);
$status_str = '';
foreach (str_split($status_binstr, 8) as $binstr) {
$status_str .= chr(bindec($binstr));
}
$this->send($status_str . $message, 'close', true);
$this->logger->debug("Closing with status: {$status_str}.");
$this->is_closing = true;
$this->receive(); // Receiving a close frame will close the socket now.
}
/**
* Disconnect from client/server.
*/
public function disconnect(): void
{
if ($this->isConnected()) {
fclose($this->socket);
$this->socket = null;
}
}
protected function write(string $data): void
{
$length = strlen($data);
$written = @fwrite($this->socket, $data);
if ($written === false) {
$this->throwException("Failed to write {$length} bytes.");
}
if ($written < strlen($data)) {
$this->throwException("Could only write {$written} out of {$length} bytes.");
}
$this->logger->debug("Wrote {$written} of {$length} bytes.");
}
protected function read(string $length): string
{
$data = '';
while (strlen($data) < $length) {
$buffer = @fread($this->socket, $length - strlen($data));
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.");
}
if ($buffer === '') {
$this->throwException("Empty read; connection dead?");
}
$data .= $buffer;
$read = strlen($data);
$this->logger->debug("Read {$read} of {$length} bytes.");
}
return $data;
}
protected function throwException(string $message, int $code = 0): void
{
$meta = ['closed' => true];
if ($this->isConnected()) {
$meta = stream_get_meta_data($this->socket);
fclose($this->socket);
$this->socket = null;
}
if (!empty($meta['timed_out'])) {
$this->logger->error($message, $meta);
throw new TimeoutException($message, ConnectionException::TIMED_OUT, $meta);
}
if (!empty($meta['eof'])) {
$code = ConnectionException::EOF;
}
$this->logger->error($message, $meta);
throw new ConnectionException($message, $code, $meta);
}
}

View File

@@ -0,0 +1,226 @@
<?php
/**
* Copyright (C) 2014-2020 Textalk/Abicart and contributors.
*
* This file is part of Websocket PHP and is free software under the ISC License.
* License text: https://raw.githubusercontent.com/Textalk/websocket-php/master/COPYING
*/
namespace WebSocket;
class Client extends Base
{
// Default options
protected static $default_options = [
'context' => null,
'filter' => ['text', 'binary'],
'fragment_size' => 4096,
'headers' => null,
'logger' => null,
'origin' => null, // @deprecated
'persistent' => false,
'return_obj' => false,
'timeout' => 5,
];
protected $socket_uri;
/**
* @param string $uri A ws/wss-URI
* @param array $options
* Associative array containing:
* - context: Set the stream context. Default: empty context
* - timeout: Set the socket timeout in seconds. Default: 5
* - fragment_size: Set framgemnt size. Default: 4096
* - headers: Associative array of headers to set/override.
*/
public function __construct(string $uri, array $options = [])
{
$this->options = array_merge(self::$default_options, $options);
$this->socket_uri = $uri;
$this->setLogger($this->options['logger']);
}
public function __destruct()
{
if ($this->isConnected() && get_resource_type($this->socket) !== 'persistent stream') {
fclose($this->socket);
}
$this->socket = null;
}
/**
* Perform WebSocket handshake
*/
protected function connect(): void
{
$url_parts = parse_url($this->socket_uri);
if (empty($url_parts) || empty($url_parts['scheme']) || empty($url_parts['host'])) {
$error = "Invalid url '{$this->socket_uri}' provided.";
$this->logger->error($error);
throw new BadUriException($error);
}
$scheme = $url_parts['scheme'];
$host = $url_parts['host'];
$user = isset($url_parts['user']) ? $url_parts['user'] : '';
$pass = isset($url_parts['pass']) ? $url_parts['pass'] : '';
$port = isset($url_parts['port']) ? $url_parts['port'] : ($scheme === 'wss' ? 443 : 80);
$path = isset($url_parts['path']) ? $url_parts['path'] : '/';
$query = isset($url_parts['query']) ? $url_parts['query'] : '';
$fragment = isset($url_parts['fragment']) ? $url_parts['fragment'] : '';
$path_with_query = $path;
if (!empty($query)) {
$path_with_query .= '?' . $query;
}
if (!empty($fragment)) {
$path_with_query .= '#' . $fragment;
}
if (!in_array($scheme, ['ws', 'wss'])) {
$error = "Url should have scheme ws or wss, not '{$scheme}' from URI '{$this->socket_uri}'.";
$this->logger->error($error);
throw new BadUriException($error);
}
$host_uri = ($scheme === 'wss' ? 'ssl' : 'tcp') . '://' . $host;
// Set the stream context options if they're already set in the config
if (isset($this->options['context'])) {
// Suppress the error since we'll catch it below
if (@get_resource_type($this->options['context']) === 'stream-context') {
$context = $this->options['context'];
} else {
$error = "Stream context in \$options['context'] isn't a valid context.";
$this->logger->error($error);
throw new \InvalidArgumentException($error);
}
} else {
$context = stream_context_create();
}
$persistent = $this->options['persistent'] === true;
$flags = STREAM_CLIENT_CONNECT;
$flags = $persistent ? $flags | STREAM_CLIENT_PERSISTENT : $flags;
$error = $errno = $errstr = null;
set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error) {
$this->logger->warning($message, ['severity' => $severity]);
$error = $message;
}, E_ALL);
// Open the socket.
$this->socket = stream_socket_client(
"{$host_uri}:{$port}",
$errno,
$errstr,
$this->options['timeout'],
$flags,
$context
);
restore_error_handler();
if (!$this->isConnected()) {
$error = "Could not open socket to \"{$host}:{$port}\": {$errstr} ({$errno}) {$error}.";
$this->logger->error($error);
throw new ConnectionException($error);
}
$address = "{$scheme}://{$host}{$path_with_query}";
if (!$persistent || ftell($this->socket) == 0) {
// Set timeout on the stream as well.
stream_set_timeout($this->socket, $this->options['timeout']);
// Generate the WebSocket key.
$key = self::generateKey();
// Default headers
$headers = [
'Host' => $host . ":" . $port,
'User-Agent' => 'websocket-client-php',
'Connection' => 'Upgrade',
'Upgrade' => 'websocket',
'Sec-WebSocket-Key' => $key,
'Sec-WebSocket-Version' => '13',
];
// Handle basic authentication.
if ($user || $pass) {
$headers['authorization'] = 'Basic ' . base64_encode($user . ':' . $pass);
}
// Deprecated way of adding origin (use headers instead).
if (isset($this->options['origin'])) {
$headers['origin'] = $this->options['origin'];
}
// Add and override with headers from options.
if (isset($this->options['headers'])) {
$headers = array_merge($headers, $this->options['headers']);
}
$header = "GET " . $path_with_query . " HTTP/1.1\r\n" . implode(
"\r\n",
array_map(
function ($key, $value) {
return "$key: $value";
},
array_keys($headers),
$headers
)
) . "\r\n\r\n";
// Send headers.
$this->write($header);
// Get server response header (terminated with double CR+LF).
$response = '';
do {
$buffer = fgets($this->socket, 1024);
if ($buffer === false) {
$meta = stream_get_meta_data($this->socket);
$message = 'Client handshake error';
$this->logger->error($message, $meta);
throw new ConnectionException($message);
}
$response .= $buffer;
} while (substr_count($response, "\r\n\r\n") == 0);
// Validate response.
if (!preg_match('#Sec-WebSocket-Accept:\s(.*)$#mUi', $response, $matches)) {
$error = "Connection to '{$address}' failed: Server sent invalid upgrade response: {$response}";
$this->logger->error($error);
throw new ConnectionException($error);
}
$keyAccept = trim($matches[1]);
$expectedResonse
= base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
if ($keyAccept !== $expectedResonse) {
$error = 'Server sent bad upgrade response.';
$this->logger->error($error);
throw new ConnectionException($error);
}
}
$this->logger->info("Client connected to {$address}");
}
/**
* Generate a random string for WebSocket key.
*
* @return string Random string
*/
protected static function generateKey(): string
{
$key = '';
for ($i = 0; $i < 16; $i++) {
$key .= chr(rand(33, 126));
}
return base64_encode($key);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace WebSocket;
use Throwable;
class ConnectionException extends Exception
{
// Native codes in interval 0-106
public const TIMED_OUT = 1024;
public const EOF = 1025;
public const BAD_OPCODE = 1026;
private $data;
public function __construct(string $message, int $code = 0, array $data = [], Throwable $prev = null)
{
parent::__construct($message, $code, $prev);
$this->data = $data;
}
public function getData(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WebSocket;
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace WebSocket\Message;
class Binary extends Message
{
protected $opcode = 'binary';
}

View File

@@ -0,0 +1,8 @@
<?php
namespace WebSocket\Message;
class Close extends Message
{
protected $opcode = 'close';
}

View File

@@ -0,0 +1,25 @@
<?php
namespace WebSocket\Message;
use WebSocket\BadOpcodeException;
class Factory
{
public function create(string $opcode, string $payload = ''): Message
{
switch ($opcode) {
case 'text':
return new Text($payload);
case 'binary':
return new Binary($payload);
case 'ping':
return new Ping($payload);
case 'pong':
return new Pong($payload);
case 'close':
return new Close($payload);
}
throw new BadOpcodeException("Invalid opcode '{$opcode}' provided");
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace WebSocket\Message;
use DateTime;
abstract class Message
{
protected $opcode;
protected $payload;
protected $timestamp;
public function __construct(string $payload = '')
{
$this->payload = $payload;
$this->timestamp = new DateTime();
}
public function getOpcode(): string
{
return $this->opcode;
}
public function getLength(): int
{
return strlen($this->payload);
}
public function getTimestamp(): DateTime
{
return $this->timestamp;
}
public function getContent(): string
{
return $this->payload;
}
public function setContent(string $payload = ''): void
{
$this->payload = $payload;
}
public function hasContent(): bool
{
return $this->payload != '';
}
public function __toString(): string
{
return get_class($this);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace WebSocket\Message;
class Ping extends Message
{
protected $opcode = 'ping';
}

View File

@@ -0,0 +1,8 @@
<?php
namespace WebSocket\Message;
class Pong extends Message
{
protected $opcode = 'pong';
}

View File

@@ -0,0 +1,8 @@
<?php
namespace WebSocket\Message;
class Text extends Message
{
protected $opcode = 'text';
}

View File

@@ -0,0 +1,176 @@
<?php
/**
* Copyright (C) 2014-2020 Textalk/Abicart and contributors.
*
* This file is part of Websocket PHP and is free software under the ISC License.
* License text: https://raw.githubusercontent.com/Textalk/websocket-php/master/COPYING
*/
namespace WebSocket;
class Server extends Base
{
// Default options
protected static $default_options = [
'filter' => ['text', 'binary'],
'fragment_size' => 4096,
'logger' => null,
'port' => 8000,
'return_obj' => false,
'timeout' => null,
];
protected $addr;
protected $port;
protected $listening;
protected $request;
protected $request_path;
/**
* @param array $options
* Associative array containing:
* - timeout: Set the socket timeout in seconds.
* - fragment_size: Set framgemnt size. Default: 4096
* - port: Chose port for listening. Default 8000.
*/
public function __construct(array $options = [])
{
$this->options = array_merge(self::$default_options, $options);
$this->port = $this->options['port'];
$this->setLogger($this->options['logger']);
$error = $errno = $errstr = null;
set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error) {
$this->logger->warning($message, ['severity' => $severity]);
$error = $message;
}, E_ALL);
do {
$this->listening = stream_socket_server("tcp://0.0.0.0:$this->port", $errno, $errstr);
} while ($this->listening === false && $this->port++ < 10000);
restore_error_handler();
if (!$this->listening) {
$error = "Could not open listening socket: {$errstr} ({$errno}) {$error}";
$this->logger->error($error);
throw new ConnectionException($error, (int)$errno);
}
$this->logger->info("Server listening to port {$this->port}");
}
public function __destruct()
{
if ($this->isConnected()) {
fclose($this->socket);
}
$this->socket = null;
}
public function getPort(): int
{
return $this->port;
}
public function getPath(): string
{
return $this->request_path;
}
public function getRequest(): array
{
return $this->request;
}
public function getHeader($header): ?string
{
foreach ($this->request as $row) {
if (stripos($row, $header) !== false) {
list($headername, $headervalue) = explode(":", $row);
return trim($headervalue);
}
}
return null;
}
public function accept(): bool
{
$this->socket = null;
return (bool)$this->listening;
}
protected function connect(): void
{
$error = null;
set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error) {
$this->logger->warning($message, ['severity' => $severity]);
$error = $message;
}, E_ALL);
if (isset($this->options['timeout'])) {
$this->socket = stream_socket_accept($this->listening, $this->options['timeout']);
} else {
$this->socket = stream_socket_accept($this->listening);
}
restore_error_handler();
if (!$this->socket) {
$this->throwException("Server failed to connect. {$error}");
}
if (isset($this->options['timeout'])) {
stream_set_timeout($this->socket, $this->options['timeout']);
}
$this->logger->info("Client has connected to port {port}", [
'port' => $this->port,
'pier' => stream_socket_get_name($this->socket, true),
]);
$this->performHandshake();
}
protected function performHandshake(): void
{
$request = '';
do {
$buffer = stream_get_line($this->socket, 1024, "\r\n");
$request .= $buffer . "\n";
$metadata = stream_get_meta_data($this->socket);
} while (!feof($this->socket) && $metadata['unread_bytes'] > 0);
if (!preg_match('/GET (.*) HTTP\//mUi', $request, $matches)) {
$error = "No GET in request: {$request}";
$this->logger->error($error);
throw new ConnectionException($error);
}
$get_uri = trim($matches[1]);
$uri_parts = parse_url($get_uri);
$this->request = explode("\n", $request);
$this->request_path = $uri_parts['path'];
/// @todo Get query and fragment as well.
if (!preg_match('#Sec-WebSocket-Key:\s(.*)$#mUi', $request, $matches)) {
$error = "Client had no Key in upgrade request: {$request}";
$this->logger->error($error);
throw new ConnectionException($error);
}
$key = trim($matches[1]);
/// @todo Validate key length and base 64...
$response_key = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$header = "HTTP/1.1 101 Switching Protocols\r\n"
. "Upgrade: websocket\r\n"
. "Connection: Upgrade\r\n"
. "Sec-WebSocket-Accept: $response_key\r\n"
. "\r\n";
$this->write($header);
$this->logger->debug("Handshake on {$get_uri}");
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WebSocket;
class TimeoutException extends ConnectionException
{
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Unit tests">
<directory suffix=".php">tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">lib/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,458 @@
<?php
/**
* Test case for Client.
* Note that this test is performed by mocking socket/stream calls.
*/
declare(strict_types=1);
namespace WebSocket;
use PHPUnit\Framework\TestCase;
class ClientTest extends TestCase
{
public function setUp(): void
{
error_reporting(-1);
}
public function testClientMasked(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
$this->assertEquals(4096, $client->getFragmentSize());
MockSocket::initialize('send-receive', $this);
$client->send('Sending a message');
$message = $client->receive();
$this->assertTrue(MockSocket::isEmpty());
$this->assertEquals('text', $client->getLastOpcode());
MockSocket::initialize('client.close', $this);
$this->assertTrue($client->isConnected());
$this->assertNull($client->getCloseStatus());
$client->close();
$this->assertFalse($client->isConnected());
$this->assertEquals(1000, $client->getCloseStatus());
$this->assertTrue(MockSocket::isEmpty());
}
public function testDestruct(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('client.destruct', $this);
}
public function testClienExtendedUrl(): void
{
MockSocket::initialize('client.connect-extended', $this);
$client = new Client('ws://localhost:8000/my/mock/path?my_query=yes#my_fragment');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testClientWithTimeout(): void
{
MockSocket::initialize('client.connect-timeout', $this);
$client = new Client('ws://localhost:8000/my/mock/path', ['timeout' => 300]);
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testClientWithContext(): void
{
MockSocket::initialize('client.connect-context', $this);
$client = new Client('ws://localhost:8000/my/mock/path', ['context' => '@mock-stream-context']);
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testClientAuthed(): void
{
MockSocket::initialize('client.connect-authed', $this);
$client = new Client('wss://usename:password@localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testWithHeaders(): void
{
MockSocket::initialize('client.connect-headers', $this);
$client = new Client('ws://localhost:8000/my/mock/path', [
'origin' => 'Origin header',
'headers' => ['Generic header' => 'Generic content'],
]);
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testPayload128(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
$payload = file_get_contents(__DIR__ . '/mock/payload.128.txt');
MockSocket::initialize('send-receive-128', $this);
$client->send($payload, 'text', false);
$message = $client->receive();
$this->assertEquals($payload, $message);
$this->assertTrue(MockSocket::isEmpty());
}
public function testPayload65536(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
$payload = file_get_contents(__DIR__ . '/mock/payload.65536.txt');
$client->setFragmentSize(65540);
MockSocket::initialize('send-receive-65536', $this);
$client->send($payload, 'text', false);
$message = $client->receive();
$this->assertEquals($payload, $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertEquals(65540, $client->getFragmentSize());
}
public function testMultiFragment(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('send-receive-multi-fragment', $this);
$client->setFragmentSize(8);
$client->send('Multi fragment test');
$message = $client->receive();
$this->assertEquals('Multi fragment test', $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertEquals(8, $client->getFragmentSize());
}
public function testPingPong(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('ping-pong', $this);
$client->send('Server ping', 'ping');
$client->send('', 'ping');
$message = $client->receive();
$this->assertEquals('Receiving a message', $message);
$this->assertEquals('text', $client->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
}
public function testRemoteClose(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $client->receive();
$this->assertNull($message);
$this->assertFalse($client->isConnected());
$this->assertEquals(17260, $client->getCloseStatus());
$this->assertNull($client->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
}
public function testSetTimeout(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('config-timeout', $this);
$client->setTimeout(300);
$this->assertTrue($client->isConnected());
$this->assertTrue(MockSocket::isEmpty());
}
public function testReconnect(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('client.close', $this);
$this->assertTrue($client->isConnected());
$this->assertNull($client->getCloseStatus());
$client->close();
$this->assertFalse($client->isConnected());
$this->assertEquals(1000, $client->getCloseStatus());
$this->assertNull($client->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('client.reconnect', $this);
$message = $client->receive();
$this->assertTrue($client->isConnected());
$this->assertTrue(MockSocket::isEmpty());
}
public function testPersistentConnection(): void
{
MockSocket::initialize('client.connect-persistent', $this);
$client = new Client('ws://localhost:8000/my/mock/path', ['persistent' => true]);
$client->send('Connect');
$client->disconnect();
$this->assertFalse($client->isConnected());
$this->assertTrue(MockSocket::isEmpty());
}
public function testBadScheme(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('bad://localhost:8000/my/mock/path');
$this->expectException('WebSocket\BadUriException');
$this->expectExceptionMessage('Url should have scheme ws or wss');
$client->send('Connect');
}
public function testBadUrl(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('this is not an url');
$this->expectException('WebSocket\BadUriException');
$this->expectExceptionMessage('Invalid url \'this is not an url\' provided.');
$client->send('Connect');
}
public function testBadStreamContext(): void
{
MockSocket::initialize('client.connect-bad-context', $this);
$client = new Client('ws://localhost:8000/my/mock/path', ['context' => 'BAD']);
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Stream context in $options[\'context\'] isn\'t a valid context');
$client->send('Connect');
}
public function testFailedConnection(): void
{
MockSocket::initialize('client.connect-failed', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Could not open socket to "localhost:8000"');
$client->send('Connect');
}
public function testFailedConnectionWithError(): void
{
MockSocket::initialize('client.connect-error', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Could not open socket to "localhost:8000"');
$client->send('Connect');
}
public function testInvalidUpgrade(): void
{
MockSocket::initialize('client.connect-invalid-upgrade', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Connection to \'ws://localhost/my/mock/path\' failed');
$client->send('Connect');
}
public function testInvalidKey(): void
{
MockSocket::initialize('client.connect-invalid-key', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Server sent bad upgrade response');
$client->send('Connect');
}
public function testSendBadOpcode(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('send-bad-opcode', $this);
$this->expectException('WebSocket\BadOpcodeException');
$this->expectExceptionMessage('Bad opcode \'bad\'. Try \'text\' or \'binary\'.');
$client->send('Bad Opcode', 'bad');
}
public function testRecieveBadOpcode(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('receive-bad-opcode', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1026);
$this->expectExceptionMessage('Bad opcode in websocket frame: 12');
$message = $client->receive();
}
public function testBrokenWrite(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('send-broken-write', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1025);
$this->expectExceptionMessage('Could only write 18 out of 22 bytes.');
$client->send('Failing to write');
}
public function testFailedWrite(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('send-failed-write', $this);
$this->expectException('WebSocket\TimeoutException');
$this->expectExceptionCode(1024);
$this->expectExceptionMessage('Failed to write 22 bytes.');
$client->send('Failing to write');
}
public function testBrokenRead(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('receive-broken-read', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1025);
$this->expectExceptionMessage('Broken frame, read 0 of stated 2 bytes.');
$client->receive();
}
public function testHandshakeError(): void
{
MockSocket::initialize('client.connect-handshake-error', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Client handshake error');
$client->send('Connect');
}
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);
$client = new Client('ws://localhost:8000/my/mock/path');
$client->send('Connect');
MockSocket::initialize('receive-empty-read', $this);
$this->expectException('WebSocket\TimeoutException');
$this->expectExceptionCode(1024);
$this->expectExceptionMessage('Empty read; connection dead?');
$client->receive();
}
public function testFrameFragmentation(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client(
'ws://localhost:8000/my/mock/path',
['filter' => ['text', 'binary', 'pong', 'close']]
);
$client->send('Connect');
MockSocket::initialize('receive-fragmentation', $this);
$message = $client->receive();
$this->assertEquals('Server ping', $message);
$this->assertEquals('pong', $client->getLastOpcode());
$message = $client->receive();
$this->assertEquals('Multi fragment test', $message);
$this->assertEquals('text', $client->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $client->receive();
$this->assertEquals('Closing', $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertFalse($client->isConnected());
$this->assertEquals(17260, $client->getCloseStatus());
$this->assertEquals('close', $client->getLastOpcode());
}
public function testMessageFragmentation(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client(
'ws://localhost:8000/my/mock/path',
['filter' => ['text', 'binary', 'pong', 'close'], 'return_obj' => true]
);
$client->send('Connect');
MockSocket::initialize('receive-fragmentation', $this);
$message = $client->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Pong', $message);
$this->assertEquals('Server ping', $message->getContent());
$this->assertEquals('pong', $message->getOpcode());
$message = $client->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Text', $message);
$this->assertEquals('Multi fragment test', $message->getContent());
$this->assertEquals('text', $message->getOpcode());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $client->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Close', $message);
$this->assertEquals('Closing', $message->getContent());
$this->assertEquals('close', $message->getOpcode());
}
public function testConvenicanceMethods(): void
{
MockSocket::initialize('client.connect', $this);
$client = new Client('ws://localhost:8000/my/mock/path');
$this->assertNull($client->getName());
$this->assertNull($client->getPier());
$this->assertEquals('WebSocket\Client(closed)', "{$client}");
$client->text('Connect');
MockSocket::initialize('send-convenicance', $this);
$client->binary(base64_encode('Binary content'));
$client->ping();
$client->pong();
$this->assertEquals('127.0.0.1:12345', $client->getName());
$this->assertEquals('127.0.0.1:8000', $client->getPier());
$this->assertEquals('WebSocket\Client(127.0.0.1:12345)', "{$client}");
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Test case for Exceptions.
*/
declare(strict_types=1);
namespace WebSocket;
use PHPUnit\Framework\TestCase;
use Throwable;
class ExceptionTest extends TestCase
{
public function setUp(): void
{
error_reporting(-1);
}
public function testConnectionException(): void
{
try {
throw new ConnectionException(
'An error message',
ConnectionException::EOF,
['test' => 'with data'],
new TimeoutException(
'Nested exception',
ConnectionException::TIMED_OUT
)
);
} catch (Throwable $e) {
}
$this->assertInstanceOf('WebSocket\ConnectionException', $e);
$this->assertInstanceOf('WebSocket\Exception', $e);
$this->assertInstanceOf('Exception', $e);
$this->assertInstanceOf('Throwable', $e);
$this->assertEquals('An error message', $e->getMessage());
$this->assertEquals(1025, $e->getCode());
$this->assertEquals(['test' => 'with data'], $e->getData());
$p = $e->getPrevious();
$this->assertInstanceOf('WebSocket\TimeoutException', $p);
$this->assertInstanceOf('WebSocket\ConnectionException', $p);
$this->assertEquals('Nested exception', $p->getMessage());
$this->assertEquals(1024, $p->getCode());
$this->assertEquals([], $p->getData());
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Test case for Message subsection.
*/
declare(strict_types=1);
namespace WebSocket;
use PHPUnit\Framework\TestCase;
use WebSocket\Message\Factory;
use WebSocket\Message\Text;
class MessageTest extends TestCase
{
public function setUp(): void
{
error_reporting(-1);
}
public function testFactory(): void
{
$factory = new Factory();
$message = $factory->create('text', 'Some content');
$this->assertInstanceOf('WebSocket\Message\Text', $message);
$message = $factory->create('binary', 'Some content');
$this->assertInstanceOf('WebSocket\Message\Binary', $message);
$message = $factory->create('ping', 'Some content');
$this->assertInstanceOf('WebSocket\Message\Ping', $message);
$message = $factory->create('pong', 'Some content');
$this->assertInstanceOf('WebSocket\Message\Pong', $message);
$message = $factory->create('close', 'Some content');
$this->assertInstanceOf('WebSocket\Message\Close', $message);
}
public function testMessage()
{
$message = new Text('Some content');
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Text', $message);
$this->assertEquals('Some content', $message->getContent());
$this->assertEquals('text', $message->getOpcode());
$this->assertEquals(12, $message->getLength());
$this->assertTrue($message->hasContent());
$this->assertInstanceOf('DateTime', $message->getTimestamp());
$message->setContent('');
$this->assertEquals(0, $message->getLength());
$this->assertFalse($message->hasContent());
$this->assertEquals('WebSocket\Message\Text', "{$message}");
}
public function testBadOpcode()
{
$factory = new Factory();
$this->expectException('WebSocket\BadOpcodeException');
$this->expectExceptionMessage("Invalid opcode 'invalid' provided");
$message = $factory->create('invalid', 'Some content');
}
}

View File

@@ -0,0 +1,29 @@
# Testing
Unit tests with [PHPUnit](https://phpunit.readthedocs.io/).
## How to run
To run all test, run in console.
```
make test
```
## Continuous integration
GitHub Actions are run on PHP versions
`7.2`, `7.3`, `7.4` and `8.0`.
Code coverage by [Coveralls](https://coveralls.io/github/Textalk/websocket-php).
## Test strategy
Test set up overloads various stream and socket functions,
and use "scripts" to define and mock input/output of these functions.
This set up negates the dependency on running servers,
and allow testing various errors that might occur.

View File

@@ -0,0 +1,447 @@
<?php
/**
* Test case for Server.
* Note that this test is performed by mocking socket/stream calls.
*/
declare(strict_types=1);
namespace WebSocket;
use PHPUnit\Framework\TestCase;
class ServerTest extends TestCase
{
public function setUp(): void
{
error_reporting(-1);
}
public function testServerMasked(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertEquals(8000, $server->getPort());
$this->assertEquals('/my/mock/path', $server->getPath());
$this->assertTrue($server->isConnected());
$this->assertEquals(4096, $server->getFragmentSize());
$this->assertNull($server->getCloseStatus());
$this->assertEquals([
'GET /my/mock/path HTTP/1.1',
'host: localhost:8000',
'user-agent: websocket-client-php',
'connection: Upgrade',
'upgrade: websocket',
'sec-websocket-key: cktLWXhUdDQ2OXF0ZCFqOQ==',
'sec-websocket-version: 13',
'',
'',
], $server->getRequest());
$this->assertEquals('websocket-client-php', $server->getHeader('USER-AGENT'));
$this->assertNull($server->getHeader('no such header'));
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('send-receive', $this);
$server->send('Sending a message');
$message = $server->receive();
$this->assertEquals('Receiving a message', $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertNull($server->getCloseStatus());
$this->assertEquals('text', $server->getLastOpcode());
MockSocket::initialize('server.close', $this);
$server->close();
$this->assertFalse($server->isConnected());
$this->assertEquals(1000, $server->getCloseStatus());
$this->assertTrue(MockSocket::isEmpty());
$server->close(); // Already closed
}
public function testDestruct(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept-destruct', $this);
$server->accept();
$message = $server->receive();
}
public function testServerWithTimeout(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server(['timeout' => 300]);
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept-timeout', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue(MockSocket::isEmpty());
}
public function testPayload128(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
$payload = file_get_contents(__DIR__ . '/mock/payload.128.txt');
MockSocket::initialize('send-receive-128', $this);
$server->send($payload, 'text', false);
$message = $server->receive();
$this->assertEquals($payload, $message);
$this->assertTrue(MockSocket::isEmpty());
}
public function testPayload65536(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
$payload = file_get_contents(__DIR__ . '/mock/payload.65536.txt');
$server->setFragmentSize(65540);
MockSocket::initialize('send-receive-65536', $this);
$server->send($payload, 'text', false);
$message = $server->receive();
$this->assertEquals($payload, $message);
$this->assertTrue(MockSocket::isEmpty());
}
public function testMultiFragment(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('send-receive-multi-fragment', $this);
$server->setFragmentSize(8);
$server->send('Multi fragment test');
$message = $server->receive();
$this->assertEquals('Multi fragment test', $message);
$this->assertTrue(MockSocket::isEmpty());
}
public function testPingPong(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('ping-pong', $this);
$server->send('Server ping', 'ping');
$server->send('', 'ping');
$message = $server->receive();
$this->assertEquals('Receiving a message', $message);
$this->assertEquals('text', $server->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
}
public function testRemoteClose(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $server->receive();
$this->assertEquals('', $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertFalse($server->isConnected());
$this->assertEquals(17260, $server->getCloseStatus());
$this->assertNull($server->getLastOpcode());
}
public function testSetTimeout(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('config-timeout', $this);
$server->setTimeout(300);
$this->assertTrue($server->isConnected());
$this->assertTrue(MockSocket::isEmpty());
}
public function testFailedSocketServer(): void
{
MockSocket::initialize('server.construct-failed-socket-server', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Could not open listening socket:');
$server = new Server(['port' => 9999]);
}
public function testFailedSocketServerWithError(): void
{
MockSocket::initialize('server.construct-error-socket-server', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Could not open listening socket:');
$server = new Server(['port' => 9999]);
}
public function testFailedConnect(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept-failed-connect', $this);
$server->accept();
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Server failed to connect');
$server->send('Connect');
}
public function testFailedConnectWithError(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept-error-connect', $this);
$server->accept();
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Server failed to connect');
$server->send('Connect');
}
public function testFailedConnectTimeout(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server(['timeout' => 300]);
MockSocket::initialize('server.accept-failed-connect', $this);
$server->accept();
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Server failed to connect');
$server->send('Connect');
}
public function testFailedHttp(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept-failed-http', $this);
$server->accept();
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('No GET in request');
$server->send('Connect');
}
public function testFailedWsKey(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept-failed-ws-key', $this);
$server->accept();
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Client had no Key in upgrade request');
$server->send('Connect');
}
public function testSendBadOpcode(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
$this->expectException('WebSocket\BadOpcodeException');
$this->expectExceptionCode(0);
$this->expectExceptionMessage('Bad opcode \'bad\'. Try \'text\' or \'binary\'.');
$server->send('Bad Opcode', 'bad');
}
public function testRecieveBadOpcode(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('receive-bad-opcode', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1026);
$this->expectExceptionMessage('Bad opcode in websocket frame: 12');
$message = $server->receive();
}
public function testBrokenWrite(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('send-broken-write', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1025);
$this->expectExceptionMessage('Could only write 18 out of 22 bytes.');
$server->send('Failing to write');
}
public function testFailedWrite(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('send-failed-write', $this);
$this->expectException('WebSocket\TimeoutException');
$this->expectExceptionCode(1024);
$this->expectExceptionMessage('Failed to write 22 bytes.');
$server->send('Failing to write');
}
public function testBrokenRead(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('receive-broken-read', $this);
$this->expectException('WebSocket\ConnectionException');
$this->expectExceptionCode(1025);
$this->expectExceptionMessage('Broken frame, read 0 of stated 2 bytes.');
$server->receive();
}
public function testEmptyRead(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('receive-empty-read', $this);
$this->expectException('WebSocket\TimeoutException');
$this->expectExceptionCode(1024);
$this->expectExceptionMessage('Empty read; connection dead?');
$server->receive();
}
public function testFrameFragmentation(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server(['filter' => ['text', 'binary', 'pong', 'close']]);
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('receive-fragmentation', $this);
$message = $server->receive();
$this->assertEquals('Server ping', $message);
$this->assertEquals('pong', $server->getLastOpcode());
$message = $server->receive();
$this->assertEquals('Multi fragment test', $message);
$this->assertEquals('text', $server->getLastOpcode());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $server->receive();
$this->assertEquals('Closing', $message);
$this->assertTrue(MockSocket::isEmpty());
$this->assertFalse($server->isConnected());
$this->assertEquals(17260, $server->getCloseStatus());
$this->assertEquals('close', $server->getLastOpcode());
}
public function testMessageFragmentation(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server(['filter' => ['text', 'binary', 'pong', 'close'], 'return_obj' => true]);
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->send('Connect');
MockSocket::initialize('receive-fragmentation', $this);
$message = $server->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Pong', $message);
$this->assertEquals('Server ping', $message->getContent());
$this->assertEquals('pong', $message->getOpcode());
$message = $server->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Text', $message);
$this->assertEquals('Multi fragment test', $message->getContent());
$this->assertEquals('text', $message->getOpcode());
$this->assertTrue(MockSocket::isEmpty());
MockSocket::initialize('close-remote', $this);
$message = $server->receive();
$this->assertInstanceOf('WebSocket\Message\Message', $message);
$this->assertInstanceOf('WebSocket\Message\Close', $message);
$this->assertEquals('Closing', $message->getContent());
$this->assertEquals('close', $message->getOpcode());
}
public function testConvenicanceMethods(): void
{
MockSocket::initialize('server.construct', $this);
$server = new Server();
$this->assertNull($server->getName());
$this->assertNull($server->getPier());
$this->assertEquals('WebSocket\Server(closed)', "{$server}");
MockSocket::initialize('server.accept', $this);
$server->accept();
$server->text('Connect');
MockSocket::initialize('send-convenicance', $this);
$server->binary(base64_encode('Binary content'));
$server->ping();
$server->pong();
$this->assertEquals('127.0.0.1:12345', $server->getName());
$this->assertEquals('127.0.0.1:8000', $server->getPier());
$this->assertEquals('WebSocket\Server(127.0.0.1:12345)', "{$server}");
$this->assertTrue(MockSocket::isEmpty());
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace WebSocket;
require dirname(__DIR__) . '/vendor/autoload.php';
require __DIR__ . '/mock/mock-socket.php';

View File

@@ -0,0 +1,34 @@
<?php
/**
* Simple echo logger (only available when running in dev environment)
*/
namespace WebSocket;
class EchoLog implements \Psr\Log\LoggerInterface
{
use \Psr\Log\LoggerTrait;
public function log($level, $message, array $context = [])
{
$message = $this->interpolate($message, $context);
$context_string = empty($context) ? '' : json_encode($context);
echo str_pad($level, 8) . " | {$message} {$context_string}\n";
}
public function interpolate($message, array $context = [])
{
// Build a replacement array with braces around the context keys
$replace = [];
foreach ($context as $key => $val) {
// Check that the value can be cast to string
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace['{' . $key . '}'] = $val;
}
}
// Interpolate replacement values into the message and return
return strtr($message, $replace);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* This class is used by tests to mock and track various socket/stream calls.
*/
namespace WebSocket;
class MockSocket
{
private static $queue = [];
private static $stored = [];
private static $asserter;
// Handler called by function overloads in mock-socket.php
public static function handle($function, $params = [])
{
$current = array_shift(self::$queue);
if ($function == 'get_resource_type' && is_null($current)) {
return null; // Catch destructors
}
self::$asserter->assertEquals($current['function'], $function);
foreach ($current['params'] as $index => $param) {
self::$asserter->assertEquals($param, $params[$index], json_encode([$current, $params]));
}
if (isset($current['error'])) {
$map = array_merge(['msg' => 'Error', 'type' => E_USER_NOTICE], (array)$current['error']);
trigger_error($map['msg'], $map['type']);
}
if (isset($current['return-op'])) {
return self::op($current['return-op'], $params, $current['return']);
}
if (isset($current['return'])) {
return $current['return'];
}
return call_user_func_array($function, $params);
}
// Check if all expected calls are performed
public static function isEmpty(): bool
{
return empty(self::$queue);
}
// Initialize call queue
public static function initialize($op_file, $asserter): void
{
$file = dirname(__DIR__) . "/scripts/{$op_file}.json";
self::$queue = json_decode(file_get_contents($file), true);
self::$asserter = $asserter;
}
// Special output handling
private static function op($op, $params, $data)
{
switch ($op) {
case 'chr-array':
// Convert int array to string
$out = '';
foreach ($data as $val) {
$out .= chr($val);
}
return $out;
case 'file':
$content = file_get_contents(__DIR__ . "/{$data[0]}");
return substr($content, $data[1], $data[2]);
case 'key-save':
preg_match('#Sec-WebSocket-Key:\s(.*)$#mUi', $params[1], $matches);
self::$stored['sec-websocket-key'] = trim($matches[1]);
return $data;
case 'key-respond':
$key = self::$stored['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$encoded = base64_encode(pack('H*', sha1($key)));
return str_replace('{key}', $encoded, $data);
}
return $data;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* This file is used by tests to overload and mock various socket/stream calls.
*/
namespace WebSocket;
function stream_socket_server($local_socket, &$errno, &$errstr)
{
$args = [$local_socket, $errno, $errstr];
return MockSocket::handle('stream_socket_server', $args);
}
function stream_socket_accept()
{
$args = func_get_args();
return MockSocket::handle('stream_socket_accept', $args);
}
function stream_set_timeout()
{
$args = func_get_args();
return MockSocket::handle('stream_set_timeout', $args);
}
function stream_get_line()
{
$args = func_get_args();
return MockSocket::handle('stream_get_line', $args);
}
function stream_get_meta_data()
{
$args = func_get_args();
return MockSocket::handle('stream_get_meta_data', $args);
}
function feof()
{
$args = func_get_args();
return MockSocket::handle('feof', $args);
}
function ftell()
{
$args = func_get_args();
return MockSocket::handle('ftell', $args);
}
function fclose()
{
$args = func_get_args();
return MockSocket::handle('fclose', $args);
}
function fwrite()
{
$args = func_get_args();
return MockSocket::handle('fwrite', $args);
}
function fread()
{
$args = func_get_args();
return MockSocket::handle('fread', $args);
}
function fgets()
{
$args = func_get_args();
return MockSocket::handle('fgets', $args);
}
function stream_context_create()
{
$args = func_get_args();
return MockSocket::handle('stream_context_create', $args);
}
function stream_socket_client($remote_socket, &$errno, &$errstr, $timeout, $flags, $context)
{
$args = [$remote_socket, $errno, $errstr, $timeout, $flags, $context];
return MockSocket::handle('stream_socket_client', $args);
}
function get_resource_type()
{
$args = func_get_args();
return MockSocket::handle('get_resource_type', $args);
}
function stream_socket_get_name()
{
$args = func_get_args();
return MockSocket::handle('stream_socket_get_name', $args);
}

View File

@@ -0,0 +1,5 @@
128-chars
abscdefgheijklmnopqrstuvwxyz0123456789
abscdefgheijklmnopqrstuvwxyz0123456789
abscdefgheijklmnopqrstuvwxyz0123456789
a

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [],
"return": 12
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return":[136, 154]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return":[98, 250, 210, 113]
},
{
"function": "fread",
"params": [
"@mock-stream",
26
],
"return-op": "chr-array",
"return": [97, 18, 145, 29, 13, 137, 183, 81, 3, 153, 185, 31, 13, 141, 190, 20, 6, 157, 183, 21, 88, 218, 227, 65, 82, 202]
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return":true
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "Unknown"
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"ssl:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 248
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,7 @@
[
{
"function": "get_resource_type",
"params": [],
"return": "@mock-bad-context"
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "get_resource_type",
"params": [],
"return": "stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,23 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"error": {
"msg": "A PHP error",
"type": 512
},
"return": false
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 224
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,19 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": false
}
]

View File

@@ -0,0 +1,65 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"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
}
}
]

View File

@@ -0,0 +1,67 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 255
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\nX-Very-Long_Header: This is added to provoke split reads of headers in client 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456\r\n"
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "Next234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,49 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: BAD\r\n\r\n"
}
]

View File

@@ -0,0 +1,49 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return": "Invalid upgrade response\r\n\r\n"
}
]

View File

@@ -0,0 +1,94 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
5,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "persistent stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "persistent stream"
},
{
"function": "ftell",
"params": [
"@mock-stream"
],
"return": 0
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 248
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "persistent stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "persistent stream"
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return":true
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
300,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
300
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,23 @@
[
{
"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
}
]

View File

@@ -0,0 +1,99 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "Unknown"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_context_create",
"params": [],
"return": "@mock-stream-context"
},
{
"function": "stream_socket_client",
"params": [
"tcp:\/\/localhost:8000",
null,
null,
5,
4,
"@mock-stream-context"
],
"return": "@mock-stream"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
5
],
"return": true
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return-op": "key-save",
"return": 199
},
{
"function": "fgets",
"params": [
"@mock-stream",
1024
],
"return-op": "key-respond",
"return": "HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 147]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [33, 111, 149, 174]
},
{
"function": "fread",
"params": [
"@mock-stream",
19
],
"return-op": "chr-array",
"return": [115, 10, 246, 203, 72, 25, 252, 192, 70, 79, 244, 142, 76, 10, 230, 221, 64, 8, 240]
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
}
]

View File

@@ -0,0 +1,55 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [136, 137]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [54, 79, 233, 244]
},
{
"function": "fread",
"params": [
"@mock-stream",
9
],
"return-op": "chr-array",
"return": [117, 35, 170, 152, 89, 60, 128, 154, 81]
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [],
"return": 33
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return": true
}
]

View File

@@ -0,0 +1,24 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
300
],
"return": true
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
}
]

View File

@@ -0,0 +1,150 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 17
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 6
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [138, 139]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [1, 1, 1, 1]
},
{
"function": "fread",
"params": [
"@mock-stream",
11
],
"return-op": "chr-array",
"return": [82, 100, 115, 119, 100, 115, 33, 113, 104, 111, 102]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [138, 128]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [1, 1, 1, 1]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [137, 139]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [180, 77, 192, 201]
},
{
"function": "fread",
"params": [
"@mock-stream",
11
],
"return-op": "chr-array",
"return": [247, 33, 169, 172, 218, 57, 224, 185, 221, 35, 167]
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 17
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 147]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [33, 111, 149, 174]
},
{
"function": "fread",
"params": [
"@mock-stream",
19
],
"return-op": "chr-array",
"return": [115, 10, 246, 203, 72, 25, 252, 192, 70, 79, 244, 142, 76, 10, 230, 221, 64, 8, 240]
}
]

View File

@@ -0,0 +1,18 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [140, 115]
}
]

View File

@@ -0,0 +1,58 @@
[
{
"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": false,
"blocked": true,
"eof": true,
"stream_type": "tcp_socket\/ssl",
"mode": "r+",
"unread_bytes": 2,
"seekable": false
}
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"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": "fclose",
"params": [
"@mock-stream"
],
"return":true
}
]

View File

@@ -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
}
]

View File

@@ -0,0 +1,58 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"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": [
"@mock-stream"
],
"return": "stream"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return":true
}
]

View File

@@ -0,0 +1,126 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [1, 136]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [105, 29, 187, 18]
},
{
"function": "fread",
"params": [
"@mock-stream",
8
],
"return-op": "chr-array",
"return": [36, 104, 215, 102, 0, 61, 221, 96]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [138, 139]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [1, 1, 1, 1]
},
{
"function": "fread",
"params": [
"@mock-stream",
11
],
"return-op": "chr-array",
"return": [82, 100, 115, 119, 100, 115, 33, 113, 104, 111, 102]
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [0, 136]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [221, 240, 46, 69]
},
{
"function": "fread",
"params": [
"@mock-stream",
8
],
"return-op": "chr-array",
"return": [188, 151, 67, 32, 179, 132, 14, 49]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [128, 131]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [9, 60, 117, 193]
},
{
"function": "fread",
"params": [
"@mock-stream",
3
],
"return-op": "chr-array",
"return": [108, 79, 1]
}
]

View File

@@ -0,0 +1,9 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
}
]

View File

@@ -0,0 +1,43 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 18
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"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": "fclose",
"params": [],
"return": true
}
]

View File

@@ -0,0 +1,86 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 26
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 6
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 6
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:8000"
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
}
]

View File

@@ -0,0 +1,43 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "fclose",
"params": [],
"return": true
}
]

View File

@@ -0,0 +1,50 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 132
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 126]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [0, 128]
},
{
"function": "fread",
"params": [
"@mock-stream",
128
],
"return-op": "file",
"return": ["payload.128.txt", 0, 132]
}
]

View File

@@ -0,0 +1,113 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 65546
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 127]
},
{
"function": "fread",
"params": [
"@mock-stream",
8
],
"return-op": "chr-array",
"return": [0, 0, 0, 0, 0, 1, 0, 0]
},
{
"function": "fread",
"params": [
"@mock-stream",
65536
],
"return-op": "file",
"return": ["payload.65536.txt", 0, 16374]
},
{
"function": "fread",
"params": [
"@mock-stream",
49162
],
"return-op": "file",
"return": ["payload.65536.txt", 16374, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
40970
],
"return-op": "file",
"return": ["payload.65536.txt", 24566, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
32778
],
"return-op": "file",
"return": ["payload.65536.txt", 32758, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
24586
],
"return-op": "file",
"return": ["payload.65536.txt", 40950, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
16394
],
"return-op": "file",
"return": ["payload.65536.txt", 49142, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
8202
],
"return-op": "file",
"return": ["payload.65536.txt", 57334, 8192]
},
{
"function": "fread",
"params": [
"@mock-stream",
10
],
"return-op": "file",
"return": ["payload.65536.txt", 65526, 10]
}
]

View File

@@ -0,0 +1,112 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [],
"return": 14
},
{
"function": "fwrite",
"params": [],
"return": 14
},
{
"function": "fwrite",
"params": [],
"return": 9
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [1, 136]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [105, 29, 187, 18]
},
{
"function": "fread",
"params": [
"@mock-stream",
8
],
"return-op": "chr-array",
"return": [36, 104, 215, 102, 0, 61, 221, 96]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [0, 136]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [221, 240, 46, 69]
},
{
"function": "fread",
"params": [
"@mock-stream",
8
],
"return-op": "chr-array",
"return": [188, 151, 67, 32, 179, 132, 14, 49]
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [128, 131]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [9, 60, 117, 193]
},
{
"function": "fread",
"params": [
"@mock-stream",
3
],
"return-op": "chr-array",
"return": [108, 79, 1]
}
]

View File

@@ -0,0 +1,50 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 23
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 147]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [33, 111, 149, 174]
},
{
"function": "fread",
"params": [
"@mock-stream",
19
],
"return-op": "chr-array",
"return": [115, 10, 246, 203, 72, 25, 252, 192, 70, 79, 244, 142, 76, 10, 230, 221, 64, 8, 240]
}
]

View File

@@ -0,0 +1,315 @@
[
{
"function": "stream_socket_accept",
"params": [
"@mock-socket"
],
"return": "@mock-stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "GET \/my\/mock\/path HTTP\/1.1"
},
{
"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": 171,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "host: localhost:8000"
},
{
"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": 149,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "user-agent: websocket-client-php"
},
{
"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": 115,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "connection: Upgrade"
},
{
"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": 94,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "upgrade: websocket"
},
{
"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": 74,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-key: cktLWXhUdDQ2OXF0ZCFqOQ=="
},
{
"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": 29,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-version: 13"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
}
,
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"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": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "fwrite",
"params": [
"@mock-stream",
"HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: YmysboNHNoWzWVeQpduY7xELjgU=\r\n\r\n"
],
"return": 129
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [129, 147]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [33, 111, 149, 174]
},
{
"function": "fread",
"params": [
"@mock-stream",
19
],
"return-op": "chr-array",
"return": [115, 10, 246, 203, 72, 25, 252, 192, 70, 79, 244, 142, 76, 10, 230, 221, 64, 8, 240]
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return": true
}
]

View File

@@ -0,0 +1,18 @@
[
{
"function": "stream_socket_accept",
"params": [],
"error": {
"msg": "A PHP error",
"type": 512
},
"return": false
},
{
"function": "fclose",
"params": [
false
],
"return": true
}
]

View File

@@ -0,0 +1,14 @@
[
{
"function": "stream_socket_accept",
"params": [],
"return": false
},
{
"function": "fclose",
"params": [
false
],
"return": true
}
]

View File

@@ -0,0 +1,265 @@
[
{
"function": "stream_socket_accept",
"params": [
"@mock-socket"
],
"return": "@mock-stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "missing http header"
},
{
"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": 171,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "host: localhost:8000"
},
{
"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": 149,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "user-agent: websocket-client-php"
},
{
"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": 115,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "connection: Upgrade"
},
{
"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": 94,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "upgrade: websocket"
},
{
"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": 74,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "no key in upgrade request"
},
{
"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": 29,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-version: 13"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
}
,
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"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": "feof",
"params": [
"@mock-stream"
],
"return": false
}
]

View File

@@ -0,0 +1,265 @@
[
{
"function": "stream_socket_accept",
"params": [
"@mock-socket"
],
"return": "@mock-stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "GET \/my\/mock\/path HTTP\/1.1"
},
{
"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": 171,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "host: localhost:8000"
},
{
"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": 149,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "user-agent: websocket-client-php"
},
{
"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": 115,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "connection: Upgrade"
},
{
"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": 94,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "upgrade: websocket"
},
{
"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": 74,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "no key in upgrade request"
},
{
"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": 29,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-version: 13"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
}
,
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"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": "feof",
"params": [
"@mock-stream"
],
"return": false
}
]

View File

@@ -0,0 +1,289 @@
[
{
"function": "stream_socket_accept",
"params": [
"@mock-socket",
300
],
"return": "@mock-stream"
},
{
"function": "stream_set_timeout",
"params": [
"@mock-stream",
300
],
"return": true
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "GET \/my\/mock\/path HTTP\/1.1"
},
{
"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": 171,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "host: localhost:8000"
},
{
"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": 149,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "user-agent: websocket-client-php"
},
{
"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": 115,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "connection: Upgrade"
},
{
"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": 94,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "upgrade: websocket"
},
{
"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": 74,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-key: cktLWXhUdDQ2OXF0ZCFqOQ=="
},
{
"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": 29,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-version: 13"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
}
,
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"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": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "fwrite",
"params": [
"@mock-stream",
"HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: YmysboNHNoWzWVeQpduY7xELjgU=\r\n\r\n"
],
"return": 129
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
}
]

View File

@@ -0,0 +1,287 @@
[
{
"function": "stream_socket_accept",
"params": [
"@mock-socket"
],
"return": "@mock-stream"
},
{
"function": "stream_socket_get_name",
"params": [
"@mock-stream"
],
"return": "127.0.0.1:12345"
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "GET \/my\/mock\/path HTTP\/1.1"
},
{
"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": 171,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "host: localhost:8000"
},
{
"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": 149,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "user-agent: websocket-client-php"
},
{
"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": 115,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "connection: Upgrade"
},
{
"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": 94,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "upgrade: websocket"
},
{
"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": 74,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-key: cktLWXhUdDQ2OXF0ZCFqOQ=="
},
{
"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": 29,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"return": "sec-websocket-version: 13"
},
{
"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": 2,
"seekable": false
}
},
{
"function": "feof",
"params": [
"@mock-stream"
],
"return": false
}
,
{
"function": "stream_get_line",
"params": [
"@mock-stream",
1024,
"\r\n"
],
"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": "feof",
"params": [
"@mock-stream"
],
"return": false
},
{
"function": "fwrite",
"params": [
"@mock-stream",
"HTTP\/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: YmysboNHNoWzWVeQpduY7xELjgU=\r\n\r\n"
],
"return": 129
},
{
"function": "fwrite",
"params": [
"@mock-stream"
],
"return": 13
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
}
]

View File

@@ -0,0 +1,70 @@
[
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": true
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fwrite",
"params": [],
"return":12
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "stream"
},
{
"function": "fread",
"params": [
"@mock-stream",
2
],
"return-op": "chr-array",
"return": [136,154]
},
{
"function": "fread",
"params": [
"@mock-stream",
4
],
"return-op": "chr-array",
"return": [245,55,62,8]
},
{
"function": "fread",
"params": [
"@mock-stream",
26
],
"return-op": "chr-array",
"return": [246,223,125,100,154,68,91,40,148,84,85,102,154,64,82,109,145,80,91,108,207,23,15,56,197,7]
},
{
"function": "fclose",
"params": [
"@mock-stream"
],
"return": true
},
{
"function": "get_resource_type",
"params": [
"@mock-stream"
],
"return": "Unknown"
}
]

View File

@@ -0,0 +1,28 @@
[
{
"function": "stream_socket_server",
"params": [
"tcp://0.0.0.0:9999",
null,
null
],
"error": {
"msg": "A PHP error",
"type": 512
},
"return": false
},
{
"function": "stream_socket_server",
"params": [
"tcp://0.0.0.0:10000",
null,
null
],
"error": {
"msg": "A PHP error",
"type": 512
},
"return": false
}
]

View File

@@ -0,0 +1,20 @@
[
{
"function": "stream_socket_server",
"params": [
"tcp://0.0.0.0:9999",
null,
null
],
"return": false
},
{
"function": "stream_socket_server",
"params": [
"tcp://0.0.0.0:10000",
null,
null
],
"return": false
}
]

View File

@@ -0,0 +1,11 @@
[
{
"function": "stream_socket_server",
"params": [
"tcp://0.0.0.0:8000",
null,
null
],
"return": "@mock-socket"
}
]