Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
restrict ETag handling to GET/HEAD and enforce user-specific caching
- Process only GET and HEAD methods to ensure middleware relevance and avoid unnecessary processing for non-cacheable HTTP methods.
- Add a check to determine if the response is cacheable, filtering out non-cacheable responses (e.g., those with 'no-store' directive or non-cacheable status codes).
- Default ETag generation now includes user-specific data (auth()->id()) to enforce personalized caching by default.
- Removed 'scope' and 'includeUser' logic for simplified and consistent caching behavior.
  • Loading branch information
eiresendez committed Dec 3, 2024
commit 8b327c8a016ed5674acda8cb68511a0679e9bd0e
33 changes: 22 additions & 11 deletions ProcessMaker/Http/Middleware/Etag/HandleEtag.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@ class HandleEtag
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next, ?string $scope = null): Response
public function handle(Request $request, Closure $next): Response
{
// Save original method and support HEAD requests.
$originalMethod = $request->getMethod();
if ($request->isMethod('HEAD')) {
$request->setMethod('GET');
// Process only GET and HEAD methods.
if (!$request->isMethod('GET') && !$request->isMethod('HEAD')) {
return $next($request);
}

// Handle response.
$response = $next($request);

// Determine if the ETag should include user-specific data.
$includeUser = $scope === 'user';
// Check if the response is cacheable.
if (!$this->isCacheableResponse($response)) {
return $response; // Skip ETag for non-cacheable responses.
}

// Generate ETag for the response.
$etag = EtagManager::getEtag($request, $response, $includeUser);
$etag = EtagManager::getEtag($request, $response);
if ($etag) {
// Set the ETag header.
$response->setEtag($etag);
Expand All @@ -43,9 +44,6 @@ public function handle(Request $request, Closure $next, ?string $scope = null):
}
}

// Restore original method and return the response.
$request->setMethod($originalMethod);

return $response;
}

Expand All @@ -56,4 +54,17 @@ private function stripWeakTags(string $etag): string
{
return str_replace('W/', '', $etag);
}

/**
* Determine if a response is cacheable.
*/
private function isCacheableResponse(Response $response): bool
{
$cacheableStatusCodes = [200, 203, 204, 206, 304];
$cacheControl = $response->headers->get('Cache-Control', '');

// Verify if the status code is cacheable and does not contain "no-store".
return in_array($response->getStatusCode(), $cacheableStatusCodes)
&& !str_contains($cacheControl, 'no-store');
}
}
18 changes: 7 additions & 11 deletions ProcessMaker/Http/Resources/Caching/EtagManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,22 @@ public static function etagGenerateUsing(?Closure $callback): void
}

/**
* Get ETag value for this request and response.
* Get the ETag value for this request and response.
*/
public static function getEtag(Request $request, Response $response, bool $includeUser = false): string
public static function getEtag(Request $request, Response $response): string
{
$etag = static::$etagGenerateCallback
? call_user_func(static::$etagGenerateCallback, $request, $response, $includeUser)
: static::defaultGetEtag($response, $includeUser);
? call_user_func(static::$etagGenerateCallback, $request, $response)
: static::defaultGetEtag($response);

return (string) Str::of($etag)->start('"')->finish('"');
}

/**
* Generate an ETag, optionally including user-specific data.
* Generate a default ETag, including user-specific data by default.
*/
private static function defaultGetEtag(Response $response, bool $includeUser = false): string
private static function defaultGetEtag(Response $response): string
{
if ($includeUser) {
return md5(auth()->id() . $response->getContent());
}

return md5($response->getContent());
return md5(auth()->id() . $response->getContent());
}
}