Added basic router initialization and basic request handling (Prototype)

This commit is contained in:
Zi Xing 2022-01-02 14:51:27 -05:00
parent eefda29f88
commit 78ce281b0c
20 changed files with 1305 additions and 58 deletions

View File

@ -4,12 +4,15 @@ clean:
build:
mkdir build
ppm --no-intro --cerror --compile="src/KimchiAPI" --directory="build"
ppm --no-intro --cerror --compile="api_handler" --directory="build"
update:
ppm --generate-package="src/KimchiAPI"
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.test_api.ppm"
install_fast:
ppm --no-intro --no-prompt --fix-conflict --skip-dependencies --install="build/net.intellivoid.kimchi_api.ppm"
ppm --no-intro --no-prompt --fix-conflict --skip-dependencies --install="build/net.intellivoid.kimchi_api.ppm"
ppm --no-intro --no-prompt --fix-conflict --skip-dependencies --install="build/net.intellivoid.test_api.ppm"

3
api_frontend/.htaccess Normal file
View File

@ -0,0 +1,3 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

6
api_frontend/index.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require 'ppm';
require 'net.intellivoid.kimchi_api';
\KimchiAPI\KimchiAPI::exec('net.intellivoid.test_api==latest');

View File

@ -2,7 +2,7 @@
namespace Methods\v1;
class Ping extends \KimchiAPI\Abstracts\Command
class PingMethod extends \KimchiAPI\Abstracts\Method
{
public function execute()

View File

@ -1,32 +1,20 @@
{
"name": "Example API service",
"version": "1.0.0.0",
"author": "Zi Xing Narrakas",
"organization": "Intellivoid Technologies",
"configuration": {
"logging_enabled": true,
"root_path": "/test",
"framework_signature": true,
"api_signature": true,
"khm_enabled": true,
"firewall_deny": [],
"headers": {}
},
"versions": [
{
"version": "v1",
"enabled": true,
"namespace": "Methods\\v1",
"response_standard": "1.0",
"methods": [
{"method": ["GET", "POST"], "path": "ping", "class": "Ping", "params": []}
]
},
{
"version": "v2",
"enabled": true,
"namespace": "Methods\\v2",
"response_standard": "2.0",
"methods": [
{"method": ["GET", "POST"], "path": "ping", "class": "Ping", "params": []}
{"methods": ["GET", "POST"], "path": "ping", "class": "PingMethod"}
]
}
]

35
api_handler/package.json Normal file
View File

@ -0,0 +1,35 @@
{
"package": {
"package_name": "net.intellivoid.test_api",
"name": "Test API",
"version": "1.0.0.0",
"author": "Test",
"organization": "Test",
"description": "Test API",
"url": "https://example.com/",
"dependencies": [
{
"package": "net.intellivoid.kimchi_api",
"version": "latest",
"source": "default@github/intellivoid/KimchiAPI",
"required": true
}
],
"configuration": {
"autoload_method": "generated_spl",
"main": null,
"post_installation": [],
"pre_installation": []
}
},
"components": [
{
"required": true,
"file": "Methods/v1/PingMethod.php"
}
],
"files": [
"package.json",
"configuration.json"
]
}

View File

@ -5,7 +5,7 @@
use KimchiAPI\KimchiAPI;
use KimchiAPI\Objects\Request;
abstract class Command
abstract class Method
{
/**
* Auth level for user commands

View File

@ -0,0 +1,172 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace KimchiAPI\Classes;
use khm\Exceptions\DatabaseException;
use khm\khm;
use KimchiAPI\Exceptions\ApiException;
use KimchiAPI\Exceptions\ConnectionBlockedException;
use KimchiAPI\Exceptions\InternalServerException;
use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Objects\Configuration;
use KimchiAPI\Utilities\Client;
class API
{
/**
* @var Configuration
*/
private $Configuration;
/**
* @var string
*/
private $ResourcesPath;
/**
* @var string
*/
private $ConfigurationFilePath;
/**
* @var Router
*/
private $Router;
/**
* @param string $resources_path
* @throws IOException
* @throws InternalServerException
*/
public function __construct(string $resources_path)
{
$this->ResourcesPath = $resources_path;
$this->ConfigurationFilePath = $this->ResourcesPath . DIRECTORY_SEPARATOR . 'configuration.json';
if(file_exists($this->ConfigurationFilePath) == false)
throw new IOException('The API configuration file \'configuration.json\' does not exist');
$DecodedConfiguration = json_decode(file_get_contents($this->ConfigurationFilePath), true);
if($DecodedConfiguration == false)
throw new InternalServerException('Cannot read configuration file, ' . json_last_error_msg());
$this->Configuration = Configuration::fromArray($DecodedConfiguration);
$this->Router = new Router();
}
/**
* Initializes the Kimchi API server.
*
* @return void
* @throws ApiException
* @throws ConnectionBlockedException
* @throws DatabaseException
*/
public function initialize()
{
if(defined('KIMCHI_API_INITIALIZED'))
throw new ApiException('Cannot initialize ' . $this->Configuration->Name . ', another API is already initialized');
define('KIMCHI_API_RESOURCES_PATH', $this->ResourcesPath);
define('KIMCHI_API_CONFIGURATION_PATH', $this->ConfigurationFilePath);
define('KIMCHI_API_NAME', $this->Configuration->Name);
define('KIMCHI_API_ROOT_PATH', $this->Configuration->ServerConfiguration->RootPath);
define('KIMCHI_API_SIGNATURES', $this->Configuration->ServerConfiguration->ApiSignature);
define('KIMCHI_API_FRAMEWORK_SIGNATURE', $this->Configuration->ServerConfiguration->FrameworkSignature);
define('KIMCHI_API_LOGGING_ENABLED', $this->Configuration->ServerConfiguration->LoggingEnabled);
define('KIMCHI_API_HEADERS', $this->Configuration->ServerConfiguration->Headers);
$this->defineClientDefinitions();
$this->defineRoutes();
define('KIMCHI_API_INITIALIZED', 1);
}
/**
* @return Configuration
*/
public function getConfiguration(): Configuration
{
return $this->Configuration;
}
/**
* @return string
*/
public function getResourcesPath(): string
{
return $this->ResourcesPath;
}
/**
* @return string
*/
public function getConfigurationFilePath(): string
{
return $this->ConfigurationFilePath;
}
/**
* @return Router
*/
public function getRouter(): Router
{
return $this->Router;
}
/**
* @return void
* @throws \KimchiAPI\Exceptions\RouterException
*/
private function defineRoutes()
{
foreach($this->Configuration->Versions as $version)
{
foreach($version->Methods as $method)
{
$full_path = '/' . $version->Version . '/' . $method->Path;
$this->Router->map(implode('|', $method->Methods), $full_path, function() use ($version, $method, $full_path)
{
print($full_path);
exit();
}, $version->Version . '/' . $method->Class);
}
}
}
/**
* @throws DatabaseException
* @throws ConnectionBlockedException
*/
private function defineClientDefinitions()
{
define('KIMCHI_CLIENT_IP_ADDRESS', Client::getClientIP());
if($this->Configuration->ServerConfiguration->KhmEnabled)
{
$khm = new khm();
$IdentifiedClient = $khm->identify();
define('KIMCHI_KHM_ENABLED', true);
define('KIMCHI_KHM_FIREWALL', $this->Configuration->ServerConfiguration->FirewallDeny);
define('KIMCHI_KHM_FLAGS', $IdentifiedClient->Flags);
foreach($this->Configuration->ServerConfiguration->FirewallDeny as $item)
{
if(in_array($item, $IdentifiedClient->Flags))
{
throw new ConnectionBlockedException('Firewall block rule ' . $item);
}
}
}
else
{
define('KIMCHI_KHM_ENABLED', false);
define('KIMCHI_KHM_FIREWALL', null);
define('KIMCHI_KHM_FLAGS', null);
}
}
}

View File

@ -0,0 +1,166 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace KimchiAPI\Classes;
use KimchiAPI\Abstracts\RequestMethod;
class Request
{
/**
* Defined dynamical parameters
*
* @var array|null
*/
private static $definedDynamicParameters;
/**
* Returns the request method that was used
*
* @return string|RequestMethod
* @noinspection PhpUnused
*/
public static function getRequestMethod(): string
{
return strtoupper($_SERVER['REQUEST_METHOD']);
}
/**
* Gets a POST parameter if it's set
*
* @param string $value
* @return string|null
*/
public static function getPostParameter(string $value): ?string
{
if(isset($_POST[$value]))
return $_POST[$value];
return null;
}
/**
* Returns all POST parameters
*
* @return array
*/
public static function getPostParameters(): array
{
return $_POST;
}
/**
* Gets a GET parameter if it's set
*
* @param string $value
* @return string|null
*/
public static function getGetParameter(string $value): ?string
{
if(isset($_GET[$value]))
return $_GET[$value];
return null;
}
/**
* Returns all the GET parameters
*
* @return array
*/
public static function getGetParameters(): array
{
return $_GET;
}
/**
* Returns a POST/GET Parameter
*
* @param string $value
* @return string|null
* @noinspection PhpUnused
*/
public static function getParameter(string $value): ?string
{
if(self::getGetParameter($value) !== null)
return self::getGetParameter($value);
if(self::getPostParameter($value) !== null)
return self::getPostParameter($value);
/** @noinspection PhpConditionAlreadyCheckedInspection */
if(isset(self::getDefinedDynamicParameters()[$value]) !== null)
return self::getDefinedDynamicParameters()[$value];
return null;
}
/**
* Returns all the parameters combined, could overwrite existing parameters
*
* @return array
*/
public static function getParameters(): array
{
return array_merge(
self::getGetParameters(),
self::getPostParameters(),
self::getDefinedDynamicParameters()
);
}
/**
* Returns a defined Dynamical Parameter
*
* @param string $value
* @return string|null
* @noinspection PhpUnused
*/
public static function getDefinedDynamicParameter(string $value): ?string
{
if(isset(self::getDefinedDynamicParameters()[$value]))
return self::getDefinedDynamicParameters()[$value];
return null;
}
/**
* Define dynamical parameters
*
* @return array
* @noinspection PhpUnused
*/
public static function getDefinedDynamicParameters(): array
{
return (self::$definedDynamicParameters == null ? [] : self::$definedDynamicParameters);
}
/**
* Sets the defined dynamical parameters
*
* @param array|null $definedDynamicParameters
*/
public static function setDefinedDynamicParameters(?array $definedDynamicParameters): void
{
self::$definedDynamicParameters = $definedDynamicParameters;
}
/**
* Returns the post body given by the client
*
* @return string|null
*/
public static function getPostBody(): ?string
{
$results = @file_get_contents('php://input');
if($results == false)
$results = stream_get_contents(fopen('php://stdin', 'r'));
IF($results == false)
return null;
return null;
}
}

View File

@ -0,0 +1,338 @@
<?php
namespace KimchiAPI\Classes;
use KimchiAPI\Exceptions\RouterException;
use Traversable;
class Router
{
/**
* @var array Array of all routes (incl. named routes).
*/
protected $routes = array();
/**
* @var array Array of all named routes.
*/
protected $namedRoutes = array();
/**
* @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host)
*/
protected $basePath = '';
/**
* @var array Array of default match types (regex helpers)
*/
protected $matchTypes = array(
'i' => '[0-9]++',
'a' => '[0-9A-Za-z]++',
'h' => '[0-9A-Fa-f]++',
'*' => '.+?',
'**' => '.++',
'' => '[^/\.]++'
);
/**
* Create router in one call from config.
*
* @param array $routes
* @param string $basePath
* @param array $matchTypes
* @throws RouterException
*/
public function __construct(array $routes=[], string $basePath = '', array $matchTypes=[])
{
$this->addRoutes($routes);
$this->setBasePath($basePath);
$this->addMatchTypes($matchTypes);
}
/**
* Retrieves all routes.
* Useful if you want to process or display routes.
* @return array All routes.
*/
public function getRoutes(): array
{
return $this->routes;
}
/**
* Add multiple routes at once from array in the following format:
*
* $routes = array(
* array($method, $route, $target, $name)
* );
*
* @param array $routes
* @return void
* @throws RouterException
*/
public function addRoutes(array $routes)
{
/** @noinspection PhpConditionAlreadyCheckedInspection */
if(!is_array($routes) && !$routes instanceof Traversable)
{
throw new RouterException('Routes should be an array or an instance of Traversable');
}
foreach($routes as $route)
{
call_user_func_array(array($this, 'map'), $route);
}
}
/**
* Set the base path.
* Useful if you are running your application from a subdirectory.
* @param $basePath
*/
public function setBasePath($basePath)
{
$this->basePath = $basePath;
}
/**
* Add named match types. It uses array_merge so keys can be overwritten.
*
* @param array $matchTypes The key is the name and the value is the regex.
*/
public function addMatchTypes(array $matchTypes)
{
$this->matchTypes = array_merge($this->matchTypes, $matchTypes);
}
/**
* Map a route to a target
*
* @param string $method One of 5 HTTP Methods, or a pipe-separated list of multiple HTTP Methods (GET|POST|PATCH|PUT|DELETE)
* @param string $route The route regex, custom regex must start with an @. You can use multiple pre-set regex filters, like [i:id]
* @param mixed $target The target where this route should point to. Can be anything.
* @param string|null $name Optional name of this route. Supply if you want to reverse route this url in your application.
* @throws RouterException
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnnecessaryCurlyVarSyntaxInspection
* @noinspection RedundantSuppression
*/
public function map(string $method, string $route, $target, string $name=null)
{
$route = KIMCHI_API_ROOT_PATH . $route;
$this->routes[] = array($method, $route, $target, $name);
if($name)
{
if(isset($this->namedRoutes[$name]))
{
throw new RouterException("Can not redeclare route '{$name}'");
}
$this->namedRoutes[$name] = $route;
}
}
/**
* Reversed routing
*
* Generate the URL for a named route. Replace regexes with supplied parameters
*
* @param string $routeName The name of the route.
* @param array $params
* @return string The URL of the route with named parameters in place.
* @throws RouterException
* @noinspection PhpUnnecessaryCurlyVarSyntaxInspection
*/
public function generate(string $routeName, array $params=[]): string
{
// Check if named route exists
if(!isset($this->namedRoutes[$routeName]))
{
throw new RouterException("Route '{$routeName}' does not exist.");
}
// Replace named parameters
$route = $this->namedRoutes[$routeName];
// prepend base path to route url again
$url = $this->basePath . $route;
/** @noinspection RegExpRedundantEscape */
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER))
{
foreach($matches as $index => $match)
{
/** @noinspection PhpUnusedLocalVariableInspection */
list($block, $pre, $type, $param, $optional) = $match;
if ($pre)
{
$block = substr($block, 1);
}
if(isset($params[$param]))
{
// Part is found, replace for param value
$url = str_replace($block, $params[$param], $url);
}
elseif ($optional && $index !== 0)
{
// Only strip proceeding slash if it's not at the base
$url = str_replace($pre . $block, '', $url);
}
else
{
// Strip match block
$url = str_replace($block, '', $url);
}
}
}
return $url;
}
/**
* Match a given Request Url against stored routes
* @param string|null $requestUrl
* @param string|null $requestMethod
* @return array|boolean Array with route information on success, false on failure (no match).
* @noinspection PhpIssetCanBeReplacedWithCoalesceInspection
*/
public function match(?string $requestUrl=null, string $requestMethod = null)
{
$params = array();
/** @noinspection PhpUnusedLocalVariableInspection */
$match = false;
// set Request Url if it isn't passed as parameter
if($requestUrl === null)
{
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
}
// strip base path from request url
$requestUrl = substr($requestUrl, strlen($this->basePath));
// Strip query string (?a=b) from Request Url
/** @noinspection SpellCheckingInspection */
if (($strpos = strpos($requestUrl, '?')) !== false)
{
$requestUrl = substr($requestUrl, 0, $strpos);
}
// set Request Method if it isn't passed as a parameter
if($requestMethod === null)
{
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
}
foreach($this->routes as $handler)
{
list($methods, $route, $target, $name) = $handler;
$method_match = (stripos($methods, $requestMethod) !== false);
// Method did not match, continue to next route.
if (!$method_match) continue;
if ($route === '*')
{
// * wildcard (matches all)
$match = true;
}
elseif (isset($route[0]) && $route[0] === '@')
{
// @ regex delimiter
$pattern = '`' . substr($route, 1) . '`u';
$match = preg_match($pattern, $requestUrl, $params) === 1;
}
elseif (($position = strpos($route, '[')) === false)
{
// No params in url, do string comparison
$match = strcmp($requestUrl, $route) === 0;
}
else
{
// Compare longest non-param string with url
if (strncmp($requestUrl, $route, $position) !== 0)
{
continue;
}
$regex = $this->compileRoute($route);
$match = preg_match($regex, $requestUrl, $params) === 1;
}
if ($match)
{
if ($params)
{
foreach($params as $key => $value)
{
if(is_numeric($key)) unset($params[$key]);
}
}
Request::setDefinedDynamicParameters($params);
return array(
'target' => $target,
'params' => $params,
'name' => $name
);
}
}
return false;
}
/**
* Compile the regex for a given route (EXPENSIVE)
* @param $route
* @return string
*/
protected function compileRoute($route): string
{
/** @noinspection RegExpRedundantEscape */
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER))
{
$matchTypes = $this->matchTypes;
foreach($matches as $match)
{
list($block, $pre, $type, $param, $optional) = $match;
if (isset($matchTypes[$type]))
{
$type = $matchTypes[$type];
}
if ($pre === '.')
{
$pre = '\.';
}
$optional = $optional !== '' ? '?' : null;
//Older versions of PCRE require the 'P' in (?P<named>)
$pattern = '(?:'
. ($pre !== '' ? $pre : null)
. '('
. ($param !== '' ? "?P<$param>" : null)
. $type
. ')'
. $optional
. ')'
. $optional;
$route = str_replace($block, $pattern, $route);
}
}
return "`^(?J)$route$`u";
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class ApiException 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

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class ConnectionBlockedException 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

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class RouterException 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

