Express.js Node Framework - Not caching - node.js

My Node.js app uses Express and my app has a route that sends a JSON file with Tweet data. I want it to cache for 20 seconds. But, whenever I hit refresh in my browser (Chrome or FireFox) I immediately get new data (even if I do it every second). Note that the data does change more than every 20 seconds, but I still want a 20 second cache.
Here is my route.
app.get('/tweet-stats.json', function(req, res) {
res.set('Cache-Control', 'public, max-age=20');
res.set('Expires', new Date(Date.now() + 20000));
res.set('Last-Modified', new Date(Date.now()));
res.set('Content-Type', 'application/json');
res.send(publicTweetStatus());
});
Here are the request and response headers from FireFox (FireBug):
Response Headers
HTTP/1.1 200 OK
X-Powered-By: Express
Cache-Control: public, max-age=20
Expires: Fri May 10 2013 06:52:11 GMT+0000 (UTC)
Last-Modified: Fri May 10 2013 06:51:51 GMT+0000 (UTC)
Content-Type: application/json
Content-Length: 209
Connection: keep-alive
Request Headers
GET /tweet-stats.json HTTP/1.1
Host: mydevelopmenturl.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
How can I make browsers cache this for 20 seconds before checking with the server again?
UPDATE
So, I tried robertklep's suggestion and it worked in some browsers/OSs and not others:
Ubuntu Chrome - No Cache!!!!!!!!!!!!
Ubuntu FireFox - No Cache!!!!!!!!!!!!!!!
Windows 7 - Chrome - Cache
Windows 7 - FireFox - Cache
Windows 7 - IE 9 - Cache
Windows 7 - Opera - No Cache!!!!!!!!!!!!!!!!!
iOS Safari - Cache
Mac OSX - Safari - Cache
Mac OSX - Chrome - Cache
Mac OSX - Firefox - Cache
Why the differences?

Related

CORS blocks one API request (not others) in Safari

I am running a React (16) webapp (deployed on Netlify) that is failing with one if its API calls being blocked by CORS but only in Safari. There's no problem in Chrome or Firefox. The console shows the following:
Origin https://chicha.netlify.app is not allowed by Access-Control-Allow-Origin.
XMLHttpRequest cannot load https://chicha-api.herokuapp.com/votes due to access control checks.
Failed to load resource: Origin https://chicha.netlify.app is not allowed by Access-Control-Allow-Origin.
Headers of the blocked request (Safari):
Summary
URL: https://chicha-api.herokuapp.com/votes
Status: —
Source: —
Initiator:
xhr.js:178
Request
Accept: application/json, text/plain, */*
Origin: https://chicha.netlify.app
Referer: https://chicha.netlify.app/events
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
X-Requested-With: XMLHttpRequest
Response
No response headers
Here are the headers of the same request that is not blocked (Chrome):
Request URL: https://chicha-api.herokuapp.com/votes
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: 52.214.138.78:443
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: x-requested-with
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: https://chicha.netlify.app
Connection: keep-alive
Content-Length: 0
Date: Wed, 03 Jun 2020 10:43:59 GMT
Server: Cowboy
Vary: Origin, Access-Control-Request-Headers
Via: 1.1 vegur
X-Powered-By: Express
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en,es;q=0.9,ca;q=0.8,fr;q=0.7
Access-Control-Request-Headers: x-requested-with
Access-Control-Request-Method: GET
Connection: keep-alive
Host: chicha-api.herokuapp.com
Origin: https://chicha.netlify.app
Referer: https://chicha.netlify.app/events
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
The app makes the call with axios configured as followed:
class ApiClient {
constructor() {
this.apiClient = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URI,
withCredentials: true,
headers: { 'X-Requested-With': 'XMLHttpRequest' },
});
}
getVotes = () => this.apiClient.get('/votes');
getEvents = () => this.apiClient.get('/events');
...
The API (Node.js with Express deployed on Heroku) has the following CORS configuration:
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_DOMAIN],
})
);
... where FRONTEND_DOMAIN is https://chicha.netlify.app in environment config vars on Heroku.
What's strange is other API requests have no issue. API headers of a successful request (Safari):
Summary
URL: https://chicha-api.herokuapp.com/events
Status: 304 Not Modified
Source: Memory Cache
Address: 52.215.119.172:443
Initiator:
xhr.js:178
Request
GET /events HTTP/1.1
Accept: application/json, text/plain, */*
Origin: https://chicha.netlify.app
Accept-Language: en-gb
If-None-Match: W/"c010-APRvUYTowK2az7ovB1dPcY+SGuk"
Host: chicha-api.herokuapp.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Referer: https://chicha.netlify.app/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
X-Requested-With: XMLHttpRequest
Response
HTTP/1.1 304 Not Modified
Connection: keep-alive
Access-Control-Allow-Credentials: true
ETag: W/"c010-APRvUYTowK2az7ovB1dPcY+SGuk"
Date: Wed, 03 Jun 2020 10:26:18 GMT
Via: 1.1 vegur
Access-Control-Allow-Origin: https://chicha.netlify.app
Content-Length: 0
Vary: Origin
Server: Cowboy
X-Powered-By: Express
Does the fact that the blocked request has a referrer which is distinct from origin matter? Or perhaps some issue with the cookie not being sent with the request (despite the withCredentials configuration in axios)?
EDIT (4 Jun 2020): I've been able to replicate this on localhost, so I'm editing to indicate it's just an issue in Safari. Given related questions and some testing I believe this has to do with the allowedHeaders / CORS Access-Control-Allow-Headers configuration and maybe the way Safari configures OPTIONS preflight request. I haven't been able to see all the request details even configuring Reactotron and apisauce.
EDIT (7 Jun 2020): so I've noted cookies aren't being set in Safari, so the currentUser I set into express on the server side is not persisting (in req.session.currentUser where I set it after login). Also while I was able to produce the error at one point in localhost I am not able to reliably replicate it locally.
It turns out Safari was blocking the cookie sent from my API as a result of the "Prevent cross-site tracking" option in Safari privacy settings. Disabling this and everything works perfectly.
So it seems the only workaround is to place the API on the same domain as the web app, or to use a proxy, as referenced in this question and answer:
Is there a workaround for Safari iOS "Prevent Cross Site Tracking" option, when issuing cookies from API on different domain?
EDIT: The following answer only applies to localhost development, not for when this issue also happens on a remote server. It's a temporary solution in development mode.
Well, if someone else finds this question, there's an easy fix in Safari. First, make sure you have your Develop menu in the menu bar by going to "Safari" -> "Settings" and in the tab "Advanced" select the option "Show Develop menu in menu bar". Once this is done, you should see the Develop menu. Now, select "Develop" -> "Disable Cross-Origin Restrictions".
Disable Cross-Origin Restrictions in Develop menu

