diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000..4cbca96 --- /dev/null +++ b/.github/workflows/close-pull-request.yml @@ -0,0 +1,9 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [ opened ] + +jobs: + run: + uses: hyperf/.github/.github/workflows/close-pull-request.yml@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f7d23f..8ec4a62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,18 +8,4 @@ name: Release jobs: release: - name: Release - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false + uses: hyperf/.github/.github/workflows/release.yml@master diff --git a/composer.json b/composer.json index 5852cb1..5164b43 100644 --- a/composer.json +++ b/composer.json @@ -10,26 +10,18 @@ ], "homepage": "https://hyperf.io", "support": { - "docs": "https://hyperf.wiki", "issues": "https://github.com/hyperf/hyperf/issues", - "pull-request": "https://github.com/hyperf/hyperf/pulls", - "source": "https://github.com/hyperf/hyperf" + "source": "https://github.com/hyperf/hyperf", + "docs": "https://hyperf.wiki", + "pull-request": "https://github.com/hyperf/hyperf/pulls" }, "require": { - "php": ">=7.2", - "hyperf/contract": "~2.1.0", - "hyperf/http-message": "~2.1.0", - "hyperf/utils": "~2.1.0", - "psr/container": "^1.0", - "psr/log": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.9", - "malukenho/docheader": "^0.1.6", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^9.4" - }, - "suggest": { + "php": ">=8.1", + "hyperf/contract": "~3.1.0", + "hyperf/http-message": "~3.1.0", + "hyperf/stringable": "~3.1.0", + "hyperf/support": "~3.1.0", + "psr/container": "^1.0 || ^2.0" }, "autoload": { "psr-4": { @@ -46,16 +38,10 @@ }, "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.1-dev" }, "hyperf": { "config": "Hyperf\\WebSocketClient\\ConfigProvider" } - }, - "bin": [ - ], - "scripts": { - "cs-fix": "php-cs-fixer fix $1", - "test": "phpunit --colors=always" } } diff --git a/src/Client.php b/src/Client.php index c86364f..da53864 100644 --- a/src/Client.php +++ b/src/Client.php @@ -9,28 +9,22 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace Hyperf\WebSocketClient; +use Hyperf\HttpMessage\Server\Response; use Hyperf\WebSocketClient\Exception\ConnectException; use Psr\Http\Message\UriInterface; use Swoole\Coroutine; +use Swoole\WebSocket\CloseFrame as SwCloseFrame; use Swoole\WebSocket\Frame as SwFrame; class Client { - /** - * @var UriInterface - */ - protected $uri; + protected Coroutine\Http\Client $client; - /** - * @var Coroutine\Http\Client - */ - protected $client; - - public function __construct(UriInterface $uri) + public function __construct(protected UriInterface $uri, array $headers = []) { - $this->uri = $uri; $host = $uri->getHost(); $port = $uri->getPort(); $ssl = $uri->getScheme() === 'wss'; @@ -40,7 +34,7 @@ public function __construct(UriInterface $uri) } $this->client = new Coroutine\Http\Client($host, $port, $ssl); - + $headers && $this->client->setHeaders($headers); parse_str($this->uri->getQuery(), $query); $query = http_build_query($query); @@ -50,8 +44,15 @@ public function __construct(UriInterface $uri) $ret = $this->client->upgrade($path); if (! $ret) { - $errCode = $this->client->errCode; - throw new ConnectException(sprintf('Websocket upgrade failed by [%s] [%s].', $errCode, swoole_strerror($errCode))); + if ($this->client->errCode !== 0) { + $errCode = $this->client->errCode; + $errMsg = $this->client->errMsg; + } else { + $errCode = $this->client->statusCode; + $errMsg = Response::getReasonPhraseByCode($errCode); + } + + throw new ConnectException(sprintf('Websocket upgrade failed by [%s] [%s].', $errCode, $errMsg)); } } @@ -63,22 +64,20 @@ public function __destruct() public function recv(float $timeout = -1) { $ret = $this->client->recv($timeout); - if ($ret instanceof SwFrame) { - return new Frame($ret); - } - return $ret; + return match (true) { + $ret instanceof SwCloseFrame => new CloseFrame($ret), + $ret instanceof SwFrame => new Frame($ret), + default => $ret, + }; } /** * @param int $flags SWOOLE_WEBSOCKET_FLAG_FIN or SWOOLE_WEBSOCKET_FLAG_COMPRESS */ - public function push(string $data, int $opcode = WEBSOCKET_OPCODE_TEXT, int $flags = null): bool + public function push(string $data, int $opcode = WEBSOCKET_OPCODE_TEXT, ?int $flags = null): bool { - if (isset($flags)) { - return $this->client->push($data, $opcode, $flags); - } - return $this->client->push($data, $opcode); + return $this->client->push($data, $opcode, $flags); } public function close(): bool diff --git a/src/ClientFactory.php b/src/ClientFactory.php index 02271ac..d48ea95 100644 --- a/src/ClientFactory.php +++ b/src/ClientFactory.php @@ -9,19 +9,23 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace Hyperf\WebSocketClient; use Hyperf\HttpMessage\Uri\Uri; -use Hyperf\Utils\Str; +use Hyperf\Stringable\Str; + +use function Hyperf\Coroutine\defer; +use function Hyperf\Support\make; class ClientFactory { - public function create(string $uri, bool $autoClose = true): Client + public function create(string $uri, bool $autoClose = true, array $headers = []): Client { if (! Str::startsWith($uri, ['ws://', 'wss://'])) { $uri = 'ws://' . $uri; } - $client = make(Client::class, ['uri' => new Uri($uri)]); + $client = make(Client::class, ['uri' => new Uri($uri), 'headers' => $headers]); if ($autoClose) { defer(function () use ($client) { $client->close(); diff --git a/src/CloseFrame.php b/src/CloseFrame.php new file mode 100644 index 0000000..0b6fa62 --- /dev/null +++ b/src/CloseFrame.php @@ -0,0 +1,30 @@ +code = $frame->code; + $this->reason = $frame->reason; + } +} diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 81b9e13..df9062c 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -9,6 +9,7 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace Hyperf\WebSocketClient; class ConfigProvider @@ -16,13 +17,6 @@ class ConfigProvider public function __invoke(): array { return [ - 'annotations' => [ - 'scan' => [ - 'paths' => [ - __DIR__, - ], - ], - ], ]; } } diff --git a/src/Exception/ConnectException.php b/src/Exception/ConnectException.php index 46e7842..6289f56 100644 --- a/src/Exception/ConnectException.php +++ b/src/Exception/ConnectException.php @@ -9,8 +9,11 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace Hyperf\WebSocketClient\Exception; -class ConnectException extends \RuntimeException +use RuntimeException; + +class ConnectException extends RuntimeException { } diff --git a/src/Frame.php b/src/Frame.php index 9f99b35..60a4ed3 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -9,26 +9,19 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace Hyperf\WebSocketClient; +use Stringable; use Swoole\WebSocket\Frame as SwFrame; -class Frame +class Frame implements Stringable { - /** - * @var bool - */ - public $finish = true; + public bool $finish = true; - /** - * @var string - */ - public $opcode; + public int $opcode; - /** - * @var string - */ - public $data; + public string $data; public function __construct(SwFrame $frame) { @@ -37,7 +30,7 @@ public function __construct(SwFrame $frame) $this->data = $frame->data; } - public function __toString() + public function __toString(): string { return $this->data; } @@ -53,7 +46,7 @@ public function getOpcodeDefinition(): string return $map[$this->opcode] ?? 'WEBSOCKET_BAD_OPCODE'; } - public function getOpcode(): string + public function getOpcode(): int { return $this->opcode; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 0accaf0..56bfe3a 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -9,22 +9,52 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace HyperfTest\WebSocketClient; +use Hyperf\Codec\Json; use Hyperf\HttpMessage\Uri\Uri; use Hyperf\WebSocketClient\Client; +use Hyperf\WebSocketClient\Exception\ConnectException; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; /** * @internal * @coversNothing */ +#[CoversNothing] class ClientTest extends TestCase { public function testClientConnectFailed() { - $this->expectException(\Hyperf\WebSocketClient\Exception\ConnectException::class); + $this->expectException(ConnectException::class); new Client(new Uri('ws://172.168.1.1:9522')); } + + public function testClientConnected() + { + $client = new Client(new Uri('ws://127.0.0.1:10002/ws')); + + $client->push('ping'); + + $this->assertSame('pong', $client->recv(1)->data); + + $client->close(); + } + + public function testClientHeaders() + { + $client = new Client(new Uri('ws://127.0.0.1:10002/ws'), ['x-token' => $token = uniqid()]); + + $client->push('headers'); + + $data = $client->recv(1); + $headers = Json::decode($data->data); + + $this->assertSame($token, $headers['x-token']); + + $client->close(); + } } diff --git a/tests/FrameTest.php b/tests/FrameTest.php index 72d55af..eac4f56 100644 --- a/tests/FrameTest.php +++ b/tests/FrameTest.php @@ -9,10 +9,12 @@ * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + namespace HyperfTest\WebSocketClient; use Hyperf\WebSocketClient\Frame; use Mockery; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; use Swoole\WebSocket\Frame as SwFrame; @@ -20,11 +22,15 @@ * @internal * @coversNothing */ +#[CoversNothing] class FrameTest extends TestCase { public function testFrame() { - $frame = new Frame(Mockery::mock(SwFrame::class)); + $swframe = Mockery::mock(SwFrame::class); + $swframe->finish = true; + $swframe->opcode = 1; + $frame = new Frame($swframe); $this->assertSame($frame->data, (string) $frame); }