-
Notifications
You must be signed in to change notification settings - Fork 70
/
Router.php
146 lines (122 loc) · 4.64 KB
/
Router.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
namespace Laravel\Reverb\Servers\Reverb\Http;
use Closure;
use GuzzleHttp\Psr7\Message;
use Illuminate\Support\Arr;
use Laravel\Reverb\Servers\Reverb\Concerns\ClosesConnections;
use Laravel\Reverb\Servers\Reverb\Connection as ReverbConnection;
use Psr\Http\Message\RequestInterface;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use Ratchet\RFC6455\Handshake\ServerNegotiator;
use React\Promise\PromiseInterface;
use ReflectionFunction;
use ReflectionMethod;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
class Router
{
use ClosesConnections;
/**
* The server negotiator instance.
*/
protected ServerNegotiator $negotiator;
/**
* Create a new router instance.
*/
public function __construct(protected UrlMatcherInterface $matcher)
{
$this->negotiator = new ServerNegotiator(new RequestVerifier);
}
/**
* Dispatch the request to the appropriate controller.
*/
public function dispatch(RequestInterface $request, Connection $connection): mixed
{
$uri = $request->getUri();
$context = $this->matcher->getContext();
$context->setMethod($request->getMethod());
$context->setHost($uri->getHost());
try {
$route = $this->matcher->match($uri->getPath());
} catch (MethodNotAllowedException $e) {
return $this->close($connection, 405, 'Method not allowed.', ['Allow' => $e->getAllowedMethods()]);
} catch (ResourceNotFoundException $e) {
return $this->close($connection, 404, 'Not found.');
}
$controller = $this->controller($route);
if ($this->isWebSocketRequest($request)) {
$wsConnection = $this->attemptUpgrade($request, $connection);
return $controller($request, $wsConnection, ...Arr::except($route, ['_controller', '_route']));
}
$routeParameters = Arr::except($route, [
'_controller',
'_route',
]) + ['request' => $request, 'connection' => $connection];
$response = $controller(
...$this->arguments($controller, $routeParameters)
);
return $response instanceof PromiseInterface ?
$response->then(fn ($response) => $connection->send($response)->close()) :
$connection->send($response)->close();
}
/**
* Get the controller callable for the given route.
*
* @param array<string, mixed> $route
*/
protected function controller(array $route): callable
{
return $route['_controller'];
}
/**
* Determine whether the request is for a WebSocket connection.
*/
protected function isWebSocketRequest(RequestInterface $request): bool
{
return $request->getHeader('Upgrade')[0] ?? null === 'websocket';
}
/**
* Negotiate the WebSocket connection upgrade.
*/
protected function attemptUpgrade(RequestInterface $request, Connection $connection): ReverbConnection
{
$response = $this->negotiator->handshake($request);
$connection->write(Message::toString($response));
return new ReverbConnection($connection);
}
/**
* Get the arguments for the controller.
*
* @return array<int, mixed>
*/
protected function arguments(callable $controller, array $routeParameters): array
{
$parameters = $this->parameters($controller);
return array_map(function ($parameter) use ($routeParameters) {
return $routeParameters[$parameter['name']] ?? null;
}, $parameters);
}
/**
* Get the parameters for the controller.
*
* @return array<int, array{ name: string, type: string, position: int }>
*/
protected function parameters(mixed $controller): array
{
$method = match (true) {
$controller instanceof Closure => new ReflectionFunction($controller),
is_string($controller) => count($parts = explode('::', $controller)) > 1 ? new ReflectionMethod(...$parts) : new ReflectionFunction($controller),
! is_array($controller) => new ReflectionMethod($controller, '__invoke'),
is_array($controller) => new ReflectionMethod($controller[0], $controller[1]),
};
$parameters = $method->getParameters();
return array_map(function ($parameter) {
return [
'name' => $parameter->getName(),
'type' => $parameter->getType()->getName(),
'position' => $parameter->getPosition(),
];
}, $parameters);
}
}