ERR_INVALID_SIGNED_EXCHANGE error in Google Chrome

I've set up my simple website with valid Let's Encrypt SSL certificate (from certbot). My nginx config is very short and trivial.
Website shows up correctly in latest Firefox. It shows 404 page, which is OK to me and should work as expected: 404 page.
If I try Google Chrome, i get an error:
The webpage at https://example.org/ might be temporarily down or it
may have moved permanently to a new web address.
ERR_INVALID_SIGNED_EXCHANGE
I assume that the application/signed-exchange header may cause this.
What is this header and should i remove it from response?
Request
GET / HTTP/1.1
Host: example.org
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru;q=0.8
DNT: 1
example.org example.org
Response
HTTP/1.1 404 Not Found
Server: nginx
Date: Fri, 29 Mar 2019 12:05:49 GMT
Content-Type: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Content-Length: 345
Connection: keep-alive
What to fix?
The Content-Type in the response is incorrect. It should be a single type, as Steffen Ullrich said. For a 404 page, I suspect you want Content-Type: text/html.
This may be something particular to your nginx config. On my server, 404 pages have Content-Type: text/html.

Client caching recommendations for custom web fonts

What is the best way to get IIS to set the headers for woff files so that they can be served from the client browser cache.
I'm working on an MVC .NET site that is hosted in IIS7.5, served through cloudflare with static caching turned on. The site uses a custom woff web font. When requests are made for pages that use these fonts IIS is serving them with the headers shown below. Subsequent requests all look the same. To me it looks like these are not getting cached by the client browser. I'd expect the server to be responding with 304 (Not Modified) and then the browser should serve the woff from its cache.
Request Headers
GET /blah/Content/fonts/AzoSans-Thin-webfont.woff HTTP/1.1
Host: blah.co.uk
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36
Accept: */*
DNT: 1
Referer: http://blah.co.uk/bundles/Content/stylesheets/main?v=f9NXr53WMUdV9DfYJMkEU_5QZZi0g8eB1lB5lqxgdXc1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: __cfduid=d96b367152ae58725c15e5946cf1d67f41415385741070; ASP.NET_SessionId=3tcc3e1nd0z005tlknrbph5h; redesign#lang=en
Response Headers
HTTP/1.1 200 OK
Date: Fri, 07 Nov 2014 19:27:57 GMT
Content-Type: application/font-woff
Content-Length: 27728
Connection: keep-alive
Last-Modified: Thu, 06 Mar 2014 10:40:46 GMT
ETag: "0cbfc872839cf1:0"
X-Powered-By: ASP.NET
CF-Cache-Status: HIT
Vary: Accept-Encoding
Expires: Fri, 07 Nov 2014 23:27:57 GMT
Cache-Control: public, max-age=14400
Accept-Ranges: bytes
Server: cloudflare-nginx
CF-RAY: 185bee60092d0a90-LHR
The problem here seems to be that cloudflare is changing the headers. If I bypass cloudflare I get
Cache-Control:max-age=0
and the server responds with a 304 and the browser uses content from its cache.

Cannot gzip compress static files in Nodejs

Gzipping static files does not work as required (as I think). I used gzippo and express.compress(). Both gzipp the files one time. There is no Content-Encoding:gzip if I refresh the page again.
Her is how I setup my express app:
var gzippo = require('gzippo');
var express = require('express');
var app = express();
app.use(express.compress());
app.use(app.router);
app.use(gzippo.staticGzip(__dirname + '/www'));
This is what Chrome network Response Headers show after page update:
EDITED for full request headers
GET / HTTP/1.1
Host: myhost.com
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/ *;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: __utma=161759779.498387976.1381482631.1394444924.1394395346.80; __utmb=161759779.3.10.1394395346; __utmc=161759779; __utmz=161759779.1394444924.79.7.utmcsr=gtmetrix.com|utmccn=(referral)|utmcmd=referral|utmcct=/reports/myhost.com/5iXAs1ej/retest
If-None-Match: "25020-1394452200000"
If-Modified-Since: Mon, 10 Mar 2014 11:50:00 GMT
If I refresh again it shows: Edited with full response headers.
HTTP/1.1 304 Not Modified
x-powered-by: Express
accept-ranges: bytes
etag: "25020-1394452200000"
date: Mon, 10 Mar 2014 10:51:45 GMT
cache-control: public, max-age=0
last-modified: Mon, 10 Mar 2014 11:50:00 GMT
If I edit the page again I get Content-Encoding:gzip but only one time.
I don't know if there is something wrong with my express setup.
NOTE: I serve my page as: res.sendfile(__dirname + '/www/index.html');
If I edit the page again I get Content-Encoding:gzip but only one time. I don't know if there is something wrong with my express setup.
All is well. The first time your server sends the file with gzip compression. The second time the normal etag cacheing mechanism comes into play, and since the file has not been modified, the server tells the browser "you already have the right version of this file" and thus there is no need for the server to send a response body at all, just headers, thus no need for any Content-Encoding header.

HTML5 <audio> Safari live broadcast vs not

I'm attempting to embed an HTML5 audio element pointing to MP3 or OGG data served by a PHP file . When I view the page in Safari, the controls appear, but the UI says "Live Broadcast." When I click play, the audio starts as expected. Once it ends, however, I can't start it playing again by clicking play. Even using the JS API on the audio element and setting currentTime to 0 fails with an index error exception.
I suspected the headers from the PHP script were the problem, particularly missing a content length. But that's not the case. The response headers include a proper Content- Length to indicate the audio has finite size. Furthermore, everything works as expected in Firefox 3.5+. I can click play on the audio element multiple times to hear the sound replay.
If I remove the PHP script from the equation and serve up a static copy of the MP3 file, everything works fine in Safari.
Does this mean Safari is treating audio src URLs with query parameters differently than URLs that don't have them? Anyone have any luck getting this to work?
My simple example page is:
<!DOCTYPE html>
<html>
<head></head>
<body>
<audio controls autobuffer>
<source src="say.php?text=this%20is%20a%20test&format=.ogg" />
<source src="say.php?text=this%20is%20a%20test&format=.mp3" />
</audio>
</body>
</html>
HTTP Headers from PHP script:
HTTP/1.x 200 OK
Date: Sun, 03 Jan 2010 15:39:34 GMT
Server: Apache
X-Powered-By: PHP/5.2.10
Content-Length: 8993
Keep-Alive: timeout=2, max=98
Connection: Keep-Alive
Content-Type: audio/mpeg
HTTP Headers from direct file access:
HTTP/1.x 200 OK
Date: Sun, 03 Jan 2010 20:06:59 GMT
Server: Apache
Last-Modified: Sun, 03 Jan 2010 03:20:02 GMT
Etag: "a404b-c3f-47c3a14937c80"
Accept-Ranges: bytes
Content-Length: 8993
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: audio/mpeg
I tried hard-coding the Accept-Ranges header into the script too, but no luck.
Can you post the headers sent by the server both with and without the PHP script? I'm wondering if the PHP script is sending a different Content-Type than serving the files normally.
It would also be a good idea to specify the type attribute on the source elements, so the browser does not have to download both clips to determine if it can play them.
I cannot reproduce your problem. I have tried to recreate the problem in Safari 4.0.4, and the current WebKit nightly, with the following test page. I am simply using mod_rewrite to dispatch to different formats based on a parameter instead of PHP, but I don't think that should make a difference, unless somehow PHP is modifying the file.
<!DOCTYPE html>
<title>Auido test</title>
<audio controls autobuffer>
<source src="gnossienne-no-1?foo=bar&format=.mp4">
<source src="gnossienne-no-1?foo=bar&format=.ogg">
</audio>
Can you try my example out and let me know if it works for you?
edit Ah. After poking around at it a bit more, it appears that the problem is due to an odd way that the <audio> element in Safari behaves in attempting to determine the size of the content.
Here's an excerpt from a packet capture of Safari upon encountering an <audio> element pointing to a file served directly from Apache. As you can see, it first tries to fetch the first two bytes of the media, presumably so it can get a Content-Length back, and possibly other headers. It then tries to fetch the whole thing. Then, inexplicably, it tries to fetch the first two bytes again, but passes appropriate caching headers to get a "304 Not Modified" response. And finally, still inexplicably, it fetches the last 3440 bytes of the file all over again. It does all of these in separate TCP connections, which adds considerable overhead, in addition to the overhead of fetching the data a couple of times.
GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1
Host: ephemera.continuation.org
Range: bytes=0-1
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
Accept-Encoding: identity
Cookie: [redacted]
HTTP/1.1 206 Partial Content
Date: Tue, 05 Jan 2010 02:12:48 GMT
Server: Apache
Last-Modified: Tue, 05 Jan 2010 02:02:08 GMT
ETag: "b2a80ad-11f6-47c6139aaa800"
Accept-Ranges: bytes
Content-Length: 2
Content-Range: bytes 0-1/4598
Connection: close
Content-Type: audio/mpeg
# 2 bytes of data
GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1
Host: ephemera.continuation.org
Range: bytes=0-4597
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
Accept-Encoding: identity
Cookie: [redacted]
HTTP/1.1 206 Partial Content
Date: Tue, 05 Jan 2010 02:12:48 GMT
Server: Apache
Last-Modified: Tue, 05 Jan 2010 02:02:08 GMT
ETag: "b2a80ad-11f6-47c6139aaa800"
Accept-Ranges: bytes
Content-Length: 4598
Content-Range: bytes 0-4597/4598
Connection: close
Content-Type: audio/mpeg
# 4598 bytes of data
GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1
Host: ephemera.continuation.org
Range: bytes=0-1
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
Accept-Encoding: identity
Cookie: [redacted]
If-None-Match: "b2a80ad-11f6-47c6139aaa800"
If-Modified-Since: Tue, 05 Jan 2010 02:02:08 GMT
HTTP/1.1 304 Not Modified
Date: Tue, 05 Jan 2010 02:12:49 GMT
Server: Apache
Connection: close
ETag: "b2a80ad-11f6-47c6139aaa800"
# no data
GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1
Host: ephemera.continuation.org
Range: bytes=1158-4597
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
Accept-Encoding: identity
Cookie: [redacted]
HTTP/1.1 206 Partial Content
Date: Tue, 05 Jan 2010 02:12:49 GMT
Server: Apache
Last-Modified: Tue, 05 Jan 2010 02:02:08 GMT
ETag: "b2a80ad-11f6-47c6139aaa800"
Accept-Ranges: bytes
Content-Length: 3440
Content-Range: bytes 1158-4597/4598
Connection: close
Content-Type: audio/mpeg
# 3440 bytes of data
Anyhow, on to how it deals with the output of your PHP script. Here, Safari again tries to download the first two bytes, but your script ignores the Range request and returns the whole thing. Apparently, WebKit doesn't like that, and so it tries again, without the Range request. Again, your script sends the full contents. Safari now tries once more, adding an Icy-Metadata header, which indicates it thinks that it's downloading a stream and wants streaming metadata to be sent. It finally accepts the output of that, and the <audio> element can play.
GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1
Host: tts.mindtrove.info
Range: bytes=0-1
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
Accept-Encoding: identity
HTTP/1.1 200 OK
Date: Tue, 05 Jan 2010 02:14:28 GMT
Server: Apache
X-Powered-By: PHP/5.2.10
Content-Length: 4598
Connection: close
Content-Type: audio/mpeg
# 4598 bytes of data
GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1
Host: tts.mindtrove.info
Connection: close
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Accept: */*
HTTP/1.1 200 OK
Date: Tue, 05 Jan 2010 02:14:28 GMT
Server: Apache
X-Powered-By: PHP/5.2.10
Content-Length: 4598
Connection: close
Content-Type: audio/mpeg
# 4598 bytes of data
GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1
Host: tts.mindtrove.info
Accept: */*
User-Agent: Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540
Icy-Metadata: 1
Connection: close
HTTP/1.1 200 OK
Date: Tue, 05 Jan 2010 02:14:28 GMT
Server: Apache
X-Powered-By: PHP/5.2.10
Content-Length: 4598
Connection: close
Content-Type: audio/mpeg
# 4598 bytes of data
In summary, it appears that Safari (or more accurately, QuickTime, which Safari uses to handle all media and media downloading) has a completely braindamaged approach to downloading media. Something in the way you send your data back, probably the fact that you don't respond to Range requests, makes it think that you are sending streaming media, causing it to download the content repeatedly (though even when confronted with a server that does respond to a Range request, it still does several more requests than it really needs to).
My advice would be to try to respond appropriately to Range requests; when serving up media, browsers will likely use them to try to minimize bandwidth, by only buffering as much as they need to be able to play through (although have the autobuffer attribute which indicates that you would like them to buffer the whole thing, browsers may ignore that). I would recommend using X-Sendfile to let Apache deal with serving the file, caching, and range requests, but you appear to be on Dreamhost, which doesn't have mod_xsendfile installed, so you're going to have to roll your own Range handling.
Old topic but still valid in 2019. We finally found a solution... Below PHP script sample will consider Safari's "Range" header request.
$bytes_total = strlen($data);
if (isset($_SERVER['HTTP_RANGE'])) {
$byte_range = explode('-',trim(str_ireplace('bytes=','',$_SERVER['HTTP_RANGE'])));
$byte_from = $byte_range[0];
$byte_to = ($byte_range[1]+1);
$data = substr($data,$byte_from,$byte_to);
header('HTTP/1.1 206 Partial Content');
}
else {
$byte_from = 0;
$byte_to = $bytes_total;
}
$length = strlen($data);
header('Content-type: '.$content_type);
header('Accept-Ranges: bytes');
header('Content-length: ' . $length);
header('Content-Range: bytes '.$byte_from.'-'.$byte_to.'/'.$bytes_total);
header('Content-Transfer-Encoding: binary');
print($data);
For the record, while both Pochang and Chris are correct that you need the Content-Range header to fix this problem in Safari, Chrome requires one extra header that must be included for setting currentTime to work properly:
header( 'Accept-Ranges: bytes');
Note that you don't actually have to respond correctly to the request's Range header, you just have to include this in the response.
I got the same problem.
The key point is the Content-Range header.
Everything works fine after I add it to the mp3-output php.
Pochang is correct. Including a Content-Range header in the PHP response will cause Safari to behave properly. It also allows seeking (media.currentTime = 0;) without the dreaded INDEX_SIZE_ERR in Safari, though not in Chrome.
The PHP code for the header is:
$len = strlen( $data );
$shortlen = $len - 1;
header( 'Content-Range: bytes 0-'.$shortlen.'/'.$len);
And in 2020 it is still actual question.
Simply adding Content-Range header does not work.
Bellow is my implementation(based on some answers here).
$content_length = strlen($media_total);
$total_bytes = $content_length;
$content_length_1 = $content_length - 1;
if (isset($_SERVER['HTTP_RANGE'])) {
$byte_range = explode('-',trim(str_ireplace('bytes=','',$_SERVER['HTTP_RANGE'])));
$byte_from = $byte_range[0];
$byte_to = intval($byte_range[1]);
$byte_to = $byte_to == 0 ? $content_length_1 : $byte_to;
$media_total = substr($media_total,$byte_from,$byte_to);
$content_length = strlen($media_total);
header('HTTP/1.1 206 Partial Content');
}
else {
$byte_from = 0;
$byte_to = $content_length_1;
}
$content_range = 'bytes '.$byte_from.'-' . $byte_to . '/' . $total_bytes;
header('Accept-Ranges: bytes');
header("Content-Range: ".$content_range);
header("Content-type: ".$type);
header("Content-length: ".$content_length);
header('Content-Transfer-Encoding: binary');
echo $media_total;
exit;
Original question here: Timing problem for generated audio in some browsers

Resources