5 Commits

Author SHA1 Message Date
as2252258 28973df8b6 eee 2026-06-12 23:57:20 +08:00
as2252258 4cfd04c988 eee 2026-04-17 16:30:52 +08:00
as2252258 16b8df159a eee 2026-04-17 13:56:30 +08:00
as2252258 83f6dc721a eee 2026-04-17 11:54:24 +08:00
as2252258 a2138bdd3b eee 2026-02-26 14:39:05 +08:00
13 changed files with 1869 additions and 1443 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
],
"license": "MIT",
"require": {
"php": ">=8.4",
"php": ">=8.5",
"composer-runtime-api": "^2.0",
"psr/http-server-middleware": "^1.0",
"psr/http-message": "^1.0"
+12 -1
View File
@@ -20,10 +20,21 @@ interface AuthorizationInterface
public function getUniqueId(): string|int;
/**
* @return string
*/
public function getNickname(): string;
/**
* @return string
*/
public function getAvatar(): string;
/**
* @param string $key
* @param int $timeout
* @param int $timeout
* @return bool
*/
public function lock(string $key, int $timeout): bool;
File diff suppressed because it is too large Load Diff
+25 -50
View File
@@ -23,7 +23,7 @@ class Message extends Component implements MessageInterface
private array $headers = [];
/**
/**
* @var StreamInterface
*/
public StreamInterface $stream;
@@ -31,7 +31,13 @@ class Message extends Component implements MessageInterface
/**
* @var array
*/
private array $cookieParams = [];
public array $cookieParams = [];
/**
* @var array
*/
public array $data = [];
/**
@@ -45,24 +51,24 @@ class Message extends Component implements MessageInterface
}
/**
* Retrieve cookies.
*
* Retrieves cookies sent by the client to the server.
*
* The data MUST be compatible with the structure of the $_COOKIE
* superglobal.
*
* @return array
*/
public function getCookieParams(): array
{
// TODO: Implement getCookieParams() method.
return $this->cookieParams;
}
/**
* @return array
*/
public function getHeaders(): array
{
return $this->headers;
}
/**
* @return array
*/
public function getCookieParams(): array
{
return $this->cookieParams;
}
/**
/**
* Return an instance with the specified cookies.
*
* The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
@@ -120,38 +126,7 @@ class Message extends Component implements MessageInterface
return $this;
}
/**
* Retrieves all message header values.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* // Emit headers iteratively:
* foreach ($message->getHeaders() as $name => $values) {
* foreach ($values as $value) {
* header(sprintf('%s: %s', $name, $value), false);
* }
* }
*
* While header names are not case-sensitive, getHeaders() will preserve the
* exact case in which headers were originally specified.
*
* @return string[][] Returns an associative array of the message's headers. Each
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
public function getHeaders(): array
{
// TODO: Implement getHeaders() method.
return $this->headers;
}
/**
/**
* Checks if a header exists by the given case-insensitive name.
*
* @param string $name Case-insensitive header field name.
+17
View File
@@ -25,4 +25,21 @@ class DataGrip
}
public function reset(?string $type = null): void
{
if ($type === null) {
foreach ($this->servers as $server) {
if ($server instanceof RouterCollector) {
$server->clear();
}
}
return;
}
if (isset($this->servers[$type])) {
$this->servers[$type]->clear();
}
}
}
+80
View File
@@ -5,6 +5,7 @@ namespace Kiri\Router;
use Closure;
use Kiri;
use Kiri\Router\Annotate\Defer;
use Kiri\Router\Format\IFormat;
use Kiri\Router\Format\MixedFormat;
use Kiri\Router\Format\NoBody;
@@ -32,6 +33,15 @@ class Handler implements RequestHandlerInterface
*/
protected array $middlewares = [];
/**
* @var Defer[]
*/
protected array $deferred = [];
protected ?string $sourceFile = null;
protected string $sourceKind = 'attribute';
/**
* @param array|Closure $handler
* @param array $parameters
@@ -124,6 +134,49 @@ class Handler implements RequestHandlerInterface
}
public function setSourceFile(?string $sourceFile): void
{
$this->sourceFile = $sourceFile;
}
public function getSourceFile(): ?string
{
return $this->sourceFile;
}
public function setSourceKind(string $sourceKind): void
{
$this->sourceKind = $sourceKind;
}
public function getSourceKind(): string
{
return $this->sourceKind;
}
/**
* @param Defer[] $deferred
* @return void
*/
public function setDeferred(array $deferred): void
{
$this->deferred = $deferred;
}
/**
* @return Defer[]
*/
public function getDeferred(): array
{
return $this->deferred;
}
/**
* @param ServerRequestInterface $request
* @return ResponseInterface
@@ -135,8 +188,35 @@ class Handler implements RequestHandlerInterface
$data = call_user_func([$controller, $this->handler[1]], ...$this->parameters);
$this->executeDeferred();
/** 根据返回类型 */
return $this->format->call($data);
}
/**
* @return void
*/
private function executeDeferred(): void
{
foreach ($this->deferred as $defer) {
try {
$callback = $defer->callback;
$params = $defer->params;
if (is_array($callback)) {
[$class, $method] = $callback;
$instance = Kiri::getDi()->get($class);
call_user_func([$instance, $method], ...$params);
} else {
$instance = Kiri::getDi()->get($callback);
call_user_func([$instance, '__invoke'], ...$params);
}
} catch (\Throwable $throwable) {
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
}
}
}
}
+5
View File
@@ -34,6 +34,9 @@ class OnRequest implements OnRequestInterface
public RouterCollector $router;
public DataGrip $dataGrip;
/**
* @var ExceptionHandlerInterface
*/
@@ -59,6 +62,7 @@ class OnRequest implements OnRequestInterface
*/
public function __construct(public ResponseInterface $response, DataGrip $dataGrip)
{
$this->dataGrip = $dataGrip;
$this->responseEmitter = $this->response->emmit;
$exception = \config('servers.request.exception');
if (!in_array(ExceptionHandlerInterface::class, class_implements($exception))) {
@@ -82,6 +86,7 @@ class OnRequest implements OnRequestInterface
/** @var CQ $PsrRequest */
Context::set(ResponseInterface::class, new ConstrictResponse($this->response->contentType));
$PsrRequest = Context::set(RequestInterface::class, CQ::builder($request));
$this->router = $this->dataGrip->get(ROUTER_TYPE_HTTP);
CoordinatorManager::utility(Coordinator::WORKER_START)->yield();
+839 -829
View File
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Kiri\Router;
class RouteArtifactState
{
public function store(string $type, array $artifact): void
{
$payload = [
'timestamp' => time(),
'type' => $type,
'artifact' => $artifact,
];
$directory = dirname($this->getFilePath($type));
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($this->getFilePath($type), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
public function load(string $type): array
{
$file = $this->getFilePath($type);
if (!file_exists($file)) {
return [];
}
$data = json_decode((string)file_get_contents($file), true);
if (!is_array($data)) {
return [];
}
return is_array($data['artifact'] ?? null) ? $data['artifact'] : [];
}
public function has(string $type): bool
{
return file_exists($this->getFilePath($type));
}
private function getFilePath(string $type): string
{
$basePath = realpath($_SERVER['PWD'] ?? APP_PATH ?? getcwd()) ?: ($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
$basePath = str_replace('\\', '/', $basePath);
$runtimePath = defined('APP_PATH')
? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-route-artifacts/'
: sys_get_temp_dir() . '/kiri-route-artifacts/';
return $runtimePath . md5($basePath . '::' . $type) . '.json';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Kiri\Router;
class RouteEntry
{
/**
* @param array $deferred Array of ['callback' => string|array, 'params' => array]
*/
public function __construct(
public readonly string $requestMethod,
public readonly string $path,
public readonly string $class,
public readonly string $method,
public readonly array $middlewares = [],
public readonly ?string $sourceFile = null,
public readonly string $sourceKind = 'attribute',
public readonly array $deferred = [],
) {
}
}
+59 -17
View File
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Kiri\Router;
use Closure;
use Kiri\Di\HotReloadState;
use Kiri\Server\Events\OnWorkerStart;
use Kiri;
use Kiri\Abstracts\CoordinatorManager;
use Kiri\Coordinator;
use Kiri\Router\RouteArtifactState;
use Kiri\Router\Validator\ValidatorMiddleware;
use Kiri\Router\Base\Middleware as MiddlewareManager;
use Kiri\Router\Constrict\RequestMethod;
@@ -35,6 +37,8 @@ class Router
*/
private static string $type = ROUTER_TYPE_HTTP;
private static ?string $currentSourceFile = null;
/**
* @param string $name
@@ -187,13 +191,53 @@ class Router
public function scan_build_route(): void
{
$coordinator = CoordinatorManager::utility(Coordinator::WORKER_START);
$this->read_dir_file(APP_PATH . 'routes');
$container = Kiri::getDi();
$container->get(DataGrip::class)->reset(static::$type);
$scanner = $container->get(Kiri\Di\Scanner::class);
$scanner->scan(APP_PATH . 'app/');
$artifactState = $container->get(RouteArtifactState::class);
$scanConfig = array_merge(
config('servers.reload.scan', []),
config('site.scanner', [])
);
$scanner->setConfig($scanConfig);
$changedFiles = $container->get(HotReloadState::class)->consume();
$normalizedAppPath = str_replace('\\', '/', APP_PATH . 'app');
$normalizedRoutePath = str_replace('\\', '/', APP_PATH . 'routes');
$routeChanged = false;
$appChangedFiles = [];
foreach ($changedFiles as $changedFile) {
if (str_starts_with($changedFile, $normalizedRoutePath . '/')) {
$routeChanged = true;
continue;
}
if (str_starts_with($changedFile, $normalizedAppPath . '/')) {
$appChangedFiles[] = $changedFile;
}
}
$usedArtifact = false;
if (($scanConfig['cache_enabled'] ?? false) && !$routeChanged && $artifactState->has(static::$type)) {
$artifact = $artifactState->load(static::$type);
$router = $container->get(DataGrip::class)->get(static::$type);
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
}
if (!$usedArtifact) {
$this->read_dir_file(APP_PATH . 'routes');
}
if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) {
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact);
} elseif (!$usedArtifact) {
$scanner->scan(APP_PATH . 'app/');
} else {
$scanner->scanFiles([], APP_PATH . 'app/', null, false);
}
$this->reset($container);
$artifactState->store(static::$type, $container->get(DataGrip::class)->get(static::$type)->exportArtifact());
$coordinator->done();
}
@@ -208,19 +252,8 @@ class Router
public function reset(ContainerInterface $container): void
{
$router = $container->get(DataGrip::class)->get(static::$type);
foreach ($router->getMethods() as $name => $method) {
$middlewares = $method->getMiddlewares();
foreach ($middlewares as $key => $middleware) {
$middlewares[$key] = di($middleware);
}
$requestHandler = new HttpRequestHandler($middlewares, $method);
$validator = MiddlewareManager::getValidator($method->getClass(), $method->getMethod());
if (!is_null($validator)) {
$requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(ResponseInterface::class), $method->getClass(), $method->getMethod()));
}
$router->setHttpHandler($name, $requestHandler);
if ((bool)config('servers.reload.scan.prebuild_http_handlers', false)) {
$router->warmHttpHandlers();
}
}
@@ -253,10 +286,19 @@ class Router
private function resolve_file($files): void
{
try {
static::$currentSourceFile = str_replace('\\', '/', realpath($files) ?: $files);
include "$files";
} catch (\Throwable $throwable) {
\Kiri::getLogger()->json_log($throwable);
} finally {
static::$currentSourceFile = null;
}
}
public static function getCurrentSourceFile(): ?string
{
return static::$currentSourceFile;
}
}
+207 -8
View File
@@ -6,6 +6,8 @@ namespace Kiri\Router;
use Closure;
use Kiri\Router\Annotate\Defer;
use Kiri\Router\Annotate\DeferRegistry;
use Kiri\Router\Base\NotFoundController;
use Kiri\Router\Constrict\RequestMethod;
use Psr\Http\Server\MiddlewareInterface;
@@ -14,6 +16,7 @@ use Throwable;
use Traversable;
use Kiri\Router\Base\Middleware;
use Kiri\Router\Format\ResponseFormat;
use Kiri\Router\Validator\ValidatorMiddleware;
/**
@@ -42,7 +45,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
/**
* @var array<string, Handler>
* @var array<string, Handler|RouteEntry>
*/
private array $methods = [];
@@ -69,7 +72,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
/**
* @return Handler[]
* @return array<string, Handler|RouteEntry>
*/
public function getMethods(): array
{
@@ -77,6 +80,16 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
}
public function clear(): void
{
$this->_item = [];
$this->dump = [];
$this->groupTack = [];
$this->methods = [];
$this->httpHandler = [];
}
/**
* @param string $method
* @param HttpRequestHandler $handler
@@ -171,14 +184,26 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
if (is_string($closure[0])) {
$closure[0] = $container->get($closure[0]);
}
return $interpreter->addRouteByString(... $closure);
$handler = $interpreter->addRouteByString(... $closure);
$sourceFile = Router::getCurrentSourceFile();
if ($sourceFile !== null) {
$handler->setSourceFile($sourceFile);
$handler->setSourceKind('route_file');
}
return $handler;
}
if (!str_contains($closure, '@')) {
$closure .= '@';
}
[$className, $method] = explode('@', $closure);
$class = $container->get($this->resetName($className));
return $interpreter->addRouteByString($class, $method);
$handler = $interpreter->addRouteByString($class, $method);
$sourceFile = Router::getCurrentSourceFile();
if ($sourceFile !== null) {
$handler->setSourceFile($sourceFile);
$handler->setSourceKind('route_file');
}
return $handler;
}
@@ -205,8 +230,100 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
*/
public function register(string $path, string $method, Handler $handler): void
{
if ($handler->getSourceFile() === null && $handler->getClass() !== null) {
$reflect = \Kiri::getDi()->getReflectionClass($handler->getClass());
$handler->setSourceFile($this->normalizePath((string)$reflect->getFileName()));
$handler->setSourceKind('attribute');
}
$this->methods[$method . '_' . $path] = $handler;
$handler->setMiddlewares($this->registerMiddleware($handler->getClass(), $handler->getMethod()));
$handler->setDeferred(DeferRegistry::get($handler->getClass(), $handler->getMethod()));
}
public function exportArtifact(): array
{
$entries = [];
$hasClosureRoutes = false;
foreach ($this->methods as $methodPath => $handler) {
if ($handler instanceof Handler && $handler->isClosure()) {
$hasClosureRoutes = true;
continue;
}
[$requestMethod, $path] = explode('_', $methodPath, 2);
$class = $handler instanceof Handler ? $handler->getClass() : $handler->class;
$method = $handler instanceof Handler ? $handler->getMethod() : $handler->method;
$middlewares = $handler instanceof Handler ? $handler->getMiddlewares() : $handler->middlewares;
$sourceFile = $handler instanceof Handler ? $handler->getSourceFile() : $handler->sourceFile;
$sourceKind = $handler instanceof Handler ? $handler->getSourceKind() : $handler->sourceKind;
$deferred = $handler instanceof Handler ? $this->serializeDeferred($handler->getDeferred()) : ($handler->deferred ?? []);
$entries[] = [
'request_method' => $requestMethod,
'path' => $path,
'class' => $class,
'method' => $method,
'middlewares' => $middlewares,
'source_file' => $sourceFile,
'source_kind' => $sourceKind,
'deferred' => $deferred,
];
}
return [
'has_closure_routes' => $hasClosureRoutes,
'entries' => $entries,
];
}
public function importArtifact(array $artifact, array $excludeSourceFiles = []): bool
{
if (($artifact['has_closure_routes'] ?? false) === true) {
return false;
}
$entries = $artifact['entries'] ?? null;
if (!is_array($entries)) {
return false;
}
$exclude = array_fill_keys(array_map([$this, 'normalizePath'], $excludeSourceFiles), true);
foreach ($entries as $entry) {
if (!is_array($entry)) {
continue;
}
$sourceFile = $entry['source_file'] ?? null;
if (is_string($sourceFile) && isset($exclude[$this->normalizePath($sourceFile)])) {
continue;
}
$class = $entry['class'] ?? null;
$method = $entry['method'] ?? null;
$requestMethod = $entry['request_method'] ?? null;
$path = $entry['path'] ?? null;
if (!is_string($class) || !is_string($method) || !is_string($requestMethod) || !is_string($path)) {
continue;
}
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(
requestMethod: $requestMethod,
path: $path,
class: $class,
method: $method,
middlewares: is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [],
sourceFile: is_string($sourceFile) ? $this->normalizePath($sourceFile) : null,
sourceKind: is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute',
deferred: is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [],
);
}
return true;
}
@@ -262,10 +379,11 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
/**
* @param array $response
* @param array $middlewares
* @return array
* @param string $class
* @param string $method
* @return Defer[]
*/
private function appendMiddleware(array $response, array $middlewares): array
{
foreach ($middlewares as $middleware) {
@@ -289,7 +407,12 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
*/
public function query(string $path, string $method): HttpRequestHandler
{
return $this->httpHandler[$method . '_' . $path] ?? $this->not_found_handler();
$key = $method . '_' . $path;
if (!isset($this->httpHandler[$key]) && isset($this->methods[$key])) {
$this->httpHandler[$key] = $this->compileHandler($this->methods[$key]);
}
return $this->httpHandler[$key] ?? $this->not_found_handler();
}
@@ -309,6 +432,14 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
}
public function warmHttpHandlers(): void
{
foreach ($this->methods as $name => $method) {
$this->httpHandler[$name] = $this->compileHandler($method);
}
}
/**
* @param string $route
* @return string
@@ -324,6 +455,74 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
}
/**
* @param Defer[] $deferred
* @return array
*/
private function serializeDeferred(array $deferred): array
{
$result = [];
foreach ($deferred as $defer) {
$result[] = [
'callback' => $defer->callback,
'params' => $defer->params,
];
}
return $result;
}
/**
* @param array $data
* @return Defer[]
*/
private function deserializeDeferred(array $data): array
{
$result = [];
foreach ($data as $item) {
if (isset($item['callback'])) {
$result[] = new Defer($item['callback'], $item['params'] ?? []);
}
}
return $result;
}
private function normalizePath(string $path): string
{
$resolved = realpath($path) ?: $path;
return str_replace('\\', '/', $resolved);
}
private function compileHandler(Handler|RouteEntry $method): HttpRequestHandler
{
if ($method instanceof RouteEntry) {
$controller = \Kiri::getDi()->get($method->class);
$handler = di(ControllerInterpreter::class)->addRouteByString($controller, $method->method);
$handler->setRequestMethod($method->requestMethod);
$handler->setMiddlewares($method->middlewares);
$handler->setSourceFile($method->sourceFile);
$handler->setSourceKind($method->sourceKind);
$handler->setDeferred($this->deserializeDeferred($method->deferred));
$method = $handler;
}
$middlewares = $method->getMiddlewares();
foreach ($middlewares as $key => $middleware) {
$middlewares[$key] = di($middleware);
}
$requestHandler = new HttpRequestHandler($middlewares, $method);
$validator = Middleware::getValidator($method->getClass(), $method->getMethod());
if ($validator !== null) {
$requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(\Psr\Http\Message\ResponseInterface::class), $method->getClass(), $method->getMethod()));
}
return $requestHandler;
}
/**
* @param mixed $offset
* @return bool
+1 -1
View File
@@ -98,7 +98,7 @@ class Session
*/
private static function getSessionId(ServerRequestInterface $request): string
{
$cookies = $request->getCookieParams();
$cookies = $request->cookieParams;
// 从 Cookie 中获取 Session ID
if (isset($cookies[self::SESSION_NAME]) && !empty($cookies[self::SESSION_NAME])) {