Try setting the expiry date to sometime in the future that isn't 15 minutes
$app = JFactory::getApplication();
$app->setHeader('Expires', gmdate('D, d M Y H:i:s', time() + 60) . ' GMT');
Optionally, also set the application to enable browser cache
$app->allowCache(true);
We should get a HTTP header with Expires set to 1 minute in the future, somehow.
If we don't set allowCache(true) then we get a date in 2005. This would be OK if allowCache allowed our value of Expires. Instead, it is hard-wired to set an expiry date of 15 minutes into the future (900s)
Joomla 3.8.6
I propose adding a function setExpiryPeriod to WebApplication which sets the number of seconds into the future the HTTP expiry field can be set.
Labels |
Added:
?
|
Status | New | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2018-03-19 13:30:58 |
Closed_By | ⇒ | waveywhite |
Status | Closed | ⇒ | New |
Closed_Date | 2018-03-19 13:30:58 | ⇒ | |
Closed_By | waveywhite | ⇒ |
Status | New | ⇒ | Discussion |
Category | ⇒ | Libraries |
One minute is just an example. In our case we have a large busy site with dynamic AJAX resources which we would like the browser to cache between page loads, so 2-5 minutes can make a huge difference.
I'll check out setting the expires header in this way, so I guess that this is not so much an issue as a feature request for a nice-to-have. In which case, having a DateTime sounds sensible and could be more usable than a count of seconds.
One minute is just an example. In our case we have a large busy site with dynamic AJAX resources which we would like the browser to cache between page loads, so 2-5 minutes can make a huge difference.
Ahh, sorry. Read that at first as you expected the framework to default to one minute. Apparently I need my Monday morning nap before commenting on anymore GitHub issues
Here is how to do it
[EDIT]
If you call
JResponse::allowCache(true);
The above will prevent 2 headers from being added by WebApplication::respond():
$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);
// HTTP 1.0
$this->setHeader('Pragma', 'no-cache');
You still get the expires header added as you describe but that is ok, (see below)
since you can just use 'Cache-Control' + max-age to take priority over expires header (it is more flexible and the newer / modern way of doing it)
So the final code to use is
// Prevent Joomla WebApplication::respond(): from setting 'Cache-Control' and 'Pragma'
JFactory::getApplication()->allowCache(true);
// CONTROL INTERMEDIARY CACHES (PROXY, ETC)
// Instruct them not cache (potentially) private resource (here you can add more code to decide)
$cacheControl = JFactory::getUser()->id ? 'private' : 'public';
// Set MAX-AGE, to allow modern browsers to cache the page, despite expires header in the past
$cacheControl .= ', max-age=300';
JResponse::setHeader('Cache-Control', $cacheControl );
// Make sure no legacy proxies do any caching (just in case)
// or just do not add this at all, Cache-Control + max-age take priority
// or as you said you cannot override it (if so just skip this)
JResponse::setHeader('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT');
Except use JFactory::getApplication()->setHeader()
instead of the deprecated JResponse
class
I do not say that
JFactory::getApplication()->allowCache(true);
should not take more parameters to allow more control over what it does, maybe it should be made more flexible
allowCache()
is a poorly defined API endpoint and should be broken up. If you don't pass a param it acts as a getter, if you do pass a param it acts as a setter. Since it's toggling a boolean flag I wouldn't complicate that method anymore. If you want to add an endpoint that creates more control over the cache behavior feel free to propose it, but we shouldn't be adding an overly complex method to do all the things.
OK. It turns out the source of my woes is in fact \Joomla\CMS\Document\JsonDocument. It's overwriting the caching parameter in the application and I can't set it. It ignores the cache parameter passed in to render.
public function render($cache = false, $params = array())
{
$app = \JFactory::getApplication();
$app->allowCache(false);
if ($this->_mime == 'application/json')
{
// Browser other than Internet Explorer < 10
$app->setHeader('Content-Disposition', 'attachment; filename="' . $this->getName() . '.json"', true);
}
parent::render();
return $this->getBuffer();
}
So the problems are:
When is Joomla 4 being released? ;-)
I'm going to have to give up on solving this in my application, I need a fix to Joomla! I've been trying @ggppdk's suggestion but it doesn't work in this case.
I set Cache-Control to max-age=300 in my JSON view (document->type() is json), by using the Application api, as suggested by @mbabker
error_log in WebApplication::setHeader shows:
params = name=Cache-Control value=max-age=300 single= replace=1
headers = [{"name":"Cache-Control","value":"max-age=300"}]
JsonDocument then sets $app->allowCache(false) as a hard-wired value
shows:
params = name=Pragma value=no-cache single= replace=
headers = [
{"name":"Cache-Control","value":"max-age=300"},{"name":"Content-Type","value":"text\\/plain; charset=utf-8"},
{"name":"Expires","value":"Wed, 17 Aug 2005 00:00:00 GMT"},
{"name":"Last-Modified","value":"Fri, 23 Mar 2018 10:01:10 GMT"},{"name":"Cache-Control","value":"no-store, no-cache, must-revalidate, post-check=0, pre-check=0"},
{"name":"Pragma","value":"no-cache"}]
Resulting headers are:
Cache-Control: max-age=300, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Date: Fri, 23 Mar 2018 10:01:10 GMT
Expires: Wed, 17 Aug 2005 00:00:00 GMT
Last-Modified: Fri, 23 Mar 2018 10:01:10 GMT
Pragma: no-cache
So, Cache-Control header is treated as a multi-value header and the combination of the values disables caching.
Can we just take the $app->allCache(false) call out of JsonDocument::render()? It defaults to false in WebApplication, anyway.
-David.
Title |
|
Yes my suggestion will not work in case of JSON document as you said
\Joomla\CMS\Document\JsonDocument.
has code inside render() that will force cache to off
public function render($cache = false, $params = array())
{
$app = \JFactory::getApplication();
$app->allowCache(false);
I don't see why it needs to be there (and if someone can give a valid reason then it can stay).
I think the idea of adding it there was to guarantee that JSON responses are not cached,
Removing it now, will cause a change in default behaviour that will break websites that have code / configuration to enable browser caching and do it without checking the document type but expect that JSON responses are not cached
Only by extending API we could keep "B/C" behaviour
function allowCache($allow, $force_types=array())
thus any call like
$app->allowCache(false);
aka called without specifying $force_types
, cannot override the call that specified $force_types
the above is ugly ? probably yes, but i am talking about B/C and about breaking some websites ...
Adding method parameters comes with the technical cost of new B/C constraints for the future. Please don't do that. Even if it's temporary it's bad. And some will argue adding optional parameters to existing methods is a B/C break too (actually on this platform we've gone so far as to say adding methods to classes breaks B/C, don't get me started).
You do realize there is no place in core calling allowCache(true)
, right? Meaning the only way this breaks is if some extension is blindly calling it. Which, there probably is. Because plugins do such a terrible job at checking context that it's almost easier right now to rip out everything that's not supporting HTML responses than it is to add features or make changes for non-HTML responses.
Labels |
Added:
J3 Issue
|
Yes, this is the same.
I'm not keen on the additional parameter but it makes sense, I think, to remove the erroneous call in JsonDocument::render. I see the potential issue with plugins but it will pose a performance issue rather than a behaviour issue in that scenario, because the cache would then be disabled. If it was the other way round it would change the behaviour.
I was going to suggest this as a work around
and i see it is already mentioned in the other issue
For now, in my module I did an ugly workaround by just returning the headers and data and exiting prematurely. However, I would prefer to not skip the remaining Joomla stuff that would be happening, like some event triggers being handled by some plugins.
i mean as temporarily solution until the Joomla version that this addressed
call php header(...) directly and exit when your output is done ...
I really hate that workaround. I'd rather us fix the freakin' framework than keep saying "bypass and avoid it".
All saying "use this workaround" is doing is basically saying "m'eh, we'll fix this eventually, if you're lucky".
Status | Discussion | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2020-03-12 20:40:01 |
Closed_By | ⇒ | joomla-cms-bot |
Status | Closed | ⇒ | Duplicate Report |
Closed_By | joomla-cms-bot | ⇒ | jwaisner |
Set to "closed" on behalf of @jwaisner by The JTracker Application at issues.joomla.org/joomla-cms/19941
Duplicate of #19105
Why one minute? At that point you might as well not have a cache directive.
You can manually set an Expires header elsewhere, the code in the WebApplication setting the 15 minute header isn't passing the
$replace
flag tosetHeader
meaning if an Expires header is already set it won't be overwritten.Honestly, I'd rather this be a
DateTime
object versus an arbitrary number of seconds. Otherwise the Expires header can and will change because the response is sent on a different second, I'd suggest we should be able to set a consistent expiry time and the best way to do that is with a date object. The arbitrarytime() + 900
code works as a generic default but lacks flexibility.