13

I want to make an etag that matches what Apache produces. How does apache create it's etags?

Stu Thompson
  • 38,370
  • 19
  • 110
  • 156
Chris Bartow
  • 14,873
  • 11
  • 43
  • 46

4 Answers4

11

Apache uses the standard format of inode-filesize-mtime. The only caveat to this is that the mtime must be epoch time and padded with zeros so it is 16 digits. Here is how to do it in PHP:

$fs = stat($file);
header("Etag: ".sprintf('"%x-%x-%s"', $fs['ino'], $fs['size'],base_convert(str_pad($fs['mtime'],16,"0"),10,16)));
Chris Bartow
  • 14,873
  • 11
  • 43
  • 46
  • 1
    Why not `header(sprintf('Etag: "%x-%x-%016x"', $fs['ino'], $fs['size'], $fs['mtime']));`? – Alix Axel Jun 11 '11 at 09:35
  • 1
    I noticed that the MTime part of the ETag of my Apache server actually has a much preciser MTime, it uses the microseconds as well. So if you want to generate the ETag exactly as Apache does it you'll need the microtime of the mtime. To my knowledge this isn't possible in PHP. What you can do however is check if Apache's MTime is _close enough_ (<1 second), so you can at least return a `304 Not Modified`. – Halcyon Feb 06 '12 at 13:20
2

One thing to remember about Apache's Etags is that they don't play well in clusters because they include inode information that can—and probably will—vary between machines in the same cluster.

Hank Gay
  • 70,339
  • 36
  • 160
  • 222
1

If you're dynamically generating your page though, this probably won't make sense. If you're in PHP, you can pick the inode and file size of the main script, but the modify time won't tell you if your data has changed. Unless you have a good caching process or just generate static pages, etags aren't helpful. If you do have a good caching process, the inode and file size are probably irrelevant.

Edit: For people who don't know what etags are - they're just supposed to be a value that changes when the content has changed, for caching purposes. The browser gets the etag from the web server, compares it to the etag for its cached copy and then fetches the whole page if the etag has changed.

Neall
  • 26,428
  • 5
  • 49
  • 48
  • This approach can be useful for things like download scripts which just stream files through PHP and other situations where responses aren't actually dynamic per-request. – Matt Kantor Sep 16 '12 at 23:10
1

the answer above (from Chris) works well, but can be simplified using an implicit cast in the sprintf:

sprintf('"%x-%x-%x"', $s['ino'], $s['size'], str_pad($s['mtime'], 16, "0"));

The suggested %016x doesn't work because the padding is applied after the conversion to hex, rather than before.

PWolanin
  • 11
  • 1
  • This does **not** work, I'm not sure why, maybe because %x needs an `int` as input? – Halcyon Feb 06 '12 at 13:06
  • 1
    @FritsvanCampen: it turns out to be a bit more complicated than that. `sprintf` will attempt to cast the string to an `int`, but since these numbers are so large they overflow and cause problems. The solution is to use `float` instead: `sprintf('"%x-%x-%x"', $s['ino'], $s['size'], (float) str_pad($s['mtime'], 16, '0'))`. – Matt Kantor Sep 16 '12 at 23:02