Completed API architecture & HTTP handler

This commit is contained in:
Zi Xing 2022-01-05 13:47:49 -05:00
parent 26d359c189
commit 6a239a5cf5
10 changed files with 158 additions and 170 deletions

View File

@ -8,6 +8,7 @@ build:
update:
ppm --generate-package="src/KimchiAPI"
ppm --generate-package="api_handler"
install:
ppm --no-intro --no-prompt --fix-conflict --install="build/net.intellivoid.kimchi_api.ppm"

View File

@ -0,0 +1,16 @@
<?php
namespace Methods\v1;
use KimchiAPI\Classes\Request;
use KimchiAPI\Objects\Response;
class ParameterTestMethod extends \KimchiAPI\Abstracts\Method
{
public function execute(): Response
{
$response = new Response();
$response->ResultData = Request::getParameters();
return $response;
}
}

View File

@ -9,7 +9,7 @@
public function execute(): Response
{
$response = new Response();
$response->ResultData = ['Foo' => 'Bar'];
$response->ResultData = "Pong!";
return $response;
}
}

View File

@ -15,7 +15,8 @@
"version": "v1",
"enabled": true,
"methods": [
{"methods": ["GET", "POST"], "path": "ping", "class": "\\Methods\\v1\\PingMethod"}
{"methods": ["GET", "POST"], "path": "ping", "class": "\\Methods\\v1\\PingMethod"},
{"methods": ["GET", "POST"], "path": "parameter_test", "class": "\\Methods\\v1\\ParameterTestMethod"}
]
}
]

View File

