Background images in css are not getting cached - css

I have some react code that is rendering content dynamically via React.createElement. As such, css is applied via an object. Elements in that dynamic generation can have background image, pointing to a public aws S3 bucket.
It seems that every time my components re-render, the background images are being fetched again from S3. This is delaying the page render. I have S3 meta-data for Cache-Control set on all the objects . Here are request and response headers for background image load -
Response header -
Accept-Ranges: bytes
Cache-Control: public, max-age=604800
Content-Length: 52532
Content-Type: application/octet-stream
Date: Sun, 06 Feb 2022 05:57:32 GMT
ETag: "f29655808a5f80627d9ea7f44058a5e3"
Last-Modified: Sun, 06 Feb 2022 05:55:10 GMT
Server: AmazonS3
x-amz-meta-filetype: IMAGE
Request Header -
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,hi;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Host: <bucket-name>s3.amazonaws.com
Pragma: no-cache
Referer: https://<my-domain>.com/
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
I can see in Network tab that images are being loaded multiple times and it also shows data transfers being done everytime. What am I doing wrong here? Can someone please help finding the root cause. Thanks.

could it be a forgotten "disable cache" option selected in the network tab in the dev tools ? Because it seems the server responds with the correct type of cache headers.

If images are optimized and not huge, using base64 data url is a good solution.
const getBase64FromUrl = async (url) => {
const data = await fetch(url);
const blob = await data.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
}
});
}
const image = getBase64FromUrl('the image url')
While creating the element you can use
background-image: `url(${image})`;
In addition, rarely do we serve from S3 directly, you should probably use cloudfront as a proxy to
reduce get request
reduce bandwidth charges
cache at cdn
better control of cache headers
hide your s3 real url

The reason you're seeing a network request is probably because you're using the Cache-Control: no-cache header in your request.
As seen here:
The no-cache response directive indicates that the response can be
stored in caches, but the response must be validated with the origin
server before each reuse, even when the cache is disconnected from the
origin server.
Cache-Control: no-cache
If you want caches to always check for content
updates while reusing stored content, no-cache is the directive to
use. It does this by requiring caches to revalidate each request with
the origin server.
Note that no-cache does not mean "don't cache". no-cache allows caches
to store a response but requires them to revalidate it before reuse.
If the sense of "don't cache" that you want is actually "don't store",
then no-store is the directive to use.
See here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives
Here is what a full request for a cached asset looks like on my network tab, when the asset returns 304 Not Modified from the validation request. (from S3) This is in a background: url context.

Related

How to test Firebase Functions with Cache Control Header on localhost?

I'm new to Cache Control Header implementation and I need someone to point out any of my mistakes and/or misunderstandings over the cache control effects on Firebase Cloud Functions.
My understanding & expectation on Cache Control over Firebase Functions
When the Cache Control Header has been successfully set using Express response object (confirmed by checking from the Chrome's Network
tab), regardless it is on localhost or production server, the Firebase
Https Functions (not callable functions) should not be invoked again
after the first reload until the cache is expired.
Am I right? But after a few rounds of testing, it seems like my cloud function on localhost still consistently get invoked (confirmed by server console logging) regardless the number of refresh on my web browser. Below is my current Http header:
**General:**
Request URL: http://localhost:5005/otk-web-solutions?id=B0Y0jp2x83WVYzWrpg5y
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:5005
Referrer Policy: strict-origin-when-cross-origin
**Response Headers:**
Access-Control-Allow-Origin: *
cache-control: public, max-age=432000, s-maxage=432000
content-length: 9688
content-type: text/html; charset=utf-8
date: Mon, 05 Apr 2021 11:52:20 GMT
etag: W/"25d8-TxL0Q+ujhzDjys8IJ1mLigY7jT8"
vary: Origin, Accept-Encoding, Authorization, Cookie
x-powered-by: Express
**Request Headers:**
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,zh;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Cookie: _ga=GA1.1.816734993.1603107580; _gid=GA1.1.223745218.1617606982; __atuvc=20%7C12%2C15%7C13%2C23%7C14; __atuvs=606aec5f76521aab00a
DNT: 1
Host: localhost:5005
If-None-Match: W/"25d8-TxL0Q+ujhzDjys8IJ1mLigY7jT8"
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
**Query String Parameters:**
id: B0Y0jp2x83WVYzWrpg5y
On Firebase documentation:
You can, though, configure caching behavior for dynamic content. For
example, if a function generates new content only periodically, you
can speed up your app by caching the generated content for at least a
short period of time.
You can also potentially reduce function execution costs because the
content is served from the CDN rather than via a triggered function.
Could it be that, the cache control header has no effects on localhost except on Firebase CDN, which means only when we've deployed it to the production server for the caching to work on the cloud CDN? Is there a right way to implement such test to see the effectiveness of the cache control header in helping to save the Firebase Cloud Functions' execution costs?
Please advise, thanks a lot!
From my understanding of the documentation and after checking your test, only if the content is served from the CDN you will be able to save costs.
Even if your request is met with a status code 304 Not Modified, it seems like you're still making the request and invoking the function if you're not using the Firebase Hosting CDN.
So to make a test to see if you can save costs by not invoking the function many times you should set up Firebase Hosting and do that same test to see if a response from the CDN invokes the function.

