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: update:
ppm --generate-package="src/KimchiAPI" ppm --generate-package="src/KimchiAPI"
ppm --generate-package="api_handler"
install: install:
ppm --no-intro --no-prompt --fix-conflict --install="build/net.intellivoid.kimchi_api.ppm" 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 public function execute(): Response
{ {
$response = new Response(); $response = new Response();
$response->ResultData = ['Foo' => 'Bar']; $response->ResultData = "Pong!";
return $response; return $response;
} }
} }

View File

@ -15,7 +15,8 @@
"version": "v1", "version": "v1",
"enabled": true, "enabled": true,
"methods": [ "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": [ "components": [
{
"required": true,
"file": "Methods/v1/ParameterTestMethod.php"
},
{ {
"required": true, "required": true,
"file": "Methods/v1/PingMethod.php" "file": "Methods/v1/PingMethod.php"

View File

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

View File

@ -106,7 +106,8 @@
return array_merge( return array_merge(
self::getGetParameters(), self::getGetParameters(),
self::getPostParameters(), self::getPostParameters(),
self::getDefinedDynamicParameters() self::getDefinedDynamicParameters(),
self::getPostBody()
); );
} }
@ -149,18 +150,21 @@
/** /**
* Returns the post body given by the client * 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'); $results = @file_get_contents('php://input');
if($results == false) if($results == false)
$results = stream_get_contents(fopen('php://stdin', 'r')); $results = stream_get_contents(fopen('php://stdin', 'r'));
IF($results == false) if($results == false)
return null; 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\ResponseStandard;
use KimchiAPI\Abstracts\ResponseType; use KimchiAPI\Abstracts\ResponseType;
use KimchiAPI\Classes\API; use KimchiAPI\Classes\API;
use KimchiAPI\Exceptions\ApiMethodNotFoundException;
use KimchiAPI\Exceptions\IOException; use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Exceptions\MissingComponentsException; use KimchiAPI\Exceptions\MissingComponentsException;
use KimchiAPI\Exceptions\UnsupportedResponseStandardException; use KimchiAPI\Exceptions\UnsupportedResponseStandardException;
@ -45,174 +46,23 @@
class KimchiAPI 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 string $package
* @param bool $import_dependencies * @param bool $import_dependencies
* @param bool $throw_error * @param bool $throw_error
* @throws AutoloaderException * @throws AutoloaderException
* @throws DatabaseException
* @throws Exceptions\ApiException * @throws Exceptions\ApiException
* @throws Exceptions\ConnectionBlockedException * @throws Exceptions\ConnectionBlockedException
* @throws Exceptions\InternalServerException * @throws Exceptions\InternalServerException
* @throws Exceptions\RouterException
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws IOException * @throws IOException
* @throws InvalidComponentException * @throws InvalidComponentException
* @throws InvalidPackageLockException * @throws InvalidPackageLockException
* @throws PackageNotFoundException * @throws PackageNotFoundException
* @throws UnsupportedResponseStandardException
* @throws VersionNotFoundException * @throws VersionNotFoundException
* @throws DatabaseException
*/ */
public static function exec(string $package, bool $import_dependencies=true, bool $throw_error=true) 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 $requestUrl
* @param string|null $requestMethod * @param string|null $requestMethod
* @return void * @return void
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws UnsupportedResponseStandardException
*/ */
public static function handleRequest(API $API, ?string $requestUrl=null, string $requestMethod = null) 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'])); call_user_func_array($match['target'], array_values($match['params']));
} }
catch(ApiMethodNotFoundException $e)
{
unset($e);
self::handle404();
}
catch(Exception $e) catch(Exception $e)
{ {
exit(); self::handleException($e);
} }
} }
else
{ self::handle404();
print("404");
exit();
}
} }
/**
* 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 * Handles the response handler and returns the response data to the client
* *
@ -296,6 +222,16 @@
$return_results = Converter::serializeResponse($response_data, $response->ResponseType); $return_results = Converter::serializeResponse($response_data, $response->ResponseType);
http_response_code($response->ResponseCode); 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) foreach($response->Headers as $header => $value)
header("$header: $value"); header("$header: $value");
header('Content-Type: ' . $response->ResponseType); header('Content-Type: ' . $response->ResponseType);

View File

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