views:

82

answers:

8

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;
    }
}
+1  A: 

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]
Chris T
Ahh, I actually want requests for files that do exist to be passed through PHP (for authentication purposes)
Kirk
Does this update help? Unless you bypass PHP completely there's not much you can do for speed I'm afraid. You could use `readfile` when it's not a partial request to speed things up a little but I feel like it's not the way to go.
Chris T
A: 

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.

alexantd
Thought about this as I was coding - really I just need to run some code to verify the user (stored in a database) has access to the resource. Apache doesn't have this functionality (at least I don't think it does...)
Kirk
+1  A: 

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.

Pekka
A: 

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

Joe Hopfgartner
A: 

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.

KageUrufu
+1  A: 

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.

Artefacto
+1  A: 

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..

Ben
+1  A: 

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.

symcbean
Thanks for the insightful answer!
Kirk