CORS Google Cloud - Authorization header in the OPTIONS via Axios

I have a CORS-enabled Spring Boot API that runs on Google Cloud Run and a Vue.js front end that runs on Firebase and uses Axios to make the calls to the back end.
The Problem is that when the front end wants to access the back end (Browser --> Google Clud), it fails with:
Access to XMLHttpRequest at 'https://<backend>' from origin 'https://<frontend>' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If I access from LOCAL front end (also Browser) to a LOCAL back end, it works: The error above is not shown in the Browser console and I get the data).
If I make the OPTIONS or GET call from Postman to the Google Cloud back end, it works.
I noticed, that with Postman I need to include the Authorization header in the OPTIONS request to send the Bearer token to Google to make it work. The Browser does not send any Authorization header in the OPTIONS call, even if I add withCredentials: true to the Axios config like this:
const response = await axios({
method: 'post',
withCredentials: true,
url: 'https://<backend>',
headers: {
'Authorization': 'Bearer ' + gCloudToken
},
data: {
// data...
}
});
Isn't that a security problem, to send the token in the header? I mean, everyone can see the headers and then fake a call to the server.
Can anybody show how to send the Authorization header in the OPTIONS call via Axios or tell how to correctly handle this problem?
UPDATE 1:
The request from the browser looks like this:
OPTIONS /path/to/api HTTP/2
Host: <backend>-ew.a.run.app
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Referer: https://frontend.web.app/
Origin: https://frontend.web.app
Connection: keep-alive
And this is the response:
HTTP/2 403 Forbidden
date: Tue, 07 Jul 2020 23:57:27 GMT
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 320
alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
X-Firefox-Spdy: h2
As you can see, no CORS headers (like e.g. access-control-allow-origin) are present.
The cause of this issue is that by not allowing unauthenticated calls the CORS preflight are always rejected with a 403 error message.
There is already a feature request for Cloud Run in orther to support CORS and authentication with Cloudrun.
The Workaround I would see so far is to allow unauthenticated calls on the CloudRun and implement the authentication on your code, However this can have security disadvantages.

Safari Caching GET request even with disabled cache

I have set all headers that I know of to disable caching (even disabling ETAG) on my server, yet Safari still occasionally (about 50% times) caches my requests.
Workflow
I am implementing oauth 1, so:
Browser makes GET /api/user request
Server returns 405
Browser redirects to 3rd party website to authenticate
Browser is redirected to api/callback which stores some info into cookie.
Browser is redirected back to original route.
Browser makes GET /api/user request which should be successful, however it gets 405 served from disk cache instead.
Request summary from Safari Network Inspector
Summary
URL: http://localhost:3000/api/user
Status: 405 Method Not Allowed
Source: Disk Cache
Request
No request, served from the disk cache.
Response
Transfer-Encoding: Identity
Content-Type: application/json; charset=utf-8
Pragma: no-cache
Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0
Vary: Cookie, Accept-Encoding
Date: Wed, 23 Jan 2019 11:34:23 GMT
Content-Encoding: gzip
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Connection: close
x-powered-by: Express
Conclusion
I have no idea what's wrong and I will greatly appreciate any help. My
Safari version is 12.0.2. I wasn't able to replicate this issue with Chrome.
Use Vary: *. This magically solved my problem.
This answer helped me: https://stackoverflow.com/a/2068353/1364158
Alternatively, you can really force browser to load a new version of request by including some meaningless random query arg in your url, e.g. /api/user?ts=18284

