0

Atomic locks — Cache::lock() for distributed mutex

Advanced5 min read·lv-20-004
interviewperformance

Concept

HTTP response caching is separate from application-level caching. It controls browser and CDN caching of HTTP responses, reducing round-trips to the server entirely.

Cache-Control header: The primary mechanism. Directives:

  • public: Can be cached by browsers and CDNs.
  • private: Only cached by the browser (user-specific data).
  • no-store: Never cache (sensitive data).
  • no-cache: Cache but must revalidate before use.
  • max-age=N: Cache for N seconds.
  • s-maxage=N: CDN cache duration (overrides max-age for shared caches).
  • immutable: Tell the browser the resource will never change (for versioned assets).

ETags: Server generates a hash of the response content. Browser sends If-None-Match: "hash" on subsequent requests. If the hash matches, server returns 304 Not Modified (no body) — saves bandwidth.

Last-Modified: Last-Modified: Wed, 01 Jan 2025 00:00:00 GMT. Browser sends If-Modified-Since. Server returns 304 if unchanged.

Laravel response methods:

  • $response->setEtag(string $etag).
  • $response->setLastModified(\DateTime $date).
  • $response->setMaxAge(int $seconds).
  • $response->setPublic() / $response->setPrivate().
  • $response->isNotModified(Request $request): Check conditional headers and return 304 if unchanged.

CDN caching: CDNs (Cloudflare, CloudFront) cache public responses. Set Cache-Control: public, max-age=86400, s-maxage=3600 to cache at CDN for 1 hour regardless of browser cache.

Code Example

php
<?php
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // Public resource — suitable for CDN + browser caching
    public function show(Request $request, Product $product): \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
    {
        $etag = md5($product->updated_at->timestamp . $product->id);
        $lastModified = $product->updated_at;

        $response = response()->json(new \App\Http\Resources\ProductResource($product));

        $response->setEtag($etag);
        $response->setLastModified($lastModified);
        $response->setPublic();
        $response->setMaxAge(300);    // Browser: cache 5 minutes
        $response->headers->set('s-maxage', '3600'); // CDN: cache 1 hour

        if ($response->isNotModified($request)) {
            return $response->setStatusCode(304); // No body — saves bandwidth
        }

        return $response;
    }

    // User-specific — private cache
    public function profile(Request $request): \Illuminate\Http\JsonResponse
    {
        $user = $request->user();
        $response = response()->json(new \App\Http\Resources\UserResource($user));

        $response->setPrivate();
        $response->setMaxAge(60); // Browser only, 1 minute

        $response->setEtag(md5($user->updated_at->timestamp));
        if ($response->isNotModified($request)) {
            return $response->setStatusCode(304);
        }

        return $response;
    }
}

// Middleware to add cache headers globally
class AddCacheControlHeaders
{
    public function handle(Request $request, \Closure $next): mixed
    {
        $response = $next($request);

        // Authenticated routes — never cache
        if (auth()->check()) {
            $response->headers->set('Cache-Control', 'private, no-store');
            return $response;
        }

        // Guest GET requests — allow public cache
        if ($request->isMethod('GET')) {
            $response->headers->set('Cache-Control', 'public, max-age=60, s-maxage=300');
        }

        return $response;
    }
}