jenssegers / php-proxy

A PHP proxy script with https and post support
https://jenssegers.com
932 stars 266 forks source link

Caching resources - jpg / fonts etc. #55

Closed ursulnegrul closed 6 years ago

ursulnegrul commented 6 years ago

is there a middleware i could use to save the remote resources locally in the same relative path, like fonts and pictures, so .htaccess won't rewrite to the proxy.php if it founds the resources locally. Thank you

jenssegers commented 6 years ago

I guess you could add a custom filter that does not forward the request and does an early response return.

huchim commented 4 years ago

I know this a closed issue, but may be this filter can help to future readers:

$response = $proxy
    ->forward($request)
    ->filter(function ($request, $response, $next) {
        // Inform origin server this is a mirror
        $request = $request->withHeader('X-Client-Key', 'Mirror/1');

        // Cache Object Id
        $cacheDir = sys_get_temp_dir();
        $cacheObjectId = hash("md5", (string) $request->getUri());
        $cacheObjectMime = $cacheObjectId . ".headers";
        $cacheEntry = $cacheDir . DIRECTORY_SEPARATOR . $cacheObjectId;
        $cacheEntryHeaders = $cacheDir . DIRECTORY_SEPARATOR . $cacheObjectMime;
        $cacheControl = $request->hasHeader("Cache-Control") ?
            $request->getHeader("Cache-Control")[0] :
            "max-age=0";
        $cacheEntryFound = file_exists($cacheEntry);
        // A value indicating if cache will be used...
        $enableCache = $cacheEntryFound && $cacheControl !== "no-cache";

        // Purge cache
        $cachePurge = $request->hasHeader("X-Cache-Purge") ?
            $request->getHeader("X-Cache-Purge")[0] :
            "no";

        if ($cacheEntryFound && strtolower($cachePurge) === "yes") {
            unlink($cacheEntry);
            unlink($cacheEntryHeaders);

            $cacheEntryFound = false;
        }

        // Serve from cache.
        if ($enableCache && $cacheEntryFound) {
            // We expect a file with original headers.
            $cacheRawHeaders = file_exists($cacheEntryHeaders) ?
                // We create an array of headers
                explode("\n", file_get_contents($cacheEntryHeaders)) :
                // or assign a default list of headrs.
                ["Content-Type: application/octet-stream"];

            // We can overwrite headers to add cache.
            $cacheRawHeaders[] = "Cache-Control: max-age=31536000";
            // Resource was served from a caché.
            $cacheRawHeaders[] = "X-Cache: HIT";

            // Mutate arrayto convert it to valid headers.
            $cacheHeaders = [];
            foreach ($cacheRawHeaders as $headerLine) {
                $headerNameLength = strpos($headerLine, ":");

                if ($headerNameLength === false) {
                    // An incorrect header line.
                    continue;
                }

                $headerName = substr($headerLine, 0, $headerNameLength);
                $headerValue = substr($headerLine, $headerNameLength + 1);

                $cacheHeaders[$headerName] = trim($headerValue);
            }

            // Support resource identifiers
            $cacheValidators = [
                "If-None-Match" => "ETag",
                "if-Modified-Since" => "Last-Modified"
            ];

            foreach ($cacheValidators as $browserHeader => $cacheHeaderCompare) {
                if ($request->hasHeader($browserHeader) && array_key_exists($cacheHeaderCompare, $cacheHeaders)) {
                    $leftValidationToken = $request->getHeader($browserHeader)[0];
                    $rightValidationToken = $cacheHeaders[$cacheHeaderCompare];

                    if ($leftValidationToken === $rightValidationToken) {
                        return $response->withStatus(304);
                    }
                }
            }

            $cacheStream = new \Laminas\Diactoros\Stream(fopen($cacheEntry, "rb"));

            // ...and creates a new response
            return new \Laminas\Diactoros\Response(
                $cacheStream,
                200,
                $cacheHeaders
            );
        }

        // Call the next item in the middleware.
        $response = $next($request, $response);

        // Save cache.
        if ($response->getStatusCode() === 200 && $response->hasHeader("Content-Type"))  {
            // Cache only if:
            // * HTTP Status Code = 200
            // * Content-Type is cacheable
            $cacheMimeList = [
                "text/javascript",
                "application/javascript",
                "image/jpeg",
                "audio/mpeg",
                "text/css",
                "image/gif",
                "image/vnd.microsoft.icon",
                "image/x-icon",
            ];

            // Cache a list of headers from source server.            
            $cacheResponseHeaders = [];
            $headerList = [
                "Content-Type",
                "Content-Encoding",
                "Date",
                "ETag",
                "Last-Modified",
            ];

            foreach ($headerList as $headerName) {
                if ($response->hasHeader($headerName)) {
                    // Example: Content-Type: text/html; charset=utf-8
                    $cacheResponseHeaderValue = implode(";", $response->getHeader($headerName));
                    $cacheResponseHeaders[] = "${headerName}: ${cacheResponseHeaderValue}";
                }
            }

            $responseMime = $response->getHeader("Content-Type")[0];

            if (in_array($responseMime, $cacheMimeList)) {
                // We have a valid response to cache it.
                file_put_contents($cacheEntry, $response->getBody());

                // Save mime type for future downloads.
                file_put_contents(
                    $cacheEntryHeaders,
                    implode("\n", $cacheResponseHeaders)
                );
            }

            // Resource was fetched from the origin server
            return $response->withHeader("X-Cache", "MISS");
        }

        return $response;
    })
    ->to('https://example.com');