Kirk Ouimet
Kirk Ouimet

Reputation: 28404

Optimize PHP for Serving All Files (And Not Apache)

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

Answers (9)

Teson
Teson

Reputation: 6736

What you could to is:

  • Upon login create a random symblink (md5..) pointing at your protected resources.
  • Pass that path to the client
  • Some logic to delete that symblink on logout or timeout.

It's only half secure, esp. if path passed without https.

OR:

  • Have a directory in apache protected by IP.
  • Have PHP maintaining that directory entry, (adding and deleting allowed IP's)
  • (you need to "service httpd reload" to have the changes applied)

Upvotes: 0

symcbean
symcbean

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

Ben
Ben

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

Artefacto
Artefacto

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

KageUrufu
KageUrufu

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

Pekka
Pekka

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

The Surrican
The Surrican

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

Christopher Tarquini
Christopher Tarquini

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

alexantd
alexantd

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

Related Questions