JSession cookie dropping cause DuplicateSessionDetected exception when use https rather http

I developing Grails+BlazeDS server and Flex AIR client and stucked with this error:
Detected duplicate HTTP-based FlexSessions, generally due to the remote host disabling session cookies. Session cookies must be enabled to manage the client connection correctly
Google searches didn't successfully, as I see some difference in situations.
The issue I got only when Flex client interact with server via https.
Flex client:
<s:ChannelSet id="userChannel">
<s:SecureAMFChannel uri="https://localhost:8443/Con/messagebroker/amfpolling" />
</s:ChannelSet>
button click in UI triggered login method:
loginResult.token = channelSet.login(usernameInput.text, passwordInput.text);
And finished with DuplicateSessionDetected exception. :(
After investigating network monitor logs, I found that jsession cookie received from server not set in next requests to a server:
Response from server (operation: client_ping)
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=F58F1ADA97E70915EF9E6E4EE1AEBE00; Path=/; Secure
Content-Type: application/x-amf
Content-Length: 173
Date: Sun, 23 Feb 2014 10:17:00 GMT
Flex Message (flex.messaging.messages.AcknowledgeMessageExt) clientId = EA18E8B9-951F-6F87-7B47-48B8B202EE75 correlationId = 7D2782C1-C8A5-41A3-2055-5E3F771424C8 destination = null messageId = EA18E8F6-9E0E-1FE4-0D26-6F0E602F5C5E timestamp = 1393150620542 timeToLive = 0 body = null hdr(DSMessagingVersion) = 1.0 hdr(DSId) = EA18E8B9-950B-4B42-EF70-369D656BA3F2
And next request to server (login operation) without jsession cookie:
POST /Conn/messagebroker/amfsecure HTTP/1.1
Referer: app:/BlazeDSClient.swf
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, text/css, image/png, image/jpeg, image/gif;q=0.8, application/x-shockwave-flash, video/mp4;q=0.9, flv-application/octet-stream;q=0.8, video/x-flv;q=0.7, audio/mp4, application/futuresplash, */*;q=0.5
x-flash-version: 12,0,0,68
Content-Type: application/x-amf
Accept-Encoding: gzip,deflate
User-Agent: Mozilla/5.0 (Windows; U; en) AppleWebKit/533.19.4 (KHTML, like Gecko) AdobeAIR/4.0
Host: localhost
Content-Length: 299
Flex Message (flex.messaging.messages.CommandMessage) operation = login clientId = null destination = auth messageId = 7B47BBF2-08C0-0E41-5D88-5E3F76FA4882 timestamp = 0 timeToLive = 0 ***not printing credentials***
and server answering with new session cookie:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=03BD8347F9E9511C299B717DD55625C9; Path=/; Secure
Content-Type: application/x-amf
Content-Length: 535
Date: Sun, 23 Feb 2014 10:17:01 GMT
Flex Message (flex.messaging.messages.ErrorMessage) clientId = null correlationId = 7B47BBF2-08C0-0E41-5D88-5E3F76FA4882 destination = auth messageId = EA18F4A7-C80D-103B-F8D0-58B6F148F142 timestamp = 1393150621768 timeToLive = 0 body = null code = Server.Processing.DuplicateSessionDetected message = Detected duplicate HTTP-based FlexSessions, generally due to the remote host disabling session cookies. Session cookies must be enabled to manage the client connection correctly. details = null rootCause = null body = null extendedData = null
And again - when used non-secure protocol everything ok - session cookie sevt to server in login operation as expected.
I have a little experience in Flex development and didn't find any method to set session cookie when triggered channel login request. Can you help to resolve this issue?
Thanks!
Gotcha!!
It's unbelievable, but the cause of DuplicateSessionDetected exception has been a Network Monitor tool of Flash Builder. After switching it off no any exception has been occurred. I think there issues when Monitor acting as proxy when used with secure protocol.
Surely, this question is already dead, but I have got something to say in this regard for future readers.
The Flash Player (including Flex) does not transmit the default JSESSIONID in the request and cannot do it until you have set SameSite=None in the JSESSIONID cookie.
I have faced the problem where the JSESSIONID cookie is dropped in the request and I have discovered that it is because modern browsers (chrome > 80) do not allow the Flash/Flex Player to access the JSESSIONID cookie it the cookie does not have SameSite=None and Secure flash.
Please, read the announcement from Adobe here
More to read about the new cookie policy:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
https://medium.com/adobetech/adobe-experience-cloud-cookie-updates-for-google-chrome-19ad67cf1598
https://digiday.com/media/what-is-chrome-samesite/
Do not perform the client_ping operation and then try the secure channelSet. by pingin the server, you are creating another channelset(by default flash creates one for you) and then you are trying to open another channelset using .login operation. Try this by restarting you server,(fresh instance) or else you will be creating more sessions.

Error pages does not following content type/ format

I followed this tutorial, http://symfony.com/doc/current/cookbook/controller/error_pages.html, and have error.html.twig and error.json.twig within app/Resources/TwigBundle/views/Exception/
Even though the content type of the request is set to application/json, all errors default to the html version of the error page.
The format of the route is also defined:
http://symfonyinstall/api/v1/users.json
Request Header:
Accept: application/json
Content-Type: application/json
Connection: keep-alive
Origin: chrome-extension: //rest-console-id
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.162 Safari/535.19
Response Header:
Status Code: 404
date: Sun, 29 Apr 2012 06:54:35 GMT
Content-Encoding: gzip
X-Powered-By: PHP/5.3.10
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
Content-Type: text/html; charset=UTF-8
cache-control: no-cache
I'm out of ideas... and I really need a json version of the errors for my API to work...
I just hit the same problem, and although your question is quite old and symfony bumped a few versions up since then, the problem is still relevant, so even if you don't need to know it any more, maybe somebody else will.
Your original problem was probaly caused by error described here, but there wasn't much going on about it after initial post. Since then the entire codebase was updated, so while the same symptoms reappeared, the error is not related. I am posting it here because anybody looking for an answer these days will probably find this question (as I did :).
Even when returing JsonResponse directly form kernel exception handler, it will still have Content-Type: text/html; charset=UTF-8. This stumped me so much that I used netcat to make a manual request without any smart software in between, and it turns out that the response in such case has actually two different Content-Type headers:
HTTP/1.0 500 Internal Server Error
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.17
Content-Type: text/html; charset=UTF-8
Cache-Control: private, must-revalidate
Content-Type: application/json
pragma: no-cache
expires: -1
X-Debug-Token: 775c55
X-Debug-Token-Link: http://127.0.0.1:8000/_profiler/775c55
Date: Thu, 27 Oct 2016 23:08:31 GMT
Now, double Content-Type header is not something you see everyday. It seems that this is implemented in Symfony\Component\Debug\ExceptionHandler class that is only used in debug mode. In order to be as robust as possible, it first renders standard Symfony error page that describes thrown exception. Rendered content is not sent back directly, instead it leverages PHP's output buffering feature to buffer and store produced output. Then it attempts to produce custom error page from framework. In case this fails, previously prepared message is sent.
Output buffering however works only for message content, and not for headers - these are always sent directly. This problem only appears in debug environment, and unusual content types on error are only common in WebAPI, where debug mode is arguably of little use. This makes exposure surface relatively small, but if an application that offers both WebAPI and end-user interface needs to be tested, this might become a problem.
Solving this problem without modifying internal Symfony files doesn't seem possible. Output control sits deep within symfony kernel and doesn't offer any configuration. Anyway, I am not convinced of benefits of such solution. If anyone could explain to me what could have happened during custom exception handler that would make default handler useless in case it failed?
Maybe user code messing with ob_* functions?

Resources