@ -1,14 +1,25 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace KimchiAPI;
// Define server information for response headers
use KimchiAPI\Abstracts\Command;
use Exception;
use khm\Exceptions\DatabaseException;
use KimchiAPI\Abstracts\Method;
use KimchiAPI\Classes\API;
use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Exceptions\MissingComponentsException;
use KimchiAPI\Utilities\Converter;
use ppm\Exceptions\AutoloaderException;
use ppm\Exceptions\InvalidComponentException;
use ppm\Exceptions\InvalidPackageLockException;
use ppm\Exceptions\PackageNotFoundException;
use ppm\Exceptions\VersionNotFoundException;
use ppm\ppm;
use RuntimeException;
// Define server information for response headers
if(defined("KIMCHI_API_SERVER") == false)
{
if(file_exists(__DIR__ . DIRECTORY_SEPARATOR . "package.json") == false)
@ -70,7 +81,7 @@
/**
* Add a single custom commands path
*
* @param string $path Custom commands path to add
* @param string $path Custom commands' path to add
* @param bool $before If the path should be prepended or appended to the list
* @throws IOException
*/
@ -78,7 +89,7 @@
{
if (!is_dir($path))
{
throw new IOException('Commands path "' . $path . '" does not exist.');
throw new IOException('Method path "' . $path . '" does not exist.');
}
elseif (!in_array($path, $this->commands_paths, true))
{
@ -115,17 +126,17 @@
* @param string $command
* @param string $filepath
*
* @return Command|null
* @return Method|null
*/
public function getCommandObject(string $command, string $filepath = ''): ?Command
public function getCommandObject(string $command, string $filepath = ''): ?Method
{
if (isset($this->commands_objects[$command]))
{
return $this->commands_objects[$command];
}
$which = [Command::AUTH_SYSTEM];
$which[] = Command::AUTH_USER;
$which = [Method::AUTH_SYSTEM];
$which[] = Method::AUTH_USER;
foreach ($which as $auth)
{
@ -163,7 +174,7 @@
}
// Start with default namespace.
$command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands';
$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)))
@ -171,7 +182,7 @@
return null;
}
$command_class = $command_namespace . '\\' . Converter::ucFirstUnicode($command) . 'Command';
$command_class = $command_namespace . '\\' . Converter::ucFirstUnicode($command) . 'Method';
if (class_exists($command_class))
{
@ -180,4 +191,65 @@
return null;
}
/**
* @param string $package
* @param bool $import_dependencies
* @param bool $throw_error
* @throws AutoloaderException
* @throws Exceptions\ApiException
* @throws Exceptions\ConnectionBlockedException
* @throws Exceptions\InternalServerException
* @throws IOException
* @throws InvalidComponentException
* @throws InvalidPackageLockException
* @throws PackageNotFoundException
* @throws VersionNotFoundException
* @throws DatabaseException
*/
public static function exec(string $package, bool $import_dependencies=true, bool $throw_error=true)
{
$decoded = explode('==', $package);
if($decoded[1] == 'latest')
$decoded[1] = ppm::getPackageLock()->getPackage($decoded[0])->getLatestVersion();
$path = ppm::getPackageLock()->getPackage($decoded[0])->getPackagePath($decoded[1]); // Find the package path
ppm::import($decoded[0], $decoded[1], $import_dependencies, $throw_error); // Import dependencies
$API = new API($path);
$API->initialize();
self::handleRequest($API);
}
/**
* Handles the request to the API
*
* @param API $API
* @param string|null $requestUrl
* @param string|null $requestMethod
* @return void
*/
public static function handleRequest(API $API, ?string $requestUrl=null, string $requestMethod = null)
{
$match = $API->getRouter()->match($requestUrl, $requestMethod);
// call closure or throw 404 status
if(is_array($match) && is_callable($match['target']))
{
try
{
call_user_func_array($match['target'], array_values($match['params']));
}
catch(Exception $e)
{
var_dump($e);
exit();
}
}
else
{
var_dump($API->getRouter()->getRoutes());
print("404");
exit();
}
}
}

