Reputation: 28404
I have a need to send all requests for any web resource through PHP for user authentication purposes, and to not serve any files directly through Apache. Here's my .htaccess:
# All requests are routed to PHP (images, css, js, everything)
RewriteRule ^(.*)$ index.php?query=$1&%{QUERY_STRING} [L]
I then process the request, verify the user has access to the resource, and then output any file that does not require processing using the following PHP read function. It turns out that this is incredibly slow compared to just letting Apache do its thing.
Can anyone recommend a way to help me improve performance?
static function read($path) {
if(!File::exists($path)) {
//echo 'File does not exist.';
header("HTTP/1.0 404 Not Found");
return;
}
$fileName = String::explode('/', $path);
if(Arr::size($fileName) > 0) {
$fileName = $fileName[Arr::size($fileName) - 1];
}
$size = File::size($path);
$time = date('r', filemtime($path));
$fm = @fopen($path, 'rb');
if(!$fm) {
header("HTTP/1.0 505 Internal server error");
return;
}
$begin = 0;
$end = $size;
if(isset($_SERVER['HTTP_RANGE'])) {
if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[0]);
if(!empty($matches[1]))
$end = intval($matches[1]);
}
}
if ($begin > 0 || $end < $size)
header('HTTP/1.0 206 Partial Content');
else
header('HTTP/1.0 200 OK');
// Find the mime type of the file
$mimeType = 'application/octet-stream';
//$finfo = @new finfo(FILEINFO_MIME);
//print_r($finfo);
//$fres = @$finfo->file($path);
//if(is_string($fres) && !empty($fres)) {
//$mimeType = $fres;
//}
// Handle CSS files
if(String::endsWith('.css', $path)) {
$mimeType = 'text/css';
}
header('Content-Type: '.$mimeType);
//header('Cache-Control: public, must-revalidate, max-age=0');
//header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:' . ($end - $begin));
header("Content-Range: bytes $begin-$end/$size");
header("Content-Disposition: inline; filename=$fileName");
header("Content-Transfer-Encoding: binary\n");
header("Last-Modified: $time");
header('Connection: close');
$cur = $begin;
fseek($fm, $begin, 0);
while(!feof($fm) && $cur < $end && (connection_status() == 0)) {
print fread($fm, min(1024 * 16, $end - $cur));
$cur += 1024 * 16;
}
}
Upvotes: 0
Views: 536
Reputation: 6736
What you could to is:
It's only half secure, esp. if path passed without https.
OR:
Upvotes: 0
Reputation: 48387
First off - your code can only serve up application/octet-stream and text/css, and you're trying to force a download for everything.
Can anyone recommend a way to help me improve performance?
Yes
gzip as mentioned elsewhere (but be selective about it and cache compressed versions serverside)
use mod_php or fastCGI in preference to CGI
use an opcode cache such as APC
send some caching information (but if you need authentication for everything, include a Varies: Cookies header)
use fpassthru instead of the while loop
use a reverse-proxy
do the authentication on the reverse proxy (simple using squid's url-rewriter)
There are other ways to solve the problem without using rewrite (e.g. 404 handler, auto-prepend on all content types) - you can occasnally get internal recursion issues with mod_rewrite.
Upvotes: 1
Reputation: 21249
To add to Joe Hopfgartner's answer..
Yes, output buffering will likely help, but you should check in the client accepts gzipped responses, if it does, then you should gzip the buffer as well, the response will be a lot small, hence faster. Check the php.net website docs on ob_* functions or google on how to gzip your content.
Also,
You should call set_time_limit(0). If your script takes too long to run it will abort transfer half way through, and you'll get a corrupt response.
You should also probably be selective on what you serve, it probably doesn't matter to restrict files like css..
Upvotes: 1
Reputation: 97845
The best you can do, if using PHP as an Apache module, is to call virtual
.
Otherwise, you'll have to settle for readfile
.
Another possibility is to bypass PHP completely and use Apache's authorization, authentication and access control facilities. You can even write an Apache module with your own authentication/authorization logic if it becomes necessary.
Upvotes: 2
Reputation: 494
Use mod_mysql_auth for apache. it does pretty much what it looks like you are trying to do look at mod Auth MySQL Under Apache 2 and Debian as an example.
I've only used this once, but it seemed to work great for me.
Upvotes: 0
Reputation: 449783
It turns out that this is incredibly slow compared to just letting Apache do its thing.
I don't think there is much that can be done to speed up the operation at this low level. Sadly, there is no easy way to have PHP perform a "logged in" check, then pass the actual serving of the file on to Apache.
Other web servers like nginx are said to have more possibilities in this field. Maybe worth checking out.
I asked essentially the same question some time ago. Maybe there are some ideas in the feedback. I did implement a variation of the accepted answer in the project I asked this for.
Upvotes: 1
Reputation: 29874
there are far better solutions to your problem.
but your answer: use output buffering ob_start(); flush(); it will increase your performance.
but here you have a better approach:
for authenticated users serve a link that includse a current timestamp and a hash of the filename/id, the timestamp and a secret token. maby like so. $link = $timetamp."/".md5($secret_token.$timestamp.$filename)."/".$filename.
then in your rewrite map pass those arguments and re-create the hash with the secret token on your server, the passed timestamp and the file. if the hash matches, return the path of the file, if its timed out or is invalid an error page.
this way you can directly serve files secured over apache. you acn also include an ip.
you could as well check out lighttpd where something like taht already exists in a module called mod_secdownload. i think there are also plugins for apache and i am sure for nginx
some link for you:
http://redmine.lighttpd.net/wiki/1/Docs:ModSecDownload http://httpd.apache.org/docs/current/mod/mod_rewrite.html
Upvotes: 0
Reputation: 11342
You could use apaches rewrite rules to route the request through PHP ONLY if the requested file does not exist. This way if you have a request for /images/logo.png
and it exists apache will just serve it like normal.
This might work (I'm a bit rusty and I hate mod_rewrite rules :P)
# Route requests through PHP if the file does not exist
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?query=$1&%{QUERY_STRING} [L]
UPDATE:
You can also just define what directories contain static content and exclude them
RewriteCond %{REQUEST_URI} !^/images/.* [AND]
RewriteCond %{REQUEST_URI} !^/css/.*
RewriteRule ^(.*)$ index.php?query=$1&%{QUERY_STRING} [L]
Upvotes: 1
Reputation: 3605
It looks like you're trying to write your own little web server there. All of what you're trying to do, Apache can already do, a lot more efficiently. I would recommend reconsidering your approach.
Upvotes: 0