Skip to content

Commit 0f36ee3

Browse files
authored
Merge pull request swooletw#286 from killtw/handshake
feat: custom handshake event
2 parents 4e9b376 + 7b0ca35 commit 0f36ee3

File tree

7 files changed

+243
-6
lines changed

7 files changed

+243
-6
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"php-coveralls/php-coveralls": "^2.1",
3636
"mockery/mockery": "~1.0",
3737
"codedungeon/phpunit-result-printer": "^0.14.0",
38-
"php-mock/php-mock": "^2.0"
38+
"php-mock/php-mock": "^2.0",
39+
"swoole/ide-helper": "@dev"
3940
},
4041
"autoload": {
4142
"files": [

config/swoole_websocket.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@
3535
// SwooleTW\Http\Websocket\Middleware\Authenticate::class,
3636
],
3737

38+
/*
39+
|--------------------------------------------------------------------------
40+
| Websocket handler for onHandShake callback
41+
| If enable, onOpen would not be triggered
42+
|--------------------------------------------------------------------------
43+
*/
44+
'handshake' => [
45+
'enabled' => false,
46+
'handler' => SwooleTW\Http\Websocket\HandShakeHandler::class,
47+
],
48+
3849
/*
3950
|--------------------------------------------------------------------------
4051
| Default websocket driver

src/Concerns/InteractsWithWebsocket.php

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ trait InteractsWithWebsocket
4141
protected $payloadParser;
4242

4343
/**
44-
* @var SwooleTW\Http\Websocket\Rooms\RoomContract
44+
* @var \SwooleTW\Http\Websocket\Rooms\RoomContract
4545
*/
4646
protected $websocketRoom;
4747

@@ -88,6 +88,46 @@ public function onOpen($server, $swooleRequest)
8888
}
8989
}
9090

91+
/**
92+
* @param \Swoole\Http\Request $swooleRequest
93+
* @param \Swoole\Http\Response $response
94+
*
95+
* @return bool
96+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
97+
*/
98+
public function onHandShake($swooleRequest, $response)
99+
{
100+
$illuminateRequest = Request::make($swooleRequest)->toIlluminate();
101+
$websocket = $this->app->make(Websocket::class);
102+
$sandbox = $this->app->make(Sandbox::class);
103+
$handler = $this->container->make('config')->get('swoole_websocket.handshake.handler');
104+
105+
try {
106+
$websocket->reset(true)->setSender($swooleRequest->fd);
107+
// set currnt request to sandbox
108+
$sandbox->setRequest($illuminateRequest);
109+
// enable sandbox
110+
$sandbox->enable();
111+
112+
if (! $this->app->make($handler)->handle($swooleRequest, $response)) {
113+
return false;
114+
}
115+
// trigger 'connect' websocket event
116+
if ($websocket->eventExists('connect')) {
117+
// set sandbox container to websocket pipeline
118+
$websocket->setContainer($sandbox->getApplication());
119+
$websocket->call('connect', $illuminateRequest);
120+
}
121+
122+
return true;
123+
} catch (Throwable $e) {
124+
$this->logServerError($e);
125+
} finally {
126+
// disable and recycle sandbox resource
127+
$sandbox->disable();
128+
}
129+
}
130+
91131
/**
92132
* "onMessage" listener.
93133
*
@@ -160,14 +200,16 @@ public function onClose($server, $fd, $reactorId)
160200
* Indicates if a packet is websocket push action.
161201
*
162202
* @param mixed
203+
*
204+
* @return bool
163205
*/
164206
protected function isWebsocketPushPacket($packet)
165207
{
166208
if (! is_array($packet)) {
167209
return false;
168210
}
169211

170-
return $this->isWebsocket
212+
return $this->isServerWebsocket
171213
&& array_key_exists('action', $packet)
172214
&& $packet['action'] === Websocket::PUSH_ACTION;
173215
}
@@ -220,7 +262,9 @@ protected function prepareWebsocket()
220262
$parser = $config->get('swoole_websocket.parser');
221263

