Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine the Content-Length of a gzipped file?

Right now I'm trying to serve CSS and JS files from a server that won't let me enable mod_gzip or mod_deflate. So I wrote a small PHP script to compress with GZIP and return to the user.

Example Code:

$filename = "style.css";

if (!file_exists($filename) || !($info = stat($filename))) {
  header("HTTP/1.1 404 Not Found");
  die();
}

header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("Etag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Cache-Control: Expires ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/html");

ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();

I'm having two problems right now. The first is, I'm having trouble determining the resulting size of the compressed file to inform the browser of the content-length. Normally, I would include this line:

header("Content-Length: ".$info["size"]);

But, if I do, the browser hangs while trying to wait for more data. Is there a way to calculate the total size? Or should I ignore this header directive.

The other issue is, whenever I view this PHP file in Firefox, it tries to have me download the result. In Chrome, it just displays it like I would expect. Any suggestions?

Edit: Thanks to SoapBox, I replaced the end of the code with this:

header("Content-Encoding: gzip");
$compressed = gzencode(file_get_contents($filename), 5);
header("Content-Length: ".strlen($compressed));
die($compressed);

This works great for the content-length! But I'm still getting Firefox to download the file instead of display it. :(

Edit Again: Here is the modified end-of-code code, courtesy of Cletus.

// Start buffered output
ob_start();
// Check for gzip capability
if (stripos($_SERVER['HTTP_ACCEPT_ENCODING'], "gzip") !== false) {
  ob_start("ob_gzhandler");
  echo file_get_contents($filename);
  ob_end_flush();
} else
  echo file_get_contents($filename);

// Write the content length
header('Content-Length: '.ob_get_length());
ob_end_flush();

I'm going to start a new question to figure out why Firefox keeps trying to download the file.

like image 285
St. John Johnson Avatar asked May 03 '09 00:05

St. John Johnson


People also ask

How do I check the size of a gzip file?

For each file, compute the ratio in sizes between gzip -c "$f" | wc -c and wc -c "$f" The average of those ratios is an approximation of the compression you should expect for a similar JS file.

What is content length of a file?

The Content-Length header indicates the size of the entity body in the message, in bytes. The size includes any content encodings (the Content-Length of a gzip-compressed text file will be the compressed size, not the original size).

How do I know if content is Gzipped?

You can tell using Developer Tools (F12). Go to the Network tab, select the file you want to examine and then look at the Headers tab on the right. If you are gzipped, then you will see that in the Content-Encoding.

What is Gzipped size?

"gzip" is often also used to refer to the gzip file format, which is: a 10-byte header, containing a magic number ( 1f 8b ), the compression method ( 08 for DEFLATE), 1-byte of header flags, a 4-byte timestamp, compression flags and the operating system ID.


2 Answers

The problem here is that to know the content length you need to know whether or not the client supports gzip encoding and you've delegated that decision by using ob_gzhandler. From HTTP Headers:

ob_start();
ob_start('ob_gzhandler');

  ... output the page content...

ob_end_flush();  // The ob_gzhandler one

header('Content-Length: '.ob_get_length());

ob_end_flush();  // The main one

Complete version:

$filename = "style.css";

if (!file_exists($filename) || !($info = stat($filename))) {
  header("HTTP/1.1 404 Not Found");
  die();
}

header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("ETag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Expires: ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/css"); // note: this was text/html for some reason?

ob_start();
ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();

This is much better than taking on the gzip encoding issue yourself.

like image 117
cletus Avatar answered Sep 30 '22 20:09

cletus


You need to first do the entire gzip and measure the result (either holding the contents in memory, or writing them to disk as you compress and then stat'ing the gzipped file), then write the Content-Length header and then send the file contents.

Or use chunked transfer encoding.

like image 35
Jeremy Huiskamp Avatar answered Sep 30 '22 18:09

Jeremy Huiskamp