diff --git a/config/static_caching.php b/config/static_caching.php index ec0ceba8fa7..e4a2fdd13b8 100644 --- a/config/static_caching.php +++ b/config/static_caching.php @@ -129,4 +129,19 @@ 'warm_queue' => null, + /* + |-------------------------------------------------------------------------- + | Shared Error Pages + |-------------------------------------------------------------------------- + | + | You may choose to share the same statically generated error page across + | all errors. For example, the first time a 404 is encountered it will + | be generated and cached, and then served for all subsequent 404s. + | + | This is only supported for half measure. + | + */ + + 'share_errors' => false, + ]; diff --git a/src/Exceptions/Concerns/RendersHttpExceptions.php b/src/Exceptions/Concerns/RendersHttpExceptions.php index 884711863fa..aa4528b5d3b 100644 --- a/src/Exceptions/Concerns/RendersHttpExceptions.php +++ b/src/Exceptions/Concerns/RendersHttpExceptions.php @@ -2,8 +2,12 @@ namespace Statamic\Exceptions\Concerns; +use Illuminate\Http\Request; +use Illuminate\Http\Response; use Statamic\Facades\Cascade; use Statamic\Statamic; +use Statamic\StaticCaching\Cacher; +use Statamic\StaticCaching\Cachers\ApplicationCacher; use Statamic\View\View; trait RendersHttpExceptions @@ -18,6 +22,10 @@ public function render() return response()->json(['message' => $this->getApiMessage()], $this->getStatusCode()); } + if ($cached = $this->getCachedError()) { + return $cached; + } + if (view()->exists('errors.'.$this->getStatusCode())) { return response($this->contents(), $this->getStatusCode()); } @@ -53,4 +61,25 @@ public function getApiMessage() { return $this->getMessage(); } + + private function getCachedError(): ?Response + { + $status = $this->getStatusCode(); + + if (! config('statamic.static_caching.share_errors')) { + return null; + } + + $cacher = app(Cacher::class); + + if (! $cacher instanceof ApplicationCacher) { + return null; + } + + $request = Request::createFrom(request())->fakeStaticCacheStatus($status); + + return $cacher->hasCachedPage($request) + ? $cacher->getCachedPage($request)->toResponse($request) + : null; + } } diff --git a/src/StaticCaching/Cachers/ApplicationCacher.php b/src/StaticCaching/Cachers/ApplicationCacher.php index da05b7ab01b..a583f577702 100644 --- a/src/StaticCaching/Cachers/ApplicationCacher.php +++ b/src/StaticCaching/Cachers/ApplicationCacher.php @@ -52,6 +52,7 @@ public function cachePage(Request $request, $content) $cacheValue = [ 'content' => $value, 'headers' => $headers, + 'status' => $event->response->getStatusCode(), ]; $this->getDefaultExpiration() @@ -79,7 +80,7 @@ public function getCachedPage(Request $request) { $cachedPage = $this->cached ?? $this->getFromCache($request); - return new Page($cachedPage['content'], $cachedPage['headers']); + return new Page($cachedPage['content'], $cachedPage['headers'], $cachedPage['status'] ?? 200); } private function getFromCache(Request $request) diff --git a/src/StaticCaching/Middleware/Cache.php b/src/StaticCaching/Middleware/Cache.php index 4f74c7de344..705e6ded4f0 100644 --- a/src/StaticCaching/Middleware/Cache.php +++ b/src/StaticCaching/Middleware/Cache.php @@ -10,6 +10,7 @@ use Statamic\Facades\File; use Statamic\Statamic; use Statamic\StaticCaching\Cacher; +use Statamic\StaticCaching\Cachers\ApplicationCacher; use Statamic\StaticCaching\Cachers\NullCacher; use Statamic\StaticCaching\NoCache\RegionNotFound; use Statamic\StaticCaching\NoCache\Session; @@ -63,6 +64,8 @@ public function handle($request, Closure $next) if ($this->shouldBeCached($request, $response)) { $lock->acquire(true); + $this->copyError($request, $response); + $this->makeReplacementsAndCacheResponse($request, $response); $this->nocache->write(); @@ -73,6 +76,21 @@ public function handle($request, Closure $next) return $response; } + private function copyError($request, $response) + { + $status = $response->getStatusCode(); + + if (! config('statamic.static_caching.share_errors')) { + return; + } + + $request = Request::createFrom($request)->fakeStaticCacheStatus($status); + + if (! $this->cacher->hasCachedPage($request)) { + $this->cacher->cachePage($request, $response); + } + } + private function attemptToGetCachedResponse($request) { if ($this->canBeCached($request) && $this->cacher->hasCachedPage($request)) { @@ -139,7 +157,9 @@ private function shouldBeCached($request, $response) return false; } - if ($response->getStatusCode() !== 200 || $response->getContent() == '') { + $statuses = $this->cacher instanceof ApplicationCacher ? [200, 404] : [200]; + + if (! in_array($response->getStatusCode(), $statuses) || $response->getContent() == '') { return false; } diff --git a/src/StaticCaching/Page.php b/src/StaticCaching/Page.php index ca804540f84..03473dc1f84 100644 --- a/src/StaticCaching/Page.php +++ b/src/StaticCaching/Page.php @@ -7,12 +7,15 @@ class Page implements Responsable { - public function __construct(public string $content, public array $headers = []) - { + public function __construct( + public string $content, + public array $headers = [], + public int $status = 200 + ) { } public function toResponse($request): Response { - return new Response($this->content, 200, $this->headers); + return new Response($this->content, $this->status, $this->headers); } } diff --git a/src/StaticCaching/ServiceProvider.php b/src/StaticCaching/ServiceProvider.php index fec06fad69b..6fad890af05 100644 --- a/src/StaticCaching/ServiceProvider.php +++ b/src/StaticCaching/ServiceProvider.php @@ -2,6 +2,7 @@ namespace Statamic\StaticCaching; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider as LaravelServiceProvider; @@ -78,5 +79,13 @@ public function boot() Blade::directive('nocache', function ($exp) { return 'handle('.$exp.', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>'; }); + + Request::macro('fakeStaticCacheStatus', function (int $status) { + $url = '/__shared-errors/'.$status; + $this->pathInfo = $url; + $this->requestUri = $url; + + return $this; + }); } } diff --git a/tests/StaticCaching/ApplicationCacherTest.php b/tests/StaticCaching/ApplicationCacherTest.php index 7ea579cdac0..6be2e434ba0 100644 --- a/tests/StaticCaching/ApplicationCacherTest.php +++ b/tests/StaticCaching/ApplicationCacherTest.php @@ -42,6 +42,7 @@ public function gets_cached_page() $cachedPage = $cacher->getCachedPage($request); $this->assertEquals('html content', $cachedPage->content); $this->assertEquals('application/html', $cachedPage->headers['Content-Type']); + $this->assertEquals(200, $cachedPage->status); } /** @test */