131 lines
4.1 KiB
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;
|
|
}
|
|
} |