Part of my app provides a file to be downloaded using the redirect() method. I have found that Chrome (and not Firefox or IE, weirdly) is caching this file so that the same version gets downloaded even if it has changed server-side. I gather that there is a way to tell a browser not to cache a file, e.g. like this in the HTML, or by adding something to the HTTP header. I could probably figure those out in a lower-level web framework, but I don't know how to get at the header in Play!, and the HTML option won't work because it's not an HTML file.
It seems like there's always a clever and simple way to do common tasks in Play!, so is there a clever and simple way to prevent caching in a controller?
Thanks!
Edit:
Matt points me to the http.cacheControl setting, which controls caching for the entire site. While this would work, I have no problem with most of the site being cached, especially the CSS etc. If possible I'd like to control caching for one URL at a time (the one pointing to the downloading file in this case). It's not exactly going to be a high-traffic site, so this is just academic interest talking.
Ideally, I'd like to do something like:
public static void downloadFile(String url) {
response.setCaching(false); // This is the method I'm looking for
redirect(url); // Send the response
}
Play framework response object has a setHeader method. You can add the headers you want like this, for example:
response.setHeader("Cache-Control", "no-cache");
I haven't tested it, but it looks like the http.cacheControl configuration setting might work.
http.cacheControl
HTTP Response headers control for static files: sets the default max-age in seconds, telling the user’s browser how long it should cache the page. This is only read in prod mode, in dev mode the cache is disabled. For example, to send no-cache:
http.cacheControl=0
Default: 3600 – set cache expiry to one hour.
It is actually this:
response().setHeader("Cache-Control", "no-cache");
Tommi's answer is ok, but to make sure it works in every browser, use:
response().setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, s-maxage=0"); // HTTP 1.1.
On play currently 2.5.x to 2.8.x
you can set cache life of both assets folder or assets file in configuration.
For folder-
play.assets.cache."/public/stylesheets/"="max-age=100"
play.assets.cache."/public/javascripts/"="max-age=200"
for specific file -
play.assets.cache."/public/stylesheets/bootstrap.min.css"="max-age=3600"
--- documentation
https://www.playframework.com/documentation/2.8.x/AssetsOverview
Related
I am starting on web-security and I have to control the cache on the portal, this portal has many urls. I understand that I need to set the header with this:
response.setHeader("Cache-Control","no-cache,no-store,must-revalidate");
response.setHeader("Pragma", "no-cache");
But my question is: The code above is valid for all the urls that I want to controling (You know the cache) or how I set this attribute for all the urls or for a url in specific?.
Assuming you have access to both request and response objects. You can use one of the following methods of HttpRequest object in your control method to set those response parameters
- getPathInfo()
- getRequestURL()
- getRequestURI()
I mean something like this
if(request.getRequestURL().equals("http://someurl"))
{
//do your stuff
}
Put this code in a web filter and map the filter to all the urls where you want to disable caching.
I have the requirement that the end user should not be able to go back to the restricted page after logout/sign out. But currently the end user is able to do that by the browser back button, visiting browser history or even by re-entering the URL in browser's address bar.
Basically, I want that the end user should not be able to access the restricted page in any way after sign out. How can I achieve this the best? Can I disable the back button with JavaScript?
You can and should not disable the browser back button or history. That's bad for user experience. There are JavaScript hacks, but they are not reliable and will also not work when the client has JS disabled.
Your concrete problem is that the requested page is been loaded from the browser cache instead of straight from the server. This is essentially harmless, but indeed confusing to the enduser, because s/he incorrectly thinks that it's really coming from the server.
You just need to instruct the browser to not cache all the restricted JSP pages (and thus not only the logout page/action itself!). This way the browser is forced to request the page from the server instead of from the cache and hence all login checks on the server will be executed. You can do this using a Filter which sets the necessary response headers in the doFilter() method:
#WebFilter
public class NoCacheFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
chain.doFilter(req, res);
}
// ...
}
Map this Filter on an url-pattern of interest, for example *.jsp.
#WebFilter("*.jsp")
Or if you want to put this restriction on secured pages only, then you should specify an URL pattern which covers all those secured pages. For example, when they are all in the folder /app, then you need to specify the URL pattern of /app/*.
#WebFilter("/app/*")
Even more, you can do this job in the same Filter as where you're checking the presence of the logged-in user.
Don't forget to clear browser cache before testing! ;)
See also:
Authentication filter and servlet for login
How to control web page caching, across all browsers?
*.jsp in Url Pattern won't work if you forward a page. Try to include your servlet too.. that will make your application secure from this back button problem.
The simplest way to do it without disabling the browser back buton is by adding this code to the page_load event for the page that you don't want the user to go back to after logging out:
if (!IsPostBack)
{
if (Session["userId"] == null)
{
Response.Redirect("Login.aspx");
}
else
{
Response.ClearHeaders();
Response.ClearContent();
Response.Clear();
Session.Abandon();
Session.Remove("\\w+");
Response.AddHeader("Cache-Control", "no-cache, no-store, max-age = 0, must-revalidate");
Response.AddHeader("Pragma", "no-cache");
Response.AddHeader("Expires", "0");
}
}
The correct way to do this is to add the
Vary: Cookie
header on secured pages. When the user logs out, clear their session cookie. Then, when they navigate back after logging out, the browser cache will miss. This also has the benefit of not completely defeating caching.
You can try telling the browser not to cache the homepage (using the appropriate headers - Expires, Cache-Control, Pragma). But it is not guaranteed to work. What you can do, is make an ajax call to the server on page load to check if the user is logged, and if not - redirect.
An alternative to implementing a Filter is to set a 'no-cache' filter on all the secured JSPs, or on all paths. This may be a good idea if the application is small, and if you would like to customize this property for a specific pages only. We can add the following Java snippet on every secured JSP that should not be cached:
<%
response.addHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setDateHeader("Expires", 0);
%>
If not on JSP, this could also be used in Controllers where routing is defined and set the headers for the 'HttpServletResponse' object.
For me the problem was , I didn't want to set headers on all pages , so I just set this header on page when logout is clicked and it clears everything related to the site :)
// Removes all site data
response.setHeader ("Clear-Site-Data", "\"cache\"");
Please read more about it over here :
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data
In my web app some pages, I have two scenarios for browser cache
Scenario 1 :-
I want to server from browser cache if not modified at server side. For example :-
User issues the request for all employees
Response returns 10 employees
User issues the request for all employees again
Expectation is this time it should be served from browser cache
User creates one more employee
User issues the request for all employees again
Expectation is this time it should be served latest from server instead of browser cache
I am planning to use below header
response.setHeader("Cache-Control", "no-cache");
As
no-cache is not instructing the browser or proxies about whether or
not to cache the content. It just tells the browser and proxies to
validate the cache content with the server before using it
Scenario 2 :-
But for some sensitive pages i don't want to cache at all, i am planning it to use below header
response.setHeader("Cache-Control", "no-store");
But some articles safe to use below header to make it work for all browsers. So i am going to use below
response.setHeader("Cache-Control", "no-cache, no-store");
Is my proposed implementation correct ?
For Scenario #1 you indeed need to set Cache-Control to no-cache (or set a max-age for even a better scalability but in this case you won't have necessary the latest value) but you also need to use the HTTP header ETag in order to allow the browser to check if the data content has changed such that the browser will be able to know if the cache entry can be reused or not.
For Scenario #2 you need to set Cache-Control to no-store to prevent the browser to cache the data as it is the standard way but indeed no-cache, no-store will help to work on old browsers if you need to support them too.
I Am serving an authenticated image using django. The image is behind a view which require login, and in the end I have to check more things than just the authentication.
Because of a reason to complicated to explain here, I cannot use the real url to the image, but I Am serving it with a custom url leading to the authenticated view.
From java the image must be reachable, to save or display. For this part I use Apache httpclient.
In Apacahe I tried a lot of things (every example and combination of examples...) but can't seem to get it working.
For other parts of the webapp I use django-rest-framwork, which I succesfully connected to from java (and c and curl).
I use the login_reuired decorator in django, which makes the attempt to get to the url redirect to a login page first.
Trying the link and the login in a webviewer, I see the 200 code (OK) in the server console.
Trying the link with the httpclient, I get a 302 Found in the console.... (looking up 302, it means a redirect..)
this is what I do in django:
in urls.py:
url(r'^photolink/(?P<filename>.*)$', 'myapp.views.photolink',name='photolink'),
in views.py:
import mimetypes
import os
#login_required
def photolink(request, filename):
# from the filename I get the image object, for this question not interesting
# there is a good reason for this complicated way to reach a photo, but not the point here
filename_photo = some_image_object.url
base_filename=os.path.basename(filename_photo)
# than this is the real path and filename to the photo:
path_filename=os.path.join(settings.MEDIA_ROOT,'photos',mac,base_filename)
mime = mimetypes.guess_type(filename_photot)[0]
logger.debug("mimetype response = %s" % mime)
image_data = open(path_filename, 'rb').read()
return HttpResponse(image_data, mimetype=mime)
by the way, if i get this working i need another decorator to pass some other tests....
but i first need to get this thing working....
for now it's not a secured url.... plain http.
in java i tried a lot of things... using apache's httpclient 4.2.1
proxy, cookies, authentication negociation, with follow redirects... and so on...
Am I overlooking some basic thing here?...
it seems the login of the website client is not suitable for automated login...
so the problem can be in my code in django....or in the java code....
In the end the problem was, using HTTP authorization.
Which is not by default used in the login_required decorator.
adding a custom decorator that checks for HTTP authorization did the trick:
see this example: http://djangosnippets.org/snippets/243/
I can't find information anywhere regarding this HTTP Header: PAGE_CACHE_FILTER_STATE.
When I try to access my RSS feed from a browser, this header has the value of NoCacheRequest, but when I access it from my Java application (URL.openConnection()), I've noticed that it gets set to FromCacheRequest and my RSS doesn't appear to update.
So I have two questions:
What is this HTTP header?
How can I make PAGE_CACHE_FILTER_STATE: NoCacheRequest for all requests?
I've never heard about nor seen PAGE_CACHE_FILTER_STATE before either, so I can't help you out with the actual specifications for it. It looks like a custom header telling you whether a cached version of the content was used or not.
To avoid caching, you could try programmatically adding something different to the URL each time. For example, you might add a random number:
http://www.example.com/feed.rss?no_cache=564482
http://www.example.com/feed.rss?no_cache=984637
You should also try sending the Pragma: no-cache and Cache-Control: no-cache HTTP headers when you request the RSS feed.