View File

@ -1,14 +1,75 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace KimchiAPI\Objects;
use KimchiAPI\Objects\Configuration\ServerConfiguration;
use KimchiAPI\Objects\Configuration\VersionConfiguration;
class Configuration
{
/**
* The name of the API service
*
* @var string
*/
public $Name;
public $Version;
/**
* The configuration for the KimchiAPI server
*
* @var ServerConfiguration
*/
public $ServerConfiguration;
public $Author;
/**
* An array of version configurations
*
* @var VersionConfiguration[]
*/
public $Versions;
public $Organization;
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
$versions = [];
foreach($this->Versions as $version)
$versions[] = $version->toArray();
return [
'name' => $this->Name,
'configuration' => $this->ServerConfiguration->toArray(),
'versions' => $versions
];
}
/**
* Constructs object from an array configuration
*
* @param array $data
* @return Configuration
*/
public static function fromArray(array $data): Configuration
{
$configuration_object = new Configuration();
if(isset($data['name']))
$configuration_object->Name = $data['name'];
if(isset($data['configuration']))
$configuration_object->ServerConfiguration = ServerConfiguration::fromArray($data['configuration']);
if(isset($data['versions']))
{
foreach($data['versions'] as $version)
$configuration_object->Versions[] = VersionConfiguration::fromArray($version);
}
return $configuration_object;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace KimchiAPI\Objects\Configuration;
use KimchiAPI\Abstracts\RequestMethod;
class MethodConfiguration
{
/**
* An array of accepted request methods that this method accepts
*
* @var RequestMethod[]
*/
public $Methods;
/**
* The HTTP route path for this request method
*
* @var string
*/
public $Path;
/**
* The class name to initialize
*
* @var string
*/
public $Class;
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [
'methods' => $this->Methods,
'path' => $this->Path,
'class' => $this->Class
];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return MethodConfiguration
*/
public static function fromArray(array $data): MethodConfiguration
{
$MethodConfigurationObject = new MethodConfiguration();
if(isset($data['methods']))
$MethodConfigurationObject->Methods = $data['methods'];
if(isset($data['path']))
$MethodConfigurationObject->Path = $data['path'];
if(isset($data['class']))
$MethodConfigurationObject->Class = $data['class'];
return $MethodConfigurationObject;
}
}

View File

@ -1,5 +1,7 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace KimchiAPI\Objects\Configuration;
class ServerConfiguration
@ -19,13 +21,90 @@
public $RootPath;
/**
*
* Indicates if the framework signature headers are to be returned to the HTTP response
*
* @var bool
*/
public $FrameworkSignature;
/**
* Indicates if the API Signature headers are to be returned to the HTTP response
*
* @var bool
*/
public $ApiSignature;
/**
* An array of hard-coded headers to be returned to the HTTP response
*
* @var array
*/
public $Headers;
/**
* Indicates if KHM is enabled for this API
*
* @var bool
*/
public $KhmEnabled;
/**
* An array of flags to deny if KHM detects one
*
* @var array
*/
public $FirewallDeny;
/**
* Returns an array representation of the object
*
* @return array
* @noinspection PhpCastIsUnnecessaryInspection
*/
public function toArray(): array
{
return [
'logging_enabled' => (bool)$this->LoggingEnabled,
'root_path' => $this->RootPath,
'framework_signature' => (bool)$this->FrameworkSignature,
'api_signature' => (bool)$this->ApiSignature,
'headers' => $this->Headers,
'khm_enabled' => (bool)$this->KhmEnabled,
'firewall_deny' => $this->FirewallDeny
];
}
/**
* Constructs object from an array representation of the object
*
* @param array $data
* @return ServerConfiguration
*/
public static function fromArray(array $data): ServerConfiguration
{
$ServerConfigurationObject = new ServerConfiguration();
if(isset($data['logging_enabled']))
$ServerConfigurationObject->LoggingEnabled = $data['logging_enabled'];
if(isset($data['root_path']))
$ServerConfigurationObject->RootPath = $data['root_path'];
if(isset($data['framework_signature']))
$ServerConfigurationObject->FrameworkSignature = $data['framework_signature'];
if(isset($data['api_signature']))
$ServerConfigurationObject->ApiSignature = $data['api_signature'];
if(isset($data['headers']))
$ServerConfigurationObject->Headers = $data['headers'];
if(isset($data['khm_enabled']))
$ServerConfigurationObject->KhmEnabled = (bool)$data['khm_enabled'];
if(isset($data['firewall_deny']))
$ServerConfigurationObject->FirewallDeny = $data['firewall_deny'];
return $ServerConfigurationObject;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace KimchiAPI\Objects\Configuration;
class VersionConfiguration
{
/**
* The version name of the configuration
*
* @var string
*/
public $Version;
/**
* Indicates if the version is enabled or not
*
* @var bool
*/
public $Enabled;
/**
* @var MethodConfiguration[]
*/
public $Methods;
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
$methods_array = [];
foreach($this->Methods as $method)
$methods_array[] = $method->toArray();
return [
'version' => $this->Version,
'enabled' => (bool)$this->Enabled,
'methods' => $methods_array
];
}
/**
* Constructs object from an array representation of the object
*
* @param array $data
* @return VersionConfiguration
*/
public static function fromArray(array $data): VersionConfiguration
{
$version_configuration = new VersionConfiguration();
if(isset($data['version']))
$version_configuration->Version = $data['version'];
if(isset($data['enabled']))
$version_configuration->Enabled = (bool)$data['enabled'];
if(isset($data['methods']))
{
foreach($data['methods'] as $method)
$version_configuration->Methods[] = MethodConfiguration::fromArray($method);
}
return $version_configuration;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace KimchiAPI\Utilities;
class Client
{
/**
* Returns the IP address of the client
*
* @return string
*/
public static function getClientIP(): string
{
if(isset($_SERVER['HTTP_CF_CONNECTING_IP']))
{
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
if(isset($_SERVER['HTTP_CLIENT_IP']))
{
return $_SERVER['HTTP_CLIENT_IP'];
}
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(isset($_SERVER['HTTP_X_FORWARDED']))
{
return $_SERVER['HTTP_X_FORWARDED'];
}
if(isset($_SERVER['HTTP_FORWARDED_FOR']))
{
return $_SERVER['HTTP_FORWARDED_FOR'];
}
if(isset($_SERVER['HTTP_FORWARDED']))
{
return $_SERVER['HTTP_FORWARDED'];
}
if(isset($_SERVER['REMOTE_ADDR']))
{
return $_SERVER['REMOTE_ADDR'];
}
if(getenv('HTTP_CLIENT_IP') !== False)
{
return getenv('HTTP_CLIENT_IP');
}
if(getenv('HTTP_X_FORWARDED_FOR'))
{
return getenv('HTTP_X_FORWARDED_FOR');
}
if(getenv('HTTP_X_FORWARDED'))
{
return getenv('HTTP_X_FORWARDED');
}
if(getenv('HTTP_FORWARDED_FOR'))
{
return getenv('HTTP_FORWARDED_FOR');
}
if(getenv('HTTP_FORWARDED'))
{
return getenv('HTTP_FORWARDED');
}
if(getenv('REMOTE_ADDR'))
{
return getenv('REMOTE_ADDR');
}
return '127.0.0.1';
}
}

View File

@ -7,7 +7,14 @@
"organization": "Intellivoid Technologies",
"description": "The API server that goes good with anything, even toothpaste!",
"url": "https://github.com/intellivoid/KimchiAPI",
"dependencies": [],
"dependencies": [
{
"package": "net.intellivoid.khm",
"version": "latest",
"source": "default@github/intellivoid/khm",
"required": true
}
],
"configuration": {
"autoload_method": "generated_spl",
"main": null,
@ -16,61 +23,101 @@
}
},
"components": [
{
"required": true,
"file": "Abstracts/Command.php"
},
{
"required": true,
"file": "Abstracts/RequestMethod.php"
},
{
"required": true,
"file": "Exceptions/InternalServerException.php"
"file": "Abstracts/Method.php"
},
{
"required": true,
"file": "Exceptions/IOException.php"
"file": "Classes/Router.php"
},
{
"required": true,
"file": "Exceptions/MethodAlreadyRegisteredException.php"
"file": "Classes/Request.php"
},
{
"required": true,
"file": "Exceptions/MethodNotFoundException.php"
},
{
"required": true,
"file": "Exceptions/MissingComponentsException.php"
},
{
"required": true,
"file": "Interfaces/MethodInterface.php"
"file": "Classes/API.php"
},
{
"required": true,
"file": "KimchiAPI.php"
},
{
"required": true,
"file": "Exceptions/BadEnvironmentException.php"
},
{
"required": true,
"file": "Exceptions/MissingComponentsException.php"
},
{
"required": true,
"file": "Exceptions/InternalServerException.php"
},
{
"required": true,
"file": "Exceptions/ConnectionBlockedException.php"
},
{
"required": true,
"file": "Exceptions/MethodAlreadyRegisteredException.php"
},
{
"required": true,
"file": "Exceptions/IOException.php"
},
{
"required": true,
"file": "Exceptions/RouterException.php"
},
{
"required": true,
"file": "Exceptions/ApiException.php"
},
{
"required": true,
"file": "Exceptions/MethodNotFoundException.php"
},
{
"required": true,
"file": "Utilities/Converter.php"
},
{
"required": true,
"file": "Utilities/Client.php"
},
{
"required": true,
"file": "Interfaces/MethodInterface.php"
},
{
"required": true,
"file": "Objects/Configuration/MethodConfiguration.php"
},
{
"required": true,
"file": "Objects/Configuration/VersionConfiguration.php"
},
{
"required": true,
"file": "Objects/Configuration/ServerConfiguration.php"
},
{
"required": true,
"file": "Objects/Configuration.php"
},
{
"required": true,
"file": "Objects/Request.php"
},
{
"required": true,
"file": "Objects/Response.php"
},
{
"required": true,
"file": "Utilities/Converter.php"
"file": "Objects/Request.php"
},
{
"required": true,
"file": "Objects/Configuration.php"
}
],
"files": [