2018-11-20 13:50:37 +00:00
|
|
|
<?php
|
|
|
|
|
|
2018-11-27 15:42:19 +00:00
|
|
|
namespace BeyondCode\LaravelWebSockets\HttpApi\Controllers;
|
2018-11-20 13:50:37 +00:00
|
|
|
|
2018-11-22 20:36:25 +00:00
|
|
|
use Exception;
|
2019-01-02 21:30:57 +00:00
|
|
|
use Pusher\Pusher;
|
2019-02-27 14:27:21 +00:00
|
|
|
use Illuminate\Support\Arr;
|
2018-11-21 11:13:40 +00:00
|
|
|
use Illuminate\Http\Request;
|
2018-11-22 20:36:25 +00:00
|
|
|
use GuzzleHttp\Psr7\Response;
|
2018-11-20 13:50:37 +00:00
|
|
|
use Ratchet\ConnectionInterface;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
2018-11-21 11:13:40 +00:00
|
|
|
use GuzzleHttp\Psr7\ServerRequest;
|
2019-02-27 14:24:00 +00:00
|
|
|
use Illuminate\Support\Collection;
|
2019-03-29 19:33:46 +00:00
|
|
|
use React\Promise\PromiseInterface;
|
2018-11-20 13:50:37 +00:00
|
|
|
use Ratchet\Http\HttpServerInterface;
|
|
|
|
|
use Psr\Http\Message\RequestInterface;
|
2018-12-04 21:22:33 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\Apps\App;
|
|
|
|
|
use BeyondCode\LaravelWebSockets\QueryParameters;
|
2018-11-22 20:36:25 +00:00
|
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
2018-11-21 11:13:40 +00:00
|
|
|
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
|
2018-11-27 15:35:28 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
|
2018-11-20 13:50:37 +00:00
|
|
|
|
2018-11-27 14:59:02 +00:00
|
|
|
abstract class Controller implements HttpServerInterface
|
2018-11-20 13:50:37 +00:00
|
|
|
{
|
2019-02-27 14:24:00 +00:00
|
|
|
/** @var string */
|
|
|
|
|
protected $requestBuffer = '';
|
|
|
|
|
|
|
|
|
|
/** @var RequestInterface */
|
|
|
|
|
protected $request;
|
|
|
|
|
|
|
|
|
|
/** @var int */
|
|
|
|
|
protected $contentLength;
|
|
|
|
|
|
2019-03-29 19:33:46 +00:00
|
|
|
/** @var ChannelManager */
|
2018-11-22 23:24:40 +00:00
|
|
|
protected $channelManager;
|
|
|
|
|
|
|
|
|
|
public function __construct(ChannelManager $channelManager)
|
|
|
|
|
{
|
|
|
|
|
$this->channelManager = $channelManager;
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-22 20:36:25 +00:00
|
|
|
public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
|
2018-11-20 13:50:37 +00:00
|
|
|
{
|
2019-02-27 14:24:00 +00:00
|
|
|
$this->request = $request;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2019-02-27 14:24:00 +00:00
|
|
|
$this->contentLength = $this->findContentLength($request->getHeaders());
|
2018-11-22 20:36:25 +00:00
|
|
|
|
2019-02-27 14:23:47 +00:00
|
|
|
$this->requestBuffer = (string) $request->getBody();
|
2018-11-22 20:36:25 +00:00
|
|
|
|
2019-03-25 22:00:54 +00:00
|
|
|
if (! $this->verifyContentLength()) {
|
2019-03-24 04:56:47 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->handleRequest($connection);
|
2019-02-27 14:24:00 +00:00
|
|
|
}
|
2018-11-22 20:36:25 +00:00
|
|
|
|
2019-02-27 14:24:00 +00:00
|
|
|
protected function findContentLength(array $headers): int
|
|
|
|
|
{
|
|
|
|
|
return Collection::make($headers)->first(function ($values, $header) {
|
|
|
|
|
return strtolower($header) === 'content-length';
|
2019-02-27 14:27:51 +00:00
|
|
|
})[0] ?? 0;
|
2018-11-20 13:50:37 +00:00
|
|
|
}
|
|
|
|
|
|
2018-12-04 21:22:33 +00:00
|
|
|
public function onMessage(ConnectionInterface $from, $msg)
|
2018-11-20 13:50:37 +00:00
|
|
|
{
|
2019-02-27 14:24:00 +00:00
|
|
|
$this->requestBuffer .= $msg;
|
|
|
|
|
|
2019-03-25 22:00:54 +00:00
|
|
|
if (! $this->verifyContentLength()) {
|
2019-03-24 04:56:47 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->handleRequest($from);
|
2019-02-27 14:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
2019-03-25 22:00:54 +00:00
|
|
|
protected function verifyContentLength()
|
2019-02-27 14:24:00 +00:00
|
|
|
{
|
2019-03-25 22:00:54 +00:00
|
|
|
return strlen($this->requestBuffer) === $this->contentLength;
|
2019-03-24 04:56:47 +00:00
|
|
|
}
|
2019-02-27 14:24:00 +00:00
|
|
|
|
2019-03-24 04:56:47 +00:00
|
|
|
protected function handleRequest(ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
$serverRequest = (new ServerRequest(
|
|
|
|
|
$this->request->getMethod(),
|
|
|
|
|
$this->request->getUri(),
|
|
|
|
|
$this->request->getHeaders(),
|
|
|
|
|
$this->requestBuffer,
|
|
|
|
|
$this->request->getProtocolVersion()
|
|
|
|
|
))->withQueryParams(QueryParameters::create($this->request)->all());
|
2019-02-27 14:24:00 +00:00
|
|
|
|
2019-03-24 04:56:47 +00:00
|
|
|
$laravelRequest = Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest));
|
2019-02-27 14:24:00 +00:00
|
|
|
|
2019-03-24 04:56:47 +00:00
|
|
|
$this
|
|
|
|
|
->ensureValidAppId($laravelRequest->appId)
|
|
|
|
|
->ensureValidSignature($laravelRequest);
|
2019-02-27 14:24:00 +00:00
|
|
|
|
2019-03-29 19:33:46 +00:00
|
|
|
// Invoke the controller action
|
2019-03-24 04:56:47 +00:00
|
|
|
$response = $this($laravelRequest);
|
|
|
|
|
|
2019-03-29 19:33:46 +00:00
|
|
|
// Allow for async IO in the controller action
|
|
|
|
|
if ($response instanceof PromiseInterface) {
|
|
|
|
|
$response->then(function ($response) use ($connection) {
|
|
|
|
|
$this->sendAndClose($connection, $response);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->sendAndClose($connection, $response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function sendAndClose(ConnectionInterface $connection, $response)
|
|
|
|
|
{
|
2019-03-24 04:56:47 +00:00
|
|
|
$connection->send(JsonResponse::create($response));
|
|
|
|
|
$connection->close();
|
2018-11-21 23:10:47 +00:00
|
|
|
}
|
|
|
|
|
|
2018-12-04 21:22:33 +00:00
|
|
|
public function onClose(ConnectionInterface $connection)
|
2018-11-21 23:10:47 +00:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 21:22:33 +00:00
|
|
|
public function onError(ConnectionInterface $connection, Exception $exception)
|
2018-11-22 20:36:25 +00:00
|
|
|
{
|
2018-11-26 22:04:20 +00:00
|
|
|
if (! $exception instanceof HttpException) {
|
|
|
|
|
return;
|
2018-11-22 20:36:25 +00:00
|
|
|
}
|
2018-11-26 22:04:20 +00:00
|
|
|
|
|
|
|
|
$response = new Response($exception->getStatusCode(), [
|
2018-12-04 21:22:33 +00:00
|
|
|
'Content-Type' => 'application/json',
|
2018-11-26 22:04:20 +00:00
|
|
|
], json_encode([
|
2018-12-04 21:22:33 +00:00
|
|
|
'error' => $exception->getMessage(),
|
2018-11-26 22:04:20 +00:00
|
|
|
]));
|
|
|
|
|
|
2018-11-28 21:37:10 +00:00
|
|
|
$connection->send(\GuzzleHttp\Psr7\str($response));
|
2018-11-26 22:15:03 +00:00
|
|
|
|
2018-11-26 22:04:20 +00:00
|
|
|
$connection->close();
|
2018-11-22 20:36:25 +00:00
|
|
|
}
|
|
|
|
|
|
2018-11-26 08:03:04 +00:00
|
|
|
public function ensureValidAppId(string $appId)
|
2018-11-21 23:10:47 +00:00
|
|
|
{
|
2018-12-01 13:17:32 +00:00
|
|
|
if (! App::findById($appId)) {
|
2018-11-26 08:03:04 +00:00
|
|
|
throw new HttpException(401, "Unknown app id `{$appId}` provided.");
|
2018-11-22 20:36:25 +00:00
|
|
|
}
|
2018-11-24 00:53:20 +00:00
|
|
|
|
2018-11-26 19:50:02 +00:00
|
|
|
return $this;
|
2018-11-20 13:50:37 +00:00
|
|
|
}
|
|
|
|
|
|
2018-11-26 08:03:04 +00:00
|
|
|
protected function ensureValidSignature(Request $request)
|
2018-11-24 14:36:13 +00:00
|
|
|
{
|
2019-01-02 21:30:57 +00:00
|
|
|
/*
|
|
|
|
|
* The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value.
|
|
|
|
|
*
|
2019-03-24 04:56:47 +00:00
|
|
|
* The `appId`, `appKey` & `channelName` parameters are actually route parameters and are never supplied by the client.
|
2019-01-02 21:30:57 +00:00
|
|
|
*/
|
2019-02-27 14:27:21 +00:00
|
|
|
$params = Arr::except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']);
|
2018-11-24 14:36:13 +00:00
|
|
|
|
2018-12-03 09:08:25 +00:00
|
|
|
if ($request->getContent() !== '') {
|
2019-01-02 21:30:57 +00:00
|
|
|
$params['body_md5'] = md5($request->getContent());
|
2018-12-03 09:08:25 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-02 21:30:57 +00:00
|
|
|
ksort($params);
|
|
|
|
|
|
|
|
|
|
$signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params);
|
|
|
|
|
|
2018-12-03 09:08:25 +00:00
|
|
|
$authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret);
|
2018-11-24 14:36:13 +00:00
|
|
|
|
|
|
|
|
if ($authSignature !== $request->get('auth_signature')) {
|
|
|
|
|
throw new HttpException(401, 'Invalid auth signature provided.');
|
|
|
|
|
}
|
2018-11-26 08:03:04 +00:00
|
|
|
|
|
|
|
|
return $this;
|
2018-11-24 14:36:13 +00:00
|
|
|
}
|
|
|
|
|
|
2018-11-21 11:13:40 +00:00
|
|
|
abstract public function __invoke(Request $request);
|
2018-12-04 21:22:33 +00:00
|
|
|
}
|