diff --git a/app/Http/Controllers/DownloadController.php b/app/Http/Controllers/DownloadController.php index 665b934d2bdd1bd3a0b4e6cdbda1028d0c9e0b8c..d7b2f60190ca697ba4b891f6dbba8189d69140b9 100644 --- a/app/Http/Controllers/DownloadController.php +++ b/app/Http/Controllers/DownloadController.php @@ -68,6 +68,8 @@ class DownloadController extends Controller unset($headers["Location"]); unset($headers["location"]); + + \App\PrometheusExporter::registerDownload(); $response = new StreamedResponse(function () use ($url) { # We are gonna stream a large file diff --git a/app/Http/Controllers/ProxyController.php b/app/Http/Controllers/ProxyController.php index 848ac22d3648b16d25d678b034cbb6b1c8ae830b..ec324b2977172cf39eb1d1ee2ea33ac8334c9120 100644 --- a/app/Http/Controllers/ProxyController.php +++ b/app/Http/Controllers/ProxyController.php @@ -93,6 +93,7 @@ class ProxyController extends Controller return redirect($targetUrl); } + \App\PrometheusExporter::registerProxyCall(); $this->writeLog($targetUrl, $request->ip()); $urlToProxy = self::generateProxyUrl($targetUrl); @@ -250,6 +251,7 @@ class ProxyController extends Controller $htmlDocument->proxifyContent(); $answer['headers']['content-type'] = $contentType . "; charset=" . $htmlDocument->getEncoding(); $body = $htmlDocument->getResult(); + \App\PrometheusExporter::registerProxyPageCall("html"); break; case 'application/pdf': // We will download all PDF Files @@ -261,38 +263,42 @@ class ProxyController extends Controller "validuntil" => $postData["valid-until"], "password" => $postData["password"] ]), 413); + \App\PrometheusExporter::registerProxyPageCall("pdf"); break; // no break case 'image/png': case 'image/jpeg': case 'image/gif': - case 'image/webp': case 'image/vnd.microsoft.icon': + case 'image/svg+xml': + case 'image/webp': + \App\PrometheusExporter::registerProxyPageCall("image"); + break; case 'application/font-woff': case 'application/x-font-woff': - case 'application/x-empty': case 'font/woff2': - case 'image/svg+xml': - case 'application/octet-stream': - case 'text/plain': - case 'image/x-icon': case 'font/eot': case 'application/vnd.ms-fontobject': case 'application/x-font-ttf': + case 'font/woff': + case 'font/woff2': + case 'application/x-empty': + case 'application/octet-stream': + case 'text/plain': + case 'image/x-icon': case 'application/x-www-form-urlencoded': case 'application/zip': case 'binary/octet-stream': case 'application/vnd.android.package-archive': - case 'image/svg+xml': - case 'font/woff': - case 'font/woff2': # Nothing to do with Images: Just return them + \App\PrometheusExporter::registerProxyPageCall("undefined"); break; case 'text/css': # Css Documents might contain references to External Documents that need to get Proxified $cssDocument = new CssDocument($password, $targetUrl, $body); $cssDocument->proxifyContent(); $body = $cssDocument->getResult(); + \App\PrometheusExporter::registerProxyPageCall("css"); break; default: # We have no Parser for this one. Let's respond: diff --git a/app/PrometheusExporter.php b/app/PrometheusExporter.php new file mode 100644 index 0000000000000000000000000000000000000000..e0226324c06736bdb0f18dfaed872d7a19293b79 --- /dev/null +++ b/app/PrometheusExporter.php @@ -0,0 +1,32 @@ +<?php + +namespace App; + +class PrometheusExporter { + + const NAMESPACE = "proxy"; + + /** + * Registers page calls not counting subdocument (css, js, etc) + */ + static function registerProxyCall() { + $registry = \Prometheus\CollectorRegistry::getDefault(); + $counter = $registry->getOrRegisterCounter(self::NAMESPACE , 'proxy_pages', 'counts calls to the proxy', []); + $counter->inc(); + } + + /** + * Registers all proxied documents and their type + */ + static function registerProxyPageCall($type) { + $registry = \Prometheus\CollectorRegistry::getDefault(); + $counter = $registry->getOrRegisterCounter(self::NAMESPACE , 'proxy_documents', 'counts proxied documents (html, js, css,...)', ['type']); + $counter->incBy(1, [$type]); + } + + static function registerDownload() { + $registry = \Prometheus\CollectorRegistry::getDefault(); + $counter = $registry->getOrRegisterCounter(self::NAMESPACE , 'proxy_download', 'counts streamed downloads', []); + $counter->inc(); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ee8ca5bcd8f77d219f29529a9163587235c545d5..1b9ab6149e3251eb18bb2824cece358943ebf95e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,6 +23,15 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - // + \Prometheus\Storage\Redis::setDefaultOptions( + [ + 'host' => env("REDIS_HOST", '127.0.0.1'), + 'port' => intval(env("REDIS_PORT", 6379)), + 'password' => env("REDIS_PASSWORD", null), + 'timeout' => 0.1, // in seconds + 'read_timeout' => '10', // in seconds + 'persistent_connections' => false + ] + ); } } diff --git a/composer.json b/composer.json index b11a27d1e6d4570a5aa8574f4b0345a0551d6b9d..a2149961be678a3189680b90affe5bf98a7285ea 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", "laravel/framework": "^8.40", - "laravel/tinker": "^2.5" + "laravel/tinker": "^2.5", + "promphp/prometheus_client_php": "^2.2" }, "require-dev": { "facade/ignition": "^2.5", @@ -62,4 +63,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 0f31599a1abb1b7a8ce46771da5d92e25a971636..c68430acb7035ccd9d4146366f62a1a34571373f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7759431e303e2d722a264a8a991a9e84", + "content-hash": "cc15a39b03ee423aae937e3af0cc7138", "packages": [ { "name": "asm89/stack-cors", @@ -1638,6 +1638,72 @@ ], "time": "2020-07-20T17:29:33+00:00" }, + { + "name": "promphp/prometheus_client_php", + "version": "v2.2.2", + "source": { + "type": "git", + "url": "https://github.com/PromPHP/prometheus_client_php.git", + "reference": "5d27b6d84900d9b3208b5b6bf88d10ed0dc7a154" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/5d27b6d84900d9b3208b5b6bf88d10ed0dc7a154", + "reference": "5d27b6d84900d9b3208b5b6bf88d10ed0dc7a154", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2|^8.0", + "symfony/polyfill-apcu": "^1.6" + }, + "replace": { + "endclothing/prometheus_client_php": "*", + "jimdo/prometheus_client_php": "*", + "lkaemmerling/prometheus_client_php": "*" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.3|^7.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.50", + "phpstan/phpstan-phpunit": "^0.12.16", + "phpstan/phpstan-strict-rules": "^0.12.5", + "phpunit/phpunit": "^8.4|^9.4", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-apc": "Required if using APCu.", + "ext-redis": "Required if using Redis.", + "promphp/prometheus_push_gateway_php": "An easy client for using Prometheus PushGateway." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Prometheus\\": "src/Prometheus/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Lukas Kämmerling", + "email": "kontakt@lukas-kaemmerling.de" + } + ], + "description": "Prometheus instrumentation library for PHP applications.", + "support": { + "issues": "https://github.com/PromPHP/prometheus_client_php/issues", + "source": "https://github.com/PromPHP/prometheus_client_php/tree/v2.2.2" + }, + "time": "2021-03-05T08:54:14+00:00" + }, { "name": "psr/container", "version": "1.1.1", @@ -3155,6 +3221,83 @@ ], "time": "2021-07-21T12:40:44+00:00" }, + { + "name": "symfony/polyfill-apcu", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-apcu.git", + "reference": "80f7fb64c5b64ebcba76f40215e63808a2062a18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/80f7fb64c5b64ebcba76f40215e63808a2062a18", + "reference": "80f7fb64c5b64ebcba76f40215e63808a2062a18", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Apcu\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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 apcu_* functions to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "apcu", + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-apcu/tree/v1.23.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": "2021-02-19T12:13:01+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.23.0", @@ -7009,5 +7152,5 @@ "php": "^7.3|^8.0" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.1.0" } diff --git a/routes/web.php b/routes/web.php index c9c6729b105f191a8b82b0ac5e0e6acd4e50da72..cb33b6b49c7a675922dc48c417a4007dd0e8ea4b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,3 +70,34 @@ Route::post('proxy/{password}/{id}/{url}', [ProxyController::class, 'streamFile' Route::post('/{url}', function ($url) { abort(405); }); + +Route::get('metrics', function (Request $request) { + // Only allow access to metrics from within our network + $ip = $request->ip(); + $allowedNetworks = [ + "10.", + "172.", + "192.", + "127.0.0.1", + ]; + + $allowed = false; + foreach($allowedNetworks as $part){ + if(stripos($ip, $part) === 0){ + $allowed = true; + } + } + + if(!$allowed){ + abort(401); + } + + $registry = \Prometheus\CollectorRegistry::getDefault(); + + $renderer = new \Prometheus\RenderTextFormat(); + $result = $renderer->render($registry->getMetricFamilySamples()); + + return response($result, 200) + ->header('Content-Type', \Prometheus\RenderTextFormat::MIME_TYPE); +}); +