Caching a GET request for audio with PHP - php

I have a PHP script that responds to a GET request for audio resources. An HTML5 Audio tag requests an audio file such as:
<audio src="get_audio.php?f=fun" preload></audio>
There is no need for the user to download that same audio file every time so I would like to cache it. In my PHP file I have:
header("Cache-Control: max-age=2419200");
header("Content-Type: audio/mpeg");
...
echo file_get_contents($path);
but when I view the Network tab of Chrome developer tools I see that it re-downloads the audio clip everytime rather than saying "from cache" and if I look in the Response headers I do see the Cache-Control header that I set. Why would it ignore this? Amidoingitright?

It's been a while since I did this in PHP, but try adding:
header("Pragma: public");
above the cache-control header.
I also think you need the expires header:
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+2419200) . ' GMT');
Other than that you could start using the get_headers() function in PHP to debug where it's going wrong.

Do you see a header in the second request called If-Modified-Since:?
This is what you need to catch, parse and respond to - if you don't want to send the file again, you respond with HTTP/1.1 304 Not Modified. If you are using Apache, you can check for the header in the result of apache_request_headers(). If you are not using Apache, you may find it hard to handle this - you will probably have to find a way for the web server to set the headers as environment variables, so they are available in $_ENV or $_SERVER. There is a way to do this in Apache using mod_rewrite (see latest comment on page linked above), so there is probably a way to do it in other server environments as well.
Caching in HTTP 1.1 permits (and indeed, encourages) this behaviour - it means that the cached copy will always be up-to-date.

Related

Do I need to define a mime type in this header?

I am working with scanning images.
Today I made a step forward but I think that I need something more in the header maybe?
Currently a scan request is POSTed to app running on Apache server and PHP 7.0. I receive that post and today I figured out I needed to reply with a custom 201 header.
I have the following custom headers set in PHP
header('HTTP/1.1 201', Created);
header('Location: /eSCL/Scans');
header('Cache-Control: no-cache, no-store, must-revalidate');
After doing I was able to see that a request is now made for an image, from the app to Apache server to /eSCL/Scans/NextDocument. "NextDocument" is actually a softlink to a .jpg, (but may also later be a PDF). The app GETs this file, I can also see that the server replies with the JPG file . That image should display in the scan app that made the request, but never does.
I think however I need something in the header to tell it it is a JPG , especially given it loads from a softlink with no extension . I tried the following with the same result
header('HTTP/1.1 201', Created);
header('Content-type:image/jpeg');
header('Location: /eSCL/Scans');
header('Cache-Control: no-cache, no-store, must-revalidate');
There must be a magic soup that causes the image to display in the app.I have tried both VueScan and Mopria android app with the same results
Thanks for any ideas in advance

PHP file cache triggers a Internal 307 == insecure warning

In a PHP 7.1 app I load files from a php file "filecache.php" - it works fine in regards of returning a cachable file.
But the thing that drives me crazy is that the images triggers a Insecure content warning in Chrome and FF (perhaps others also).
The files are loaded using a tag, I tried relative url and this gives me the error. I've also tried using complete url https://example.com/filecache?f=0983490842'> same error.
The server uses HSTS and LetsEncrypt cert - no http traffic allowed.
When I examine the Network activity in Chrome (and FF) I can see that the browser tries to retrieve a file using https, finds it in the cache which gives a 307 internal redirect - but to a http url - finally ending up with the image loaded over https from the cache. Well at least thats how I read the info below.
Any input or pointers will be greatly appreciated!
filecache.php
if(file_exists($file)){
if(substr($_GET["f"],-3)=="jpg") Header("Content-Type: image/jpeg");
if(substr($_GET["f"],-3)=="png") header("Content-Type: image/png");
header('Cache-control: max-age='.(60*60*24*365));
header('Expires: '.date("Y-m-d H:i:s",strtotime("+365 days")));
header('Last-Modified: '.gmdate(DATE_RFC1123,filemtime($file)));
readfile($file);
}else{
die("no such file");
}

PHP: filesize() stat failed with link

