Firefox stored cached incomplete response - asp.net-mvc

I just found a partial response being cached as complete in one of our customer's machines, which rendered the whole website unusable. And I have absolutely no idea, what could possible have gone wrong there.
So what could have possibly gone wrong in the following setup?
On the server-side, we have an ASP.NET-application running. One IHttpHandler handles requests to javascript-files. It basically minifies the files as they are requested and writes the result on the response-stream. It does also log the length of the string being written to the Response-Stream:
String javascript = /* Javascript is retrieved here */;
HttpResponse response = context.Response;
response.ContentEncoding = Encoding.UTF8;
response.ContentType = "application/javascript";
HttpCachePolicy cache = response.Cache;
cache.SetCacheability(HttpCacheability.Public);
cache.SetMaxAge(TimeSpan.FromDays(300));
cache.SetETag(ETag);
cache.SetExpires(DateTime.Now.AddDays(300));
cache.SetLastModified(LastModified);
cache.SetRevalidation(HttpCacheRevalidation.None);
response.Headers.Add("Vary", "Accept-Encoding");
Log.Info("{0} characters sent", javascript.length);
response.Write(javascript);
response.Flush();
response.End();
The content is then normally sent using gzip-encoding with chunked transfer-encoding. Seems simple enough to me.
Unfortunately, I just had a remote-session with a user, where only about 1/3 of the file was in the cache, which broke the file of course (15k instead of 44k). In the cache, the content-encoding was also set to gzip, all communication took place via https.
After having opened the source-file on the user's machine, I just hit Ctrl-F5 and the full content was displayed immediately.
What could have possibly gone wrong?
In case it matters, please find the cache-entry from Firefox below:
Cache entry information
key: <resource-url>
fetch count: 49
last fetched: 2015-04-28 15:31:35
last modified: 2015-04-27 15:29:13
expires: 2016-02-09 14:27:05
Data size: 15998 B
Security: This is a secure document.
security-info: (...)
request-method: GET
request-Accept-Encoding: gzip, deflate
response-head: HTTP/1.1 200 OK
Cache-Control: public, max-age=25920000
Content-Type: application/javascript; charset=utf-8
Content-Encoding: gzip
Expires: Tue, 09 Feb 2016 14:27:12 GMT
Last-Modified: Tue, 02 Jan 2001 11:00:00 GMT
Etag: W/"0"
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
Date: Wed, 15 Apr 2015 13:27:12 GMT
necko:classified: 1

Your clients browser is most likely caching the JavaScript files which would mean the src of your scripts isn't changing.
For instance if you were to request myScripts
<script src="/myScripts.js">
Then the first time, the client would request that file and any further times the browser would read its cache.
You need to append some sort of unique value such as a timestamp to the end of your scripts so even if the browser caches the file, the new timestamp will act like a new file name.
The client receives the new scripts after pressing Ctrl+F5 because this is a shortcut to empty the browsers cache.
MVC has a really nice way of doing this which involves appending a unique code which changes everytime the application or it's app pool is restarted. Check out MVC Bundling and Minification.
Hope this helps!

Related

Alamofire use configurable Caching

