Completed API architecture & HTTP handler
This commit is contained in:
parent
26d359c189
commit
6a239a5cf5
1
Makefile
1
Makefile
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
public function execute(): Response
|
||||
{
|
||||
$response = new Response();
|
||||
$response->ResultData = ['Foo' => 'Bar'];
|
||||
$response->ResultData = "Pong!";
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"required": true,
|
||||
"file": "Methods/v1/ParameterTestMethod.php"
|
||||
},
|
||||
{
|
||||
"required": true,
|
||||
"file": "Methods/v1/PingMethod.php"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -79,6 +79,10 @@
|
|||
"required": true,
|
||||
"file": "Exceptions/MissingComponentsException.php"
|
||||
},
|
||||
{
|
||||
"required": true,
|
||||
"file": "Exceptions/ApiMethodNotFoundException.php"
|
||||
},
|
||||
{
|
||||
"required": true,
|
||||
"file": "Exceptions/InternalServerException.php"
|
||||
|
|
Loading…
Reference in New Issue