I'm randomly getting download errors from a link on a page. I also simplified the link to a directory for easy usage in emails for users.
On the main page the link looks like this:
a href="http://myPage.com/Mac" target="_blank" id="macDownloadButton" class="downloadbutton w-button">Download Mac version</a>
On my server, that's a directory with an index.php in it which looks like this:
<?php
// mac version
$file="http://www.myPage.com/downloads/myApp_Mac.zip";
$filename="myApp_Mac.zip";
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Content-Length: ' . filesize($file));
header('Content-Encoding: none');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename=' . $filename);
readfile($file);
exit;
?>
Again, the reason I do this is so it's a simple link to send to users in email like, "http://myPage.com/Mac" and "http://myPage.com/Windows".
The weird thing is that it mostly works...but sometimes it doesn't.
What am I doing wrong?
It's hard to know precisely what's wrong unless you check for errors on your readfile() call.
But you're invoking your web server from your web server here when you specify a filename starting with http. You're doing
readfile('http://www.myPage.com/downloads/myApp_Mac.zip');
where you could just as easily do
readfile('../downloads/myApp_Mac.zip');
and read the zip file from the local file system to send to your user.
What's more, filesize('../downloads/myApp_Mac.zip'); will yield a numerical value quickly and send it in the Content-Length header. That will allow the browser, by knowing the total size of the file you're sending, to display a meaningful progress bar.
You should remove the Accept-Ranges header; the php program you showed us doesn't honor range requests. If you lie to the browser by telling it you do honor those requests, the browser may get confused and corrupt the downloaded copy of your file. That will baffle your user.
Your Content-Disposition header is perfect. It defines the filename to be used on your user's machine in the downloads folder.
Simple operations are more reliable, and this may help you.
The reason you got stat failed with link as an error message is this: stat(2) is a operating-system call that operates on files in local and mounted file systems.
As previously mentioned by O. Jones you should definitely always use your local file path.
Most of my previous issues have been mostly browser related where I needed to tweak/add a http header, and in one case I needed to send all the HTTP headers in lowercase but I haven't had an issue like that in years. My personal recommendation would be to use a solid download library/function - it will make a noticeable difference to your productivity as well as rule out most browser related issues you may come across.
I have used the codeIgniter download helper for the last 3 years and recommend it for 99% of use cases. At the very least I would recommend your read through it's code - you will probably find a few cases you have never even considered such as clearing the output buffer,mime detection and even a special case for Android 2.1 as well as a few headers you may or may not need.
If all else fails I have no idea what server your running this on but if you continue to have issues I would recommend monitoring which processes your machine is running while paying close attention to ram and IO usage. I've have encountered bad/misbehaving services that run periodically using 99% of my IO or ram for short intervals at a time that caused a few really odd and unexpected errors.

How can I stop X-Sendfile from serving the full video file when IE9 makes the request?

I ran into an issue where regardless of the preload attribute setting, when IE9 makes a request for a video, and the video is served by x-sendfile, the request is listed as pending and keeps the connection open.
Consequently, if you have 10 videos trying to load, IE9 will quickly eat up all of its available connections and the browser will not be able to make further requests.
When telling IE9 to request the same video from Apache, without X-Sendfile, Apache serves a small portion of the file as a 200 request. Then the browser makes a request later when the play button is pressed to serve a range of the file.
It looks like X-Sendfile is causing Apache to serve the entire file initially, instead of serving just a part of it.
How can I make X-Sendfile requests via Apache function the same as a regular request to Apache?
Setting the "Accept-Ranges" header like header("Accept-Ranges: bytes"); tells IE9 to attempt to stream the file by default, instead of serve it in one chunk.
It's recommended to check that the HTTP request is version 1.1 though before setting, since 1.0 doesn't support the header.
if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1') {
header("Accept-Ranges: bytes");
}
I wasn't able to find any documentation on this anywhere, so I'm posting my solution here.

simultaneously download on server and on user

I am currently developing an application in PHP in which my server (a dedicated server) must to download a file, and the user should download the file in same time.
Here is an example :
Server start to download a file at a time A.
User wants to download this file at the time A + 3 seconds (for example)
I already solved the problem :"If the user downloads the file faster than the server..". But I didn't know how to make a php script in which the user is gonna to download the full file (it means that the size must be the full size of the file, not the size it's currently downloaded at the time A+3seconds). I already make that :
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$data['name'].'";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$data['size']);
readfile($remoteFile);
But it doesn't work, the user is gonna download just the size it is currently on the server (which corrupt the file) and not the full file...
If you have any solution, thank you.
You could probably pipe the file manually, by opening the connection and reading until you're past all headers. Then once you've figured out the Content-Length, send that to the user and just echo all remaining data you get (do use flush() and avoid output buffers).
Pseudocode(-ish):
open the file
# grab headers
while you didn't get all HTTP headers:
read more
look for the Content-Length header
send the Content-Length header
# grab the file
while the rest of the request isn't done
read more
send it to the user
flush the buffers
done
Expanding on #Tom answer, you can use cURL to greatly simplify the algorithm by using the CURLOPT_HEADERFUNCTION and CURLOPT_READFUNCTION callbacks - see curl_setopt().
Don't send the content-length header. It's not required assuming you're using http 1.1(your webserver almost certainly does). Drawback is their browser cant show download time/size remaining.

Categories