statamic / cms

The core Laravel CMS Composer package
https://statamic.com
Other
3.92k stars 523 forks source link

Static cache redirection #10907

Open artisanbarista opened 3 days ago

artisanbarista commented 3 days ago

Bug description

Static cache is set while redirecting.

How to reproduce

Lets say i have 2 pages with STATAMIC_STATIC_CACHING_STRATEGY=half

https://www.test.com/party https://www.test.com/eu/party

I have a middleware that in case u visit https://www.test.com/party from a european ip it will redirect to https://www.test.com/eu/party

return redirect('/eu/party', 302, [
    'X-Redirected-By' => 'Party Redirecion',
    'X-Redirected-From' => '/party',
]);

Once this redirection is executed and user lands https://www.test.com/eu/party there is a button in that page to say this is not available for european users and if you landed here by mistake to click on a button to go to https://www.test.com/party?mistake=true

Unfortunattely this does not work correctly because statamic static cache allready cached https://www.test.com/eu/party while redirecting and when the user clicks on the button to go to https://www.test.com/party?mistake=true it continue showing https://www.test.com/eu/party static cached page.

As a fix for this problem I have added this

if ($event->response->getStatusCode() === 302) {
    return;
}

https://github.com/statamic/cms/blob/3956ab1343eef8b20d89c5281160c05c63f279c0/src/StaticCaching/Cachers/ApplicationCacher.php#L51

        Event::listen(ResponsePrepared::class, function (ResponsePrepared $event) use ($key, $value) {
            $headers = collect($event->response->headers->all())
                ->reject(fn ($value, $key) => in_array($key, ['date', 'x-powered-by', 'cache-control', 'expires', 'set-cookie']))
                ->all();

              if ($event->response->getStatusCode() === 302) {
                  return;
              }

            $cacheValue = [
                'content' => $value,
                'headers' => $headers,
                'status' => $event->response->getStatusCode(),
            ];

            $this->getDefaultExpiration()
                ? $this->cache->put($key, $cacheValue, now()->addMinutes($this->getDefaultExpiration()))
                : $this->cache->forever($key, $cacheValue);
        });

to prevent any 302 code responses not to be statically cached and it works fine.

I am not sure if this is a bug from my code or statamic itself. but it should be nice if there was an option in the config to exclude certain html status codes from static cache via statamic/static_cache.php configuration.

Logs

No response

Environment

Application Name: Party
Laravel Version: 10.48.22
PHP Version: 8.3.11
Composer Version: 2.7.9
Environment: local
Debug Mode: ENABLED
URL: party.com.test
Maintenance Mode: OFF

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: redis
Database: mysql
Logs: stack / single, bugsnag
Mail: log
Queue: sync
Session: file

Statamic
Addons: 2
Sites: 15 (English, Deutsch, 中文简体, and 12 more)
Stache Watcher: Disabled
Static Caching: half
Version: 5.25.0 PRO

Statamic Addons
jacksleight/statamic-bard-mutator: 3.0.0
jacksleight/statamic-bard-texstyle: 3.3.0

Installation

Existing Laravel app

Additional details

No response

duncanmcclean commented 3 days ago

What does your config/statamic/static_caching.php config file look like?

artisanbarista commented 3 days ago

`<?php

return [

/*
|--------------------------------------------------------------------------
| Active Static Caching Strategy
|--------------------------------------------------------------------------
|
| To enable Static Caching, you should choose a strategy from the ones
| you have defined below. Leave this null to disable static caching.
|
*/

'strategy' => env('STATAMIC_STATIC_CACHING_STRATEGY', 'half'),

/*
|--------------------------------------------------------------------------
| Caching Strategies
|--------------------------------------------------------------------------
|
| Here you may define all of the static caching strategies for your
| application as well as their drivers.
|
| Supported drivers: "application", "file"
|
*/

'strategies' => [

    'half' => [
        'driver' => 'application',
        'expiry' => null,
        'warm_concurrency' => 7,
    ],

    'full' => [
        'driver' => 'file',
        'path' => public_path('static'),
        'lock_hold_length' => 0,
        'permissions' => [
            'directory' => 0755,
            'file' => 0644,
        ],
    ],

],

/*
|--------------------------------------------------------------------------
| Exclusions
|--------------------------------------------------------------------------
|
| Here you may define a list of URLs to be excluded from static
| caching. You may want to exclude URLs containing dynamic
| elements like contact forms, or shopping carts.
|
*/

'exclude' => [

    'class' => null,

    'urls' => [
        //
    ],

],

/*
|--------------------------------------------------------------------------
| Invalidation Rules
|--------------------------------------------------------------------------
|
| Here you may define the rules that trigger when and how content would be
| flushed from the static cache. See the documentation for more details.
| If a custom class is not defined, the default invalidator is used.
|
| https://statamic.dev/static-caching
|
*/

'invalidation' => [

    'class' => null,

    'rules' => [
        //
    ],

],

/*
|--------------------------------------------------------------------------
| Ignoring Query Strings
|--------------------------------------------------------------------------
|
| Statamic will cache pages of the same URL but with different query
| parameters separately. This is useful for pages with pagination.
| If you'd like to ignore the query strings, you may do so.
|
*/

'ignore_query_strings' => false,

'allowed_query_strings' => [
    'mistake'
],

'disallowed_query_strings' => [
    //
],

/*
|--------------------------------------------------------------------------
| Replacers
|--------------------------------------------------------------------------
|
| Here you may define replacers that dynamically replace content within
| the response. Each replacer must implement the Replacer interface.
|
*/

'replacers' => [
    \Statamic\StaticCaching\Replacers\CsrfTokenReplacer::class,
    \Statamic\StaticCaching\Replacers\NoCacheReplacer::class,
],

/*
|--------------------------------------------------------------------------
| Warm Queue
|--------------------------------------------------------------------------
|
| Here you may define the name of the queue that requests will be pushed
| onto when warming the static cache using the static:warm command.
|
*/

'warm_queue' => null,

]; `

artisanbarista commented 3 days ago

I have also notice that there is also this issue here that seems to be similar to mine but instead with 404 error

duncanmcclean commented 14 hours ago

How are you registering your custom middleware that handles the redirect?

I suspect it's being loaded after our static caching middleware returns the cached response, so the request never reaches your middleware.