aiwass/src/RpcRouteHandler.php

109 lines
4.1 KiB
PHP

<?php
// RpcRouteHandler.php
// Created: 2024-08-13
// Updated: 2024-08-13
namespace Aiwass;
use Exception;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionUnionType;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler};
/**
* Provides a router implementation for an Aiwass RPC server.
*/
class RpcRouteHandler extends RouteHandler {
/**
* @param RpcServer $server An RPC server instance.
*/
public function __construct(
private RpcServer $server
) {}
/**
* Handles an action request.
*
* @param HttpResponseBuilder $response Response that it being built.
* @param HttpRequest $request Request that is being handled.
* @param string $action Name of the action specified in the URL.
* @return int|array{error: string, message?: string}|mixed Response to the request.
*/
#[HttpGet('/_aiwass/([A-Za-z0-9\-_\.:]+)')]
#[HttpPost('/_aiwass/([A-Za-z0-9\-_\.:]+)')]
public function handleAction(HttpResponseBuilder $response, HttpRequest $request, string $action) {
if($action === '')
return 404;
if($request->getMethod() === 'POST') {
$content = $request->getContent();
if(!($content instanceof FormContent))
return 400;
$expectProcedure = true;
$paramString = $content->getParamString();
$params = $content->getParams();
} elseif($request->getMethod() === 'GET') {
$expectProcedure = false;
$paramString = $request->getParamString();
$params = $request->getParams();
} else {
$response->setHeader('Allow', 'GET, POST');
return 405;
}
$response->setContentType('application/vnd.msgpack');
$userToken = (string)$request->getHeaderLine('X-Aiwass-Verify');
if($userToken === '') {
$response->setStatusCode(403);
return AiwassMsgPack::encode(['error' => 'aiwass:verify', 'message' => 'X-Aiwass-Verify header is missing.']);
}
$actInfo = $this->server->getActionInfo($action);
if($actInfo === null) {
$response->setStatusCode(404);
return AiwassMsgPack::encode(['error' => 'aiwass:unknown', 'message' => 'No action with that name exists.']);
}
if($actInfo->isProcedure() !== $expectProcedure) {
$response->setStatusCode(405);
$response->setHeader('Allow', $actInfo->isProcedure() ? 'POST' : 'GET');
return AiwassMsgPack::encode(['error' => 'aiwass:method', 'message' => 'This action cannot be called using this request method.']);
}
if(!$this->server->getVerificationProvider()->verify($userToken, $expectProcedure, $action, $paramString))
return AiwassMsgPack::encode(['error' => 'aiwass:verify', 'message' => 'Request token verification failed.']);
$handlerInfo = new ReflectionFunction($actInfo->getHandler());
if($handlerInfo->isVariadic())
return AiwassMsgPack::encode(['error' => 'aiwass:variadic', 'message' => 'Handler was declared as a variadic method and is thus impossible to be called.']);
$handlerArgs = [];
$handlerParams = $handlerInfo->getParameters();
foreach($handlerParams as $paramInfo) {
$paramName = $paramInfo->getName();
if(!array_key_exists($paramName, $params)) {
if($paramInfo->isOptional())
continue;
if(!$paramInfo->allowsNull())
return AiwassMsgPack::encode(['error' => 'aiwass:param:required', 'message' => 'A required parameter is missing.', 'param' => $paramName]);
}
$handlerArgs[$paramName] = $params[$paramName] ?? null;
}
if(count($handlerArgs) !== count($params)) {
$response->setStatusCode(400);
return AiwassMsgPack::encode(['error' => 'aiwass:params', 'message' => 'Unsupported arguments were specified.']);
}
return AiwassMsgPack::encode($handlerInfo->invokeArgs($handlerArgs));
}
}