222264
if ($isWebsocket) {
223-
$this->events = array_merge($this->events ?? [], $this->wsEvents);
265+
$handshake = $config->get('swoole_websocket.handshake.enabled');
266+
267+
$this->events = array_merge($this->events ?? [], array_merge($this->wsEvents, $handshake ? ['handshake'] : []));
224268
$this->isServerWebsocket = true;
225269
$this->prepareWebsocketRoom();
226270
$this->setPayloadParser(new $parser);

src/Websocket/HandShakeHandler.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace SwooleTW\Http\Websocket;
4+
5+
/**
6+
* Class HandShakeHandler
7+
*/
8+
class HandShakeHandler
9+
{
10+
/**
11+
* @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server
12+
*
13+
* @param \Swoole\Http\Request $request
14+
* @param \Swoole\Http\Response $response
15+
*
16+
* @return bool
17+
*/
18+
public function handle($request, $response)
19+
{
20+
$socketkey = $request->header['sec-websocket-key'];
21+
22+
if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $socketkey) || 16 !== strlen(base64_decode($socketkey))) {
23+
$response->end();
24+
25+
return false;
26+
}
27+
28+
$headers = [
29+
'Upgrade' => 'websocket',
30+
'Connection' => 'Upgrade',
31+
'Sec-WebSocket-Accept' => base64_encode(sha1($socketkey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)),
32+
'Sec-WebSocket-Version' => '13',
33+
];
34+
35+
if (isset($request->header['sec-websocket-protocol'])) {
36+
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
37+
}
38+
39+
foreach ($headers as $header => $val) {
40+
$response->header($header, $val);
41+
}
42+
43+
$response->status(101);
44+
$response->end();
45+
46+
return true;
47+
}
48+
}

src/Websocket/Pusher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected function __construct(
8888
* @param array $data
8989
* @param \Swoole\Websocket\Server $server
9090
*
91-
* @return \SwooleTW\Http\Websocket\Push
91+
* @return \SwooleTW\Http\Websocket\Pusher
9292
*/
9393
public static function make(array $data, $server)
9494
{

tests/Server/ManagerTest.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
use SwooleTW\Http\Server\Sandbox;
1111
use SwooleTW\Http\Tests\TestCase;
1212
use Illuminate\Container\Container;
13+
use SwooleTW\Http\Websocket\HandShakeHandler;
1314
use SwooleTW\Http\Websocket\Parser;
1415
use SwooleTW\Http\Server\PidManager;
1516
use SwooleTW\Http\Table\SwooleTable;
1617
use Laravel\Lumen\Exceptions\Handler;
1718
use Illuminate\Support\Facades\Config;
1819
use SwooleTW\Http\Websocket\Websocket;
1920
use SwooleTW\Http\Server\Facades\Server;
20-
use SwooleTW\Http\Websocket\Facades\Pusher;
2121
use SwooleTW\Http\Websocket\HandlerContract;
2222
use SwooleTW\Http\Websocket\Rooms\TableRoom;
2323
use SwooleTW\Http\Websocket\Rooms\RoomContract;
@@ -48,6 +48,8 @@ class ManagerTest extends TestCase
4848
'swoole_http.websocket.enabled' => true,
4949
'swoole_websocket.parser' => SocketIOParser::class,
5050
'swoole_websocket.handler' => WebsocketHandler::class,
51+
'swoole_websocket.handshake.enabled' => true,
52+
'swoole_websocket.handshake.handler' => HandShakeHandler::class,
5153
'swoole_websocket.default' => 'table',
5254
'swoole_websocket.settings.table' => [
5355
'room_rows' => 10,
@@ -414,6 +416,66 @@ public function testOnOpen()
414416
$manager->onOpen(m::mock('server'), $request);
415417
}
416418

419+
public function testOnHandShake()
420+
{
421+
$request = m::mock(Request::class);
422+
$request->shouldReceive('rawcontent')
423+
->once()
424+
->andReturn([]);
425+
$request->fd = 1;
426+
$request->header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
427+
428+
$response = m::mock(Response::class);
429+
$response->shouldReceive('header')->withAnyArgs()->times(4)->andReturnSelf();
430+
$response->shouldReceive('status')->with(101)->once()->andReturnSelf();
431+
$response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
432+
433+
$container = $this->getContainer($this->getServer(), $this->getConfig(true));
434+
$container->singleton(Websocket::class, function () {
435+
$websocket = m::mock(Websocket::class);
436+
$websocket->shouldReceive('reset')
437+
->with(true)
438+
->once()
439+
->andReturnSelf();
440+
$websocket->shouldReceive('setSender')
441+
->with(1)
442+
->once();
443+
$websocket->shouldReceive('eventExists')
444+
->with('connect')
445+
->once()
446+
->andReturn(true);
447+
$websocket->shouldReceive('setContainer')
448+
->with(m::type(Container::class))
449+
->once();
450+
$websocket->shouldReceive('call')
451+
->with('connect', m::type('Illuminate\Http\Request'))
452+
->once();
453+
454+
return $websocket;
455+
});
456+
$container->singleton(Sandbox::class, function () {
457+
$sandbox = m::mock(Sandbox::class);
458+
$sandbox->shouldReceive('setRequest')
459+
->with(m::type('Illuminate\Http\Request'))
460+
->once();
461+
$sandbox->shouldReceive('enable')
462+
->once();
463+
$sandbox->shouldReceive('disable')
464+
->once();
465+
$sandbox->shouldReceive('getApplication')
466+
->once()
467+
->andReturn(m::mock(Container::class));
468+
469+
return $sandbox;
470+
});
471+
472+
$container->alias(Sandbox::class, 'swoole.sandbox');
473+
474+
$manager = $this->getWebsocketManager();
475+
$manager->setApplication($container);
476+
$manager->onHandShake($request, $response);
477+
}
478+
417479
public function testOnMessage()
418480
{
419481
$frame = m::mock('frame');
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace SwooleTW\Http\Tests\Websocket;
4+
5+
use Mockery as m;
6+
use Swoole\Http\Request;
7+
use Swoole\Http\Response;
8+
use SwooleTW\Http\Tests\TestCase;
9+
use SwooleTW\Http\Websocket\HandShakeHandler;
10+
11+
class HandShakeHandlerTest extends TestCase
12+
{
13+
public function testHandle()
14+
{
15+
// arrange
16+
$request = m::mock(Request::class);
17+
$request->header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
18+
19+
$response = m::mock(Response::class);
20+
$response->shouldReceive('header')->withAnyArgs()->times(4)->andReturnSelf();
21+
$response->shouldReceive('status')->with(101)->once()->andReturnSelf();
22+
$response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
23+
24+
$handler = new HandShakeHandler;
25+
26+
// act
27+
$actual = $handler->handle($request, $response);
28+
29+
// assert
30+
$this->assertTrue($actual);
31+
}
32+
33+
public function testHandleReturnFalse()
34+
{
35+
// arrange
36+
$request = m::mock(Request::class);
37+
$request->header['sec-websocket-key'] = 'test';
38+
39+
$response = m::mock(Response::class);
40+
$response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
41+
42+
$handler = new HandShakeHandler;
43+
44+
// act
45+
$actual = $handler->handle($request, $response);
46+
47+
// assert
48+
$this->assertFalse($actual);
49+
}
50+
51+
public function testHandleWithSecWebsocketProtocol()
52+
{
53+
// arrange
54+
$request = m::mock(Request::class);
55+
$request->header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
56+
$request->header['sec-websocket-protocol'] = 'graphql-ws';
57+
58+
$response = m::mock(Response::class);
59+
$response->shouldReceive('header')->withAnyArgs()->times(5)->andReturnSelf();
60+
$response->shouldReceive('status')->with(101)->once()->andReturnSelf();
61+
$response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
62+
63+
$handler = new HandShakeHandler;
64+
65+
// act
66+
$actual = $handler->handle($request, $response);
67+
68+
// assert
69+
$this->assertTrue($actual);
70+
}
71+
}

0 commit comments

Comments
 (0)