Updated to router v3.

This commit is contained in:
flash 2025-03-20 21:07:34 +00:00
parent a8359af2e3
commit 6e177caaf8
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
6 changed files with 181 additions and 206 deletions

View file

@ -1,6 +1,6 @@
{
"require": {
"flashwave/index": "^0.2410",
"flashwave/index": "^0.2503",
"sentry/sdk": "^4.0"
},
"autoload": {

210
composer.lock generated
View file

@ -4,25 +4,27 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "023574a93f076ef7a987151cdbfdb33e",
"content-hash": "ca6ae2c3d6229791acf93914e055bf41",
"packages": [
{
"name": "flashwave/index",
"version": "v0.2410.830205",
"version": "v0.2503.201929",
"source": {
"type": "git",
"url": "https://patchii.net/flash/index.git",
"reference": "416c716b2efab9619d14be02a20cd6540e18a83f"
"reference": "1ba9e8fa34fbd30c84c23b2a9b6beb2152ca54f0"
},
"require": {
"ext-mbstring": "*",
"php": ">=8.3",
"twig/html-extra": "^3.13",
"twig/twig": "^3.14"
"php": ">=8.4",
"psr/http-message": "^2.0",
"psr/http-server-handler": "^1.0",
"twig/html-extra": "^3.20",
"twig/twig": "^3.20"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^11.4"
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^12.0"
},
"suggest": {
"ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).",
@ -59,7 +61,7 @@
],
"description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index",
"time": "2024-12-22T02:05:46+00:00"
"time": "2025-03-20T19:29:51+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -179,16 +181,16 @@
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10"
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
@ -198,8 +200,9 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
@ -232,9 +235,9 @@
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2024-11-18T16:19:46+00:00"
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "psr/http-factory",
@ -344,6 +347,62 @@
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/http-server-handler",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
"keywords": [
"handler",
"http",
"http-interop",
"psr",
"psr-15",
"psr-7",
"request",
"response",
"server"
],
"support": {
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
},
"time": "2023-04-10T20:06:20+00:00"
},
{
"name": "psr/log",
"version": "3.0.2",
@ -651,16 +710,16 @@
},
{
"name": "symfony/mime",
"version": "v7.2.1",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283"
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283",
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
"shasum": ""
},
"require": {
@ -715,7 +774,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v7.2.1"
"source": "https://github.com/symfony/mime/tree/v7.2.4"
},
"funding": [
{
@ -731,7 +790,7 @@
"type": "tidelift"
}
],
"time": "2024-12-07T08:50:44+00:00"
"time": "2025-02-19T08:51:20+00:00"
},
{
"name": "symfony/options-resolver",
@ -1123,98 +1182,22 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "twig/html-extra",
"version": "v3.18.0",
"version": "v3.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/html-extra.git",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a"
"reference": "f7d54d4de1b64182af745cfb66777f699b599734"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/f7d54d4de1b64182af745cfb66777f699b599734",
"reference": "f7d54d4de1b64182af745cfb66777f699b599734",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/mime": "^5.4|^6.4|^7.0",
"twig/twig": "^3.13|^4.0"
@ -1253,7 +1236,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.18.0"
"source": "https://github.com/twigphp/html-extra/tree/v3.20.0"
},
"funding": [
{
@ -1265,28 +1248,27 @@
"type": "tidelift"
}
],
"time": "2024-12-29T10:29:59+00:00"
"time": "2025-01-31T20:45:36+00:00"
},
{
"name": "twig/twig",
"version": "v3.18.0",
"version": "v3.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50"
"reference": "3468920399451a384bef53cf7996965f7cd40183"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
"reference": "3468920399451a384bef53cf7996965f7cd40183",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
@ -1333,7 +1315,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.18.0"
"source": "https://github.com/twigphp/Twig/tree/v3.20.0"
},
"funding": [
{
@ -1345,7 +1327,7 @@
"type": "tidelift"
}
],
"time": "2024-12-29T10:51:50+00:00"
"time": "2025-02-13T08:34:43+00:00"
}
],
"packages-dev": [],

View file

@ -17,11 +17,15 @@ use Uiharu\Lookup\NicoNicoLookupResult;
use Index\MediaType;
use Index\Cache\CacheProvider;
use Index\Colour\{Colour,ColourRgb};
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandlerTrait};
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\RouteHandlerCommon;
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Routes\ExactRoute;
use Index\Http\Streams\Stream;
use Index\Performance\Stopwatch;
final class v1_0 implements \Uiharu\IApi {
use RouteHandlerTrait;
use RouteHandlerCommon;
private UihContext $ctx;
private CacheProvider $cache;
@ -35,8 +39,9 @@ final class v1_0 implements \Uiharu\IApi {
return !str_starts_with($url, '/v');
}
#[HttpGet('/metadata/thumb/audio')]
public function getThumbAudio($response, $request) {
#[AccessControl]
#[ExactRoute('GET', '/metadata/thumb/audio')]
public function getThumbAudio(HttpResponseBuilder $response, HttpRequest $request) {
$targetUrl = (string)$request->getParam('url');
if(empty($targetUrl))
@ -53,11 +58,12 @@ final class v1_0 implements \Uiharu\IApi {
$response->setContentType('image/png');
$response->setCacheControl('public', 'max-age=31536000', 'immutable');
$response->setContent(FFMPEG::grabFirstAudioCover($parsedUrl));
$response->body = Stream::createStream(FFMPEG::grabFirstAudioCover($parsedUrl));
}
#[HttpGet('/metadata/thumb/video')]
public function getThumbVideo($response, $request) {
#[AccessControl]
#[ExactRoute('GET', '/metadata/thumb/video')]
public function getThumbVideo(HttpResponseBuilder $response, HttpRequest $request) {
$targetUrl = (string)$request->getParam('url');
if(empty($targetUrl))
@ -74,55 +80,37 @@ final class v1_0 implements \Uiharu\IApi {
$response->setContentType('image/png');
$response->setCacheControl('public', 'max-age=31536000', 'immutable');
$response->setContent(FFMPEG::grabFirstVideoFrame($parsedUrl));
$response->body = Stream::createStream(FFMPEG::grabFirstVideoFrame($parsedUrl));
}
#[HttpGet('/metadata')]
public function getMetadata($response, $request) {
if($request->getMethod() === 'HEAD') {
$response->setTypeJson();
return;
}
#[AccessControl]
#[ExactRoute('GET', '/metadata')]
public function getMetadata(HttpResponseBuilder $response, HttpRequest $request) {
return $this->handleMetadata(
$response, $request,
(string)$request->getParam('url')
);
}
#[HttpPost('/metadata')]
public function postMetadata($response, $request) {
if(!$request->isStringContent())
return 400;
#[AccessControl]
#[ExactRoute('POST', '/metadata')]
public function postMetadata(HttpResponseBuilder $response, HttpRequest $request) {
return $this->handleMetadata(
$response, $request,
(string)$request->getContent()
(string)$request->getBody()
);
}
#[HttpGet('/metadata/batch')]
public function getMetadataBatch($response, $request) {
if($request->getMethod() === 'HEAD') {
$response->setTypeJson();
return;
}
return $this->handleMetadataBatch(
$response, $request,
$request->getParam('url', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY)
);
#[AccessControl]
#[ExactRoute('GET', '/metadata/batch')]
public function getMetadataBatch(): int {
return 410;
}
#[HttpPost('/metadata/batch')]
public function postMetadataBatch($response, $request) {
if(!$request->isFormContent())
return 400;
return $this->handleMetadataBatch(
$response, $request,
$request->getContent()->getParam('url', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY)
);
#[AccessControl]
#[ExactRoute('POST', '/metadata/batch')]
public function postMetadataBatch(): int {
return 410;
}
private function metadataLookup(string $targetUrl) {
@ -172,11 +160,11 @@ final class v1_0 implements \Uiharu\IApi {
$resp->content_type = MediaTypeExts::toV1($result->getMediaType());
if($result->hasColour()) {
$colour = $result->getColour();
if($colour->getAlpha() < 1.0)
if($colour->alpha < 1.0)
$colour = new ColourRgb(
$colour->getRed(),
$colour->getGreen(),
$colour->getBlue()
$colour->red,
$colour->green,
$colour->blue,
);
$resp->color = (string)$colour;
@ -278,7 +266,7 @@ final class v1_0 implements \Uiharu\IApi {
}
$sw->stop();
$resp->took = $sw->getElapsedTime() / 1000;
$resp->took = $sw->elapsedTime / 1000;
$respJson = json_encode($resp);
$this->cache->set($cacheKey, $respJson, 10 * 60);
@ -290,7 +278,7 @@ final class v1_0 implements \Uiharu\IApi {
return $resp;
}
private function handleMetadata($response, $request, string $targetUrl) {
private function handleMetadata(HttpResponseBuilder $response, HttpRequest $request, string $targetUrl) {
$result = $this->metadataLookup($targetUrl);
if(is_int($result))
return $result;
@ -305,7 +293,7 @@ final class v1_0 implements \Uiharu\IApi {
return $result;
}
private function handleMetadataBatch($response, $request, array $urls) {
private function handleMetadataBatch(HttpResponseBuilder $response, HttpRequest $request, array $urls) {
$sw = Stopwatch::startNew();
if(count($urls) > 20)

View file

@ -7,14 +7,14 @@ final class MediaTypeExts {
public static function toV1(MediaType $mediaType): array {
$parts = [
'string' => (string)$mediaType,
'type' => $mediaType->getCategory(),
'subtype' => $mediaType->getKind(),
'type' => $mediaType->category,
'subtype' => $mediaType->kind,
];
if(!empty($suffix = $mediaType->getSuffix()))
if(!empty($suffix = $mediaType->suffix))
$parts['suffix'] = $suffix;
if(!empty($params = $mediaType->getParams()))
if(!empty($params = $mediaType->params))
$parts['params'] = $params;
return $parts;

View file

@ -3,12 +3,15 @@ namespace Uiharu;
use Index\Cache\CacheProvider;
use Index\Config\Config;
use Index\Http\Routing\HttpRouter;
use Index\Http\HttpResponseBuilder;
use Index\Http\Routing\{Router,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Filters\PrefixFilter;
use Index\Http\Routing\Routes\ExactRoute;
final class UihContext {
private CacheProvider $cache;
private Config $config;
private HttpRouter $router;
private Router $router;
private array $apis = [];
private array $lookups = [];
@ -21,52 +24,28 @@ final class UihContext {
return $this->cache;
}
public function getRouter(): HttpRouter {
public function getRouter(): Router {
return $this->router;
}
public function isOriginAllowed(string $origin): bool {
$origin = mb_strtolower(parse_url($origin, PHP_URL_HOST));
if($origin === $_SERVER['HTTP_HOST'])
return true;
$allowed = $this->config->getArray('cors:origins');
if(empty($allowed))
return true;
return in_array($origin, $allowed);
}
public function setupHttp(): void {
$this->router = new HttpRouter;
$this->router->use('/', function($response) {
$response->setPoweredBy('Uiharu');
});
$this->router = new Router(
accessControlHandler: new UiharuAccessControlHandler($this->config->getArray('cors:origins')),
);
$this->router->register(new class implements RouteHandler {
use RouteHandlerCommon;
$this->router->use('/', function($response, $request) {
$origin = $request->getHeaderLine('Origin');
if(!empty($origin)) {
if(!$this->isOriginAllowed($origin))
return 403;
$response->setHeader('Access-Control-Allow-Origin', $origin);
$response->setHeader('Vary', 'Origin');
#[PrefixFilter('/')]
public function filterPoweredBy(HttpResponseBuilder $response): void {
$response->setPoweredBy('Uiharu');
}
});
$this->router->use('/', function($response, $request) {
if($request->getMethod() === 'OPTIONS') {
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST');
return 204;
#[ExactRoute('GET', '/')]
public function getIndex(HttpResponseBuilder $response): void {
$response->accelRedirect('/index.html');
$response->setContentType('text/html; charset=utf-8');
}
});
$this->router->get('/', function($response) {
$response->accelRedirect('/index.html');
$response->setContentType('text/html; charset=utf-8');
});
}
public function dispatchHttp(): void {

View file

@ -0,0 +1,26 @@
<?php
namespace Uiharu;
use Index\Http\HttpUri;
use Index\Http\Routing\HandlerContext;
use Index\Http\Routing\Routes\RouteInfo;
use Index\Http\Routing\AccessControl\{AccessControl,SimpleAccessControlHandler};
class UiharuAccessControlHandler extends SimpleAccessControlHandler {
public function __construct(
private array $origins,
) {}
#[\Override]
public function checkAccess(
HandlerContext $context,
AccessControl $accessControl,
HttpUri $origin,
?RouteInfo $routeInfo = null,
): string|bool {
if(!in_array($origin->host, $this->origins))
return false;
return $accessControl->credentials ? (string)$origin : true;
}
}