Files
portal-api/app/Http/Middleware/AdvancedCompressionMiddleware.php

131 lines
4.1 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AdvancedCompressionMiddleware
{
/**
* Compression settings
*/
private const MIN_LENGTH = 1024; // Minimum content length to compress (1KB)
private const COMPRESSION_LEVEL = 6; // Compression level (1-9)
private const MAX_COMPRESSION_RATIO = 0.9; // Don't compress if compression ratio > 90%
/**
* Content types that should be compressed
*/
private const COMPRESSIBLE_TYPES = [
'application/json',
'text/plain',
'text/html',
'text/xml',
'application/xml',
'text/css',
'application/javascript',
'text/javascript'
];
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if (!$this->shouldCompress($request, $response)) {
return $response;
}
return $this->compressResponse($request, $response);
}
/**
* Compress the response content
*/
private function compressResponse(Request $request, Response $response): Response
{
$content = $response->getContent();
$originalLength = strlen($content);
if ($originalLength < self::MIN_LENGTH) {
return $response;
}
$acceptEncoding = $request->header('Accept-Encoding', '');
$compressed = null;
$encoding = null;
// Try Brotli compression first (best compression ratio)
if (str_contains($acceptEncoding, 'br') && function_exists('brotli_compress')) {
$compressed = brotli_compress($content, self::COMPRESSION_LEVEL);
$encoding = 'br';
}
// Try Gzip compression
elseif (str_contains($acceptEncoding, 'gzip') && function_exists('gzencode')) {
$compressed = gzencode($content, self::COMPRESSION_LEVEL);
$encoding = 'gzip';
}
// Fallback to Deflate
elseif (str_contains($acceptEncoding, 'deflate') && function_exists('gzdeflate')) {
$compressed = gzdeflate($content, self::COMPRESSION_LEVEL);
$encoding = 'deflate';
}
// Check compression efficiency
if ($compressed !== false && $encoding) {
$compressionRatio = strlen($compressed) / $originalLength;
// Only use compressed content if it's significantly smaller
if ($compressionRatio < self::MAX_COMPRESSION_RATIO) {
$response->setContent($compressed);
$response->headers->set('Content-Encoding', $encoding);
$response->headers->set('Content-Length', strlen($compressed));
$response->headers->set('Vary', 'Accept-Encoding');
// Add compression info to headers for debugging
if (config('app.debug')) {
$response->headers->set('X-Compression-Ratio', round($compressionRatio * 100, 2) . '%');
$response->headers->set('X-Original-Size', $originalLength);
$response->headers->set('X-Compressed-Size', strlen($compressed));
}
}
}
return $response;
}
/**
* Determine if the response should be compressed
*/
private function shouldCompress(Request $request, Response $response): bool
{
// Don't compress if already compressed
if ($response->headers->has('Content-Encoding')) {
return false;
}
// Don't compress file downloads
if ($response->headers->has('Content-Disposition')) {
return false;
}
// Only compress successful responses
if ($response->getStatusCode() >= 400) {
return false;
}
// Check if content type is compressible
$contentType = $response->headers->get('Content-Type', '');
foreach (self::COMPRESSIBLE_TYPES as $type) {
if (str_contains($contentType, $type)) {
return true;
}
}
return false;
}
}