@ -23,6 +23,10 @@
}
},
"components": [
{
"required": true,
"file": "Methods/v1/ParameterTestMethod.php"
},
{
"required": true,
"file": "Methods/v1/PingMethod.php"

View File

@ -8,6 +8,7 @@
use khm\khm;
use KimchiAPI\Abstracts\Method;
use KimchiAPI\Exceptions\ApiException;
use KimchiAPI\Exceptions\ApiMethodNotFoundException;
use KimchiAPI\Exceptions\ConnectionBlockedException;
use KimchiAPI\Exceptions\InternalServerException;
use KimchiAPI\Exceptions\IOException;
@ -136,7 +137,7 @@
$this->Router->map(implode('|', $method->Methods), $full_path, function() use ($version, $method, $full_path)
{
if(class_exists($method->Class) == false)
throw new ApiException('API Method not found');
throw new ApiMethodNotFoundException('API Method not found');
/** @var Method $method_class */
$method_class = new $method->Class;

View File

@ -106,7 +106,8 @@
return array_merge(
self::getGetParameters(),
self::getPostParameters(),
self::getDefinedDynamicParameters()
self::getDefinedDynamicParameters(),
self::getPostBody()
);
}
@ -149,18 +150,21 @@
/**
* Returns the post body given by the client
*
* @return string|null
* @return array
*/
public static function getPostBody(): ?string
public static function getPostBody(): array
{
$results = @file_get_contents('php://input');
if($results == false)
$results = stream_get_contents(fopen('php://stdin', 'r'));
IF($results == false)
return null;
if($results == false)
return [];
return null;
$decoded = json_decode($results, true);
if($decoded == false)
return [];
return $decoded;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class ApiMethodNotFoundException extends Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -10,6 +10,7 @@
use KimchiAPI\Abstracts\ResponseStandard;
use KimchiAPI\Abstracts\ResponseType;
use KimchiAPI\Classes\API;
use KimchiAPI\Exceptions\ApiMethodNotFoundException;
use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Exceptions\MissingComponentsException;
use KimchiAPI\Exceptions\UnsupportedResponseStandardException;
@ -45,174 +46,23 @@
class KimchiAPI
{
/**
* @var array
*/
private $commands_paths;
/**
* @var array
*/
private $command_classes;
/**
* Server constructor.
*/
public function __construct()
{
$this->commands_paths = [];
$this->command_classes = [];
}
/**
* Return the list of commands paths
*
* @return array
*/
public function getCommandsPaths(): array
{
return $this->commands_paths;
}
/**
* Return the list of command classes
*
* @return array
*/
public function getCommandClasses(): array
{
return $this->command_classes;
}
/**
* Add a single custom commands path
*
* @param string $path Custom commands' path to add
* @param bool $before If the path should be prepended or appended to the list
* @throws IOException
*/
public function addCommandsPath(string $path, bool $before=true)
{
if (!is_dir($path))
{
throw new IOException('Method path "' . $path . '" does not exist.');
}
elseif (!in_array($path, $this->commands_paths, true))
{
if ($before)
{
array_unshift($this->commands_paths, $path);
}
else
{
$this->commands_paths[] = $path;
}
}
}
/**
* Add multiple custom commands paths
*
* @param array $paths Custom commands paths to add
* @param bool $before If the paths should be prepended or appended to the list
* @throws IOException
*/
public function addCommandsPaths(array $paths, bool $before=true)
{
foreach ($paths as $path)
{
$this->addCommandsPath($path, $before);
}
}
/**
* Get an object instance of the passed command
*
* @param string $command
* @param string $filepath
*
* @return Method|null
*/
public function getCommandObject(string $command, string $filepath = ''): ?Method
{
if (isset($this->commands_objects[$command]))
{
return $this->commands_objects[$command];
}
$which = [Method::AUTH_SYSTEM];
$which[] = Method::AUTH_USER;
foreach ($which as $auth)
{
$command_class = $this->getCommandClassName($auth, $command, $filepath);
if ($command_class)
{
return new $command_class();
}
}
return null;
}
/**
* Get classname of predefined commands
*
* @see command_classes
*
* @param string $auth Auth of command
* @param string $command Command name
* @param string $filepath Path to the command file
*
* @return string|null
*/
public function getCommandClassName(string $auth, string $command, string $filepath = ''): ?string
{
$command = mb_strtolower($command);
$auth = Converter::ucFirstUnicode($auth);
// First, check for directly assigned command class.
if ($command_class = $this->command_classes[$auth][$command] ?? null)
{
return $command_class;
}
// Start with default namespace.
$command_namespace = __NAMESPACE__ . '\\Methods\\' . $auth . 'Methods';
// Check if we can get the namespace from the file (if passed).
if ($filepath && !($command_namespace = Converter::getFileNamespace($filepath)))
{
return null;
}
$command_class = $command_namespace . '\\' . Converter::ucFirstUnicode($command) . 'Method';
if (class_exists($command_class))
{
return $command_class;
}
return null;
}
/**
* @param string $package
* @param bool $import_dependencies
* @param bool $throw_error
* @throws AutoloaderException
* @throws DatabaseException
* @throws Exceptions\ApiException
* @throws Exceptions\ConnectionBlockedException
* @throws Exceptions\InternalServerException
* @throws Exceptions\RouterException
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws IOException
* @throws InvalidComponentException
* @throws InvalidPackageLockException
* @throws PackageNotFoundException
* @throws UnsupportedResponseStandardException
* @throws VersionNotFoundException
* @throws DatabaseException
*/
public static function exec(string $package, bool $import_dependencies=true, bool $throw_error=true)
{
@ -234,6 +84,8 @@
* @param string|null $requestUrl
* @param string|null $requestMethod
* @return void
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws UnsupportedResponseStandardException
*/
public static function handleRequest(API $API, ?string $requestUrl=null, string $requestMethod = null)
{
@ -246,18 +98,92 @@
{
call_user_func_array($match['target'], array_values($match['params']));
}
catch(ApiMethodNotFoundException $e)
{
unset($e);
self::handle404();
}
catch(Exception $e)
{
exit();
self::handleException($e);
}
}
else
{
print("404");
exit();
}
self::handle404();
}
/**
* Handles an exception response
*
* @param Exception $exception
* @param string $response_standard
* @param string $response_type
* @return void
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws UnsupportedResponseStandardException
*/
public static function handleException(Exception $exception, string $response_standard = ResponseStandard::KimchiAPI, string $response_type = ResponseType::Json)
{
$response = new Response();
$response->ResponseCode = 500;
$response->Success = false;
$response->ErrorCode = $exception->getCode();
$response->ErrorMessage = 'There was an internal server error while trying to process your request';
$response->Exception = $exception;
$response->ResponseStandard = ResponseStandard::KimchiAPI;
$response->ResponseType = ResponseType::Json;
self::handleResponse($response);
}
/**
* Returns a 404 response
*
* @param string $response_standard
* @param string $response_type
* @return void
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws UnsupportedResponseStandardException
*/
public static function handle404(string $response_standard = ResponseStandard::KimchiAPI, string $response_type = ResponseType::Json)
{
$response = new Response();
$response->ResponseCode = 404;
$response->Success = false;
$response->ErrorCode = 404;
$response->ErrorMessage = 'The requested resource/action is invalid or not found';
$response->ResponseStandard = $response_standard;
$response->ResponseType = $response_type;
self::handleResponse($response);
}
/**
* Returns the headers used for framework
*
* @return array
*/
public static function getFrameworkHeaders(): array
{
return [
'X-Organization' => KIMCHI_API_SERVER_ORGANIZATION,
'X-Powered-By' => 'KimchiAPI/' . KIMCHI_API_SERVER_VERSION
];
}
/**
* Returns the headers for the API
*
* @return array
*/
public static function getApiHeaders(): array
{
return [
'X-API' => KIMCHI_API_NAME
];
}
/**
* Handles the response handler and returns the response data to the client
*
@ -296,6 +222,16 @@
$return_results = Converter::serializeResponse($response_data, $response->ResponseType);
http_response_code($response->ResponseCode);
if(defined('KIMCHI_API_FRAMEWORK_SIGNATURE') && KIMCHI_API_FRAMEWORK_SIGNATURE)
{
foreach(self::getFrameworkHeaders() as $header => $value)
header("$header: $value");
}
if(defined('KIMCHI_API_SIGNATURES') && KIMCHI_API_SIGNATURES)
{
foreach(self::getApiHeaders() as $header => $value)
header("$header: $value");
}
foreach($response->Headers as $header => $value)
header("$header: $value");
header('Content-Type: ' . $response->ResponseType);

View File

@ -79,6 +79,10 @@
"required": true,
"file": "Exceptions/MissingComponentsException.php"
},
{
"required": true,
"file": "Exceptions/ApiMethodNotFoundException.php"
},
{
"required": true,
"file": "Exceptions/InternalServerException.php"