I'm using Alamofire 5 and have the requirement that some GET-requests should be cached. If the data is older then 20 minutes the real API should be hit.
What I found is to use the ResponseCacher. But I do not see a way to configure the individual request and need some advice.
let responseCacher = ResponseCacher(behavior: .modify { _, response in
let userInfo = ["date": Date()]
return CachedURLResponse(
response: response.response,
data: response.data,
userInfo: userInfo,
storagePolicy: .allowed)
})
let configuration = URLSessionConfiguration.af.default
private override init() {
configuration.timeoutIntervalForRequest = 10
configuration.requestCachePolicy = .reloadRevalidatingCacheData
Session(
configuration: configuration,
serverTrustManager: ServerTrustManager(evaluators: evaluators),
cachedResponseHandler: responseCacher
)
If the backend is returning proper caching headers that you want to limit to a certain amount of time, adding a Cache-Control: max-age= header on the request may work.
If the backend isn't return proper caching headers, using ResponseCacher is the way to go. You would modify the CachedURLResponse's response to include the proper Cache-Control header.
To elaborate on Jon's answer, the easiest way to achieve what you want is to just let the backend declare the cache semantics of this endpoint, then ensure that on the client side, URLSession uses a URLCache (which is probably the default anyway) and let URLSession and the backend do the rest. This requires, that you have control over the backend, though!
The more elaborate answer:
Here is just an example, how a server may return a response with declared cache semantics:
URL: https://www.example.com/ Status Code: 200
Age: 238645
Cache-Control: max-age=604800
Date: Tue, 12 Jan 2021 18:43:58 GMT
Etag: "3147526947"
Expires: Tue, 19 Jan 2021 18:43:58 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Vary: Accept-Encoding
x-cache: HIT
Accept-Ranges: bytes
Content-Encoding: gzip
Content-Length: 648
Content-Type: text/html; charset=UTF-8
Server: ECS (dcb/7EC7)
This server literally outputs the full range of what a server can declare regarding caching. The first eight headers (from Age to x-cache) declare the caching.
Cache-Control: max-age=604800 for example declares, that the data's freshness equals 604800 seconds. Having the date when the server created the data, the client can now check if the data is still "fresh".
Expires: Tue, 19 Jan 2021 18:43:58 GMT means the exact same thing, it declares when the data is outdated specifying the wall clock. This is redundant with the above declaration, but it is very clearly defined in the HTTP how clients should treat this.
Having an Age header is a hint, that the response has been actually delivered from a cache that exists between the client and the origin server. The age is the estimation of this data's age - the duration from when it has been created on the origin and when it has been delivered.
I don't wont to go into detail what every header means exactly and how a client and server should behave according HTTP since this is a very intense topic, but what you have to do is basically when you define an endpoint, is just to define the duration of the "freshness" of the returned data.
The whole details: Hypertext Transfer Protocol (HTTP/1.1): Caching
Once you came up with a good duration, Web-application frameworks (like Rails, SpringBoot, etc.) give great help with declaring cache semantics out of the box. Then Web-application frameworks will output the corresponding headers in the response - more or less "automagically".
The URLSession will automatically do the right thing according the HTTP protocol (well, almost). That is, it will store the response in the cache and when you perform a subsequent request it first looks for a suitable response in the cache and return that response if the "freshness" of the data is still given.
If that cached data is too old (according the given response headers and the current data and time), it will try to get a fresh data by sending the request to the origin server. Any upstream cache or eventually the origin server may then return fresh data. Your URLSession data task does all this transparently without giving you a clue whether the data comes from the cache or the origin server. And honestly, in most cases you don't need to know it.
Declaring the cache semantics according HTTP is very powerful and it usually should suit your needs. Also, the client may tailor its needs with specifying certain request headers, for example allowing to return even outdated data or ignoring any cached values, and much more.
Every detail may deserve a dedicated question and answer on SO.

MVC isn't adding Content-Length for stuff it gzips

We have a controller returning dynamically generated JS code in a JavaScriptResult (ActionResult).
public ActionResult Merchant(int id)
{
var js = "<script>alert('bleh')</script>"; // really retrieved from custom storage
return JavaScript(js); // same as new JavaScriptResult() { Script = js };
}
The default gzip filter is compressing it but not setting Content-Length. Instead, it just sets Transport-Mode: Chunked and leaves it at that.
Arr-Disable-Session-Affinity:True
Cache-Control:public, max-age=600
Content-Encoding:gzip
Content-Type:application/x-javascript; charset=utf-8
Date:Fri, 06 Nov 2015 22:25:17 GMT
Server:Microsoft-IIS/8.0
Timing-Allow-Origin:*
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.2
X-Powered-By:ASP.NET
How can I get it to add the header? I absolutely need this header. Without Content-Length, there's no way to tell if the file completed downloading, e.g. if the connection dropped. Without it, some CDNs like Amazon CloudFront aren't able to properly cache.
After turning off compression, I get a normal Content-Length. It seems trivial for the gzip filter to add this length - there must be an option somewhere?

Rails 4 redirects to 'data:,' in Chrome

There is a weird behavior in Google Chrome, which is also described in this question: rails redirects to 'data:,'
When a new resource is being created and my controller redirects to the show action, chrome initiates loading of a blank page with 'data:,' in the address bar. The reply of the author who asked the above mentioned question is the following:
This is a security feature, the HTML content of the new page matches the HTML content of the submitted form, which Chrome blocks.
However no explanation of how to fix it followed. The behavior is only present in Chrome browser.
I've been googling it and found that editing posts with an iframe in Rails 4.0 causes a redirect to "data:,"
Rails 4 now sets the X-XSS-Protection header for all requests, so the iframe trips up the XSS protection in Chrome after a form submit.
(https://github.com/elektronaut/sugar/issues/41#issuecomment-25987368)
Solution, add it to your controller:
before_filter :disable_xss_protection
protected
def disable_xss_protection
# Disabling this is probably not a good idea,
# but the header causes Chrome to choke when being
# redirected back after a submit and the page contains an iframe.
response.headers['X-XSS-Protection'] = "0"
end
Ok I think I know what this is. You can specify images and text inside a data: protocol, and I believe Chrome is seeing escaped HTML and thinking it is data. Since the mime type is not specified, it leaves the mime type blank after the colon, and just prints the comma.
http://guides.rubyonrails.org/security.html#redirection
Rails 4 automatically escapes HTML, so if you are trying to render HTML, you have to indicate not to escape it. Look at the options for render:
http://guides.rubyonrails.org/security.html#redirection
You can use raw() to render direct HTML.
http://www.webbydude.com/posts/9-the-h-helper-in-rails-3
I'm not convinced it is related to a mimetype issue. I have this issue when a user posts a blog entry that has iframes in its content. When the entry is saved it redirects to the "show" action which will have the user's content (raw/html_safe). Chrome will display the page for a split second and then for some reason re-direct again to the blank "data:," page (in history it will only leave the data:, and the submit page).
here are the response headers i registered:
Ruby 2.0.0 / Rails 4 migrated app with incorrect behavior (staging server)
Cache-Control:max-age=0, no-cache, no-store
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Encoding:gzip
Content-Length:25359
Content-Type:text/html; charset=utf-8
Date:Thu, 23 Jan 2014 16:37:11 GMT
ETag:"6d9d4961ea2df12de67f8a92c43579fb"
Server:Apache
Set-Cookie: _**********_session_dev=1774518c571bf4e65189d607b276e65e; domain=*********.com; path=/; expires=Thu, 23 Jan 2014 18:37:11 -0000; HttpOnly
Status:200 OK
Vary:Accept-Encoding
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Mod-Pagespeed:1.6.29.7-3566
X-Request-Id:9f5314a5-ad01-4aec-bd0f-04e8afd9bdac
X-UA-Compatible:chrome=1
X-XSS-Protection:1; mode=block
Ruby 1.8.7 / Rails 2 app with correct behavior (prod server)
HTTP/1.1 200 OK
Date: Thu, 23 Jan 2014 16:32:53 GMT
Server: Apache
ETag: "f12135ddd373205352f9754328368217"
Cache-Control: private, max-age=0, must-revalidate
Status: 200
X-Mod-Pagespeed: 1.4.26.5-3533
Cache-Control: max-age=0, no-cache, no-store
Vary: Accept-Encoding
Content-Length: 27167
X-Cnection: close
Content-Type: text/html; charset=utf-8
Connection: Keep-Alive
Content-Encoding: gzip
also tried having this as the initial html:
<!DOCTYPE html>
<html>
<head>...
and also just (as random tests to detect what could be wrong)
<!DOCTYPE html>
<head>...
All I know is that if the submitted content has iframes, when redirecting to the blog "display" page chrome's weird behavior kicks in.

Why does Chrome use the client cache differently in these two scenarios?

I'm working on a small single-page application using HTML5. One feature is to show PDF documents embedded in the page, which documents can be selected form a list.
NOw I'm trying to make Chrome (at first, and then all the other modern browsers) use the local client cache to fulfill simple GET request for PDF documents without going through the server (other than the first time of course). I cause the PDF file to be requested by setting the "data" property on an <object> element in HTML.
I have found a working example for XMLHttpRequest (not <object>). If you use Chrome's developer tools (Network tab) you can see that the first request goes to the server, and results in a response with these headers:
Cache-Control:public,Public
Content-Encoding:gzip
Content-Length:130
Content-Type:text/plain; charset=utf-8
Date:Tue, 03 Jul 2012 20:34:15 GMT
Expires:Tue, 03 Jul 2012 20:35:15 GMT
Last-Modified:Tue, 03 Jul 2012 20:34:15 GMT
Server:Microsoft-IIS/7.5
Vary:Accept-Encoding
The second request is served from the local cache without any server roundtrip, which is what I want.
Back in my own application, I then used ASP-NET MVC 4 and set
[OutputCache(Duration=60)]
on my controller. The first request to this controller - with URL http://localhost:63035/?doi=10.1155/2007/98732 results in the following headers:
Cache-Control:public, max-age=60, s-maxage=0
Content-Length:238727
Content-Type:application/pdf
Date:Tue, 03 Jul 2012 20:45:08 GMT
Expires:Tue, 03 Jul 2012 20:46:06 GMT
Last-Modified:Tue, 03 Jul 2012 20:45:06 GMT
Server:Microsoft-IIS/8.0
Vary:*
The second request results in another roundtrip to the server, with a much quicker response (suggesting server-side caching?) but returns 200 OK and these headers:
Cache-Control:public, max-age=53, s-maxage=0
Content-Length:238727
Content-Type:application/pdf
Date:Tue, 03 Jul 2012 20:45:13 GMT
Expires:Tue, 03 Jul 2012 20:46:06 GMT
Last-Modified:Tue, 03 Jul 2012 20:45:06 GMT
Server:Microsoft-IIS/8.0
Vary:*
The third request for the same URL results in yet another roundtrip and a 304 response with these headers:
Cache-Control:public, max-age=33, s-maxage=0
Date:Tue, 03 Jul 2012 20:45:33 GMT
Expires:Tue, 03 Jul 2012 20:46:06 GMT
Last-Modified:Tue, 03 Jul 2012 20:45:06 GMT
Server:Microsoft-IIS/8.0
Vary:*
My question is, how should I set the OutputCache attribute in order to get the desired behaviour (i.e. PDF requests fullfilled from the client cache, within X seconds of the initial request)?
Or, am I not doing things right when I cause the PDF to display by setting the "data" property on an <object> element?
Clients are never obligated to cache. Each browser is free to use its own heuristic to decide whether it is worth caching an object. After all, any use of cache "competes" with other uses of the cache.
Caching is not designed to guarantee a quick response; it is designed to, on average, increase the likelihood that frequently used resources that are not changing will already be there. What you are trying to do, is not what caches are designed to help with.
Based on the results you report, the version of Chrome you were using in 2012 decided it was pointless to cache an object that would expire in 60 seconds, and had only been asked for once. So it threw away the first copy, after using it. Then you asked a second time, and it started to give this URL a bit more priority -- it must have remembered recent URLs, and observed that this was a second request -- it kept the copy in cache, but when the third request came, asked server to verify that it was still valid (presumably because the expiration time was only a few seconds away). The server said "304 -- not modified -- use the copy you cached". It did NOT send the pdf again.
IMHO, that was reasonable cache behavior, for an object that will expire soon.
If you want to increase the chance that the PDF will stick around longer, then give a later expiration time, but say that it must check with the server to see if it is still valid.
If using HTTP Cache-Control header, this might be: private, max-age: 3600, must-revalidate. With this, you should see a round-trip to server, which will give 304 response as long as the page is valid. This should be a quick response, since no data is sent back -- the browser's cached version is used.
private is optional -- not related to this caching behavior -- I'm assuming whatever this volatile PDF is, it only makes sense for the given user and/or shouldn't be hanging around for a long time, in some shared location.
If you really need the performance of not talking to the server at all, then consider writing javascript to hide/show the DOM element holding that PDF, rather than dropping it, and needing to ask for it again.
Your javascript code for the page is the only place that "understands" that you really want that PDF to stick around, even if you aren't currently showing it to the user.
Have you tried setting the Location property of the OutputCache to "Client"
[OutputCache(Duration=60, Location = OutputCacheLocation.Client)]
By default the location property is set to "Any" which could mean that the response is cached on the client, on a proxy, or at the server.
more at MSDN OutputCacheLocation

Very Strange ASP.NET MVC and IE8 Link/URL Issue

I've got a really strange issue in MVC/IE8 and I was wondering if anyone had seen anything like it. I've got a URL that returns an excel spreadsheet in the form http://application.mycompany/Controller.aspx/Action/ID1/ID2 (I am using the .aspx in the route due to the version of IIS). This opens from a page with the application, fine. I also have e-mails with HTML bodies that get sent around that have links in them and all the links open fine apart from this one - it gives an error saying "Cannot download ID2 from application.mycompany, IE was unable to open the site". I've checked the HTML in the e-mail and the link location on the page that works and they are the same. If I type the exact link text (or copy and paste it from the e-mail) into the IE address bar it again fails, but then if I click on the same text in the addres bar at the end and hit enter a second time it loads and spits out the file. It seems to work ok pasting it straight into FireFox, but I can't check clicking on the link and loading FF as I'm working remotely and don't have Firefox on my citrix desktop, nor can I find anyone who has it as their default browser (but as it works first time, I would imagine it would be ok).
Has anyone seen this before or got any ideas what might be causing it, please? This issue occurs on more than one machine, so it isn't an odd add-in/setting as far as I can see.
Thanks
MH
----------------------------Update-----------------------
I've used Fiddler to see what's going on and the response to both requests is identical, apart from the timestamp.
Failed response:-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/vnd.ms-excel; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.0
X-AspNetMvc-Version: 1.0
content-disposition: attachment;filename=Filename.xls
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Mon, 20 Dec 2010 10:31:52 GMT
Content-Length: 2354
<style type"text/css">.text { mso-number-format:\#; } .TableHead { background-color: #BDBDBD; } </style>
....confidential file content removed
Successful response:-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/vnd.ms-excel; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.0
X-AspNetMvc-Version: 1.0
content-disposition: attachment;filename=Filename.xls
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Mon, 20 Dec 2010 10:32:18 GMT
Content-Length: 2354
<style type"text/css">.text { mso-number-format:\#; } .TableHead { background-color: #BDBDBD; } </style>
and just to reiterate, the only thing I do to generate the second request is to click on the URL text in the address bar that generated the first request and hit enter.
The error I get is ""Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later."
To quote the similar discussion on the Sitepoint forums.
It's the caching, if you select
no-cache IE can't find the file to
save it, but if you allow caching it
works. Annoying as the same file can
be downloaded at different times and
may well be different if things have
changed, so caching is a little
dangerous (I'll have to suffix the
filename with a timestamp to avoid
this, I think).
http://support.microsoft.com/kb/316431
so this fixes it
Response.Cache.SetCacheability(HttpCacheability.Private);
but it still doesn't explain why it
sometimes works, esp directly from the
web page link
"This behaviour is by design" -
translation "we can't get this to work
and CBA to get it working" - amazing
how firefox, opera and chrome don't
have this problem......
This kb article might help shed some light:
http://support.microsoft.com/kb/316431
"Web sites that want to allow this type of operation should remove the no-cache header or headers."
I've seen some wierdness around IE + Outlook + mailto links with large numbers of characters. Above 128 characters, mailto links do not work with IE + Outlook.
I would try to decrease the number of characters in your anchor to see if you can get it to be more reliable.

Resources