-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4104 from ushahidi/4073-cache-control-middle
feat(cache-control): add middleware for setting cache-control header [WIP]
- Loading branch information
Showing
9 changed files
with
297 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
<?php | ||
|
||
namespace Ushahidi\App\Http\Middleware; | ||
|
||
use Closure; | ||
use Illuminate\Support\Carbon; | ||
use Illuminate\Support\Facades\Log; | ||
use Illuminate\Contracts\Auth\Factory as Auth; | ||
|
||
class SetCacheHeadersIfAuth | ||
{ | ||
/** | ||
* The authentication guard factory instance. | ||
* | ||
* @var \Illuminate\Contracts\Auth\Factory | ||
*/ | ||
protected $auth; | ||
|
||
/** | ||
* Create a new middleware instance. | ||
* | ||
* @param \Illuminate\Contracts\Auth\Factory $auth | ||
* @return void | ||
*/ | ||
public function __construct(Auth $auth) | ||
{ | ||
$this->auth = $auth; | ||
} | ||
|
||
/** | ||
* Check if configuration enables caching for this route. | ||
* Return resolved configuration or null if not enabled. | ||
*/ | ||
protected function checkConfig($route_level) | ||
{ | ||
// caching levels that we recognize | ||
$levels = [ 'off', 'minimal' ]; | ||
|
||
// fetch config | ||
$cfg = [ | ||
'level' => config('routes.cache_control.level'), | ||
'max_age' => config('routes.cache_control.max_age'), | ||
'private_only' => config('routes.cache_control.private_only'), | ||
]; | ||
|
||
// translate level tags to numbers | ||
$cfg_level_n = array_search($cfg['level'], $levels); | ||
$r_level_n = array_search($route_level, $levels); | ||
if ($cfg_level_n === false || $r_level_n === false) { | ||
// there's some misconfiguration ... | ||
if ($cfg_level_n === false) { | ||
Log::warn('Unrecognized cache control level in config', [$cfg['level']]); | ||
} else { | ||
Log::warn('Unrecognized cache control level in route config', [$route_level]); | ||
} | ||
// so don't indicate caching | ||
return null; | ||
} | ||
|
||
if ($cfg_level_n >= $r_level_n) { | ||
return $cfg; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
/** | ||
* Add cache related HTTP headers. | ||
* | ||
* @param \Illuminate\Http\Request $request | ||
* @param \Closure $next | ||
* @param string $level cache level assigned to the route, specify the minimum | ||
* level of caching settings that should activate caching | ||
* behavior for this route. | ||
* See config/api.php for the defined levels | ||
* @param string $ifAuth authentication guard that should be satisfied | ||
* @param string|array $options cache options. Special presets: | ||
* 'preset/dont-cache' -> no-store | ||
* 'preset/default' -> default cache settings as per config | ||
* @return \Symfony\Component\HttpFoundation\Response | ||
* | ||
* @throws \InvalidArgumentException | ||
*/ | ||
public function handle($request, Closure $next, $level, $ifAuth = null, $options = []) | ||
{ | ||
$response = $next($request); | ||
|
||
// If this is not cacheable stuff, bail out | ||
if (! $request->isMethodCacheable() || ! $response->getContent()) { | ||
return $response; | ||
} | ||
|
||
// Check config and wether it enables caching | ||
$cfg = $this->checkConfig($level); | ||
if ($cfg == null) { | ||
return $response; | ||
} | ||
|
||
// If the cache settings are guarded by auth, check if the auth satisfies | ||
if ($ifAuth != null) { | ||
if ($this->auth->guard($ifAuth)->guest()) { | ||
return $response; | ||
} | ||
} | ||
|
||
// Check if options is a preset | ||
if (is_string($options) && strpos(strtolower($options), 'preset/') === 0) { | ||
$preset = strtolower(explode('/', $options)[1] ?? ""); | ||
if ($preset === 'dont-cache') { | ||
$options = 'no_store'; | ||
} elseif ($preset === 'default') { | ||
$viz = $cfg['private_only'] ? 'private' : 'public'; | ||
$maxage = $cfg['max_age']; | ||
$options = [ $viz => true, 'max_age' => "${maxage}" ]; | ||
} else { | ||
// Unrecognized preset , don't cache | ||
Log::warn('Unrecognized cache options preset', [$preset]); | ||
return $response; | ||
} | ||
} | ||
|
||
if (is_string($options)) { | ||
$options = $this->parseOptions($options); | ||
} | ||
|
||
if (isset($options['etag']) && $options['etag'] === true) { | ||
$options['etag'] = md5($response->getContent()); | ||
} | ||
|
||
if (isset($options['last_modified'])) { | ||
if (is_numeric($options['last_modified'])) { | ||
$options['last_modified'] = Carbon::createFromTimestamp($options['last_modified']); | ||
} else { | ||
$options['last_modified'] = Carbon::parse($options['last_modified']); | ||
} | ||
} | ||
|
||
$response->headers->remove('cache-control'); | ||
// Still not available in symfony's setCache() method | ||
if (isset($options['no_cache'])) { | ||
$response->headers->set('Cache-Control', 'no-cache'); | ||
unset($options['no_cache']); | ||
} elseif (isset($options['no_store'])) { | ||
$response->headers->set('Cache-Control', 'no-store'); | ||
unset($options['no_store']); | ||
} | ||
$response->setCache($options); | ||
$response->setVary('Authorization', false); | ||
$response->isNotModified($request); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Parse the given header options. | ||
* | ||
* @param string $options | ||
* @return array | ||
*/ | ||
protected function parseOptions($options) | ||
{ | ||
return collect(explode(';', $options))->mapWithKeys(function ($option) { | ||
$data = explode('=', $option, 2); | ||
|
||
return [$data[0] => $data[1] ?? true]; | ||
})->all(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
return [ | ||
|
||
// Configure cacheability of responses by browsers and intermediate caches | ||
'cache_control' => [ | ||
/* | ||
| Preset level of cacheability. | ||
| | ||
| This is a coarse-grained setting to indicate which of all the different | ||
| api endpoints should have cacheable responses. | ||
| | ||
| off - all content is marked as not cacheable | ||
| minimal - allow caching of only the most compute/data-intensive and least | ||
| consistency-critical content. This is usually the global | ||
| geojson endpoint (which may have a lot of points). On top of that, | ||
| caching is only enabled for guest users, not logged-in members. | ||
*/ | ||
'level' => env('CACHE_CONTROL_LEVEL', 'off'), | ||
|
||
/* | ||
| Longest max-age | ||
| | ||
| This value is applied to what the selected cache control level considers | ||
| the most cacheable responses. Less cacheable responses are assigned a | ||
| proportionally reduced value. | ||
| | ||
| Note that his has no effect if the cache level is set to 'off' | ||
*/ | ||
'max_age' => env('CACHE_CONTROL_MAX_AGE', 600), | ||
|
||
/* | ||
| Only private caching allowed | ||
| | ||
| Set this to true if you don't want responses to be cached in intermediate | ||
| proxies, but only in the end user's browsers instead. | ||
*/ | ||
'private_only' => env('CACHE_CONTROL_PRIVATE', false), | ||
|
||
] | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
/* | ||
* Utility function to add cache control middleware to a route | ||
* | ||
* It takes a single parameter to specify the minimum level of caching | ||
* settings that should activate caching behavior for this route. | ||
* See config/api.php for the defined levels | ||
*/ | ||
if (!function_exists('add_cache_control')) { | ||
|
||
function add_cache_control(string $route_level) | ||
{ | ||
return [ | ||
// These are parsed bottom first, so the default is to cache (if the config allows it) | ||
"cache.headers.ifAuth:{$route_level},api,preset/dont-cache", # applies to api-authenticated requests | ||
"cache.headers.ifAuth:{$route_level},,preset/default", | ||
]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters