Sunday, April 27, 2014

PHP Date vs. gettimeofday

I just had a lot of trouble with a unit test failing. Unfortunately lots of things had changed: new computer, new linux distro, new version of PHP (from 5.3 to 5.5), new version of PHPUnit (from 3.4 all the way to 4.0 then back to 3.7), etc.

I eventually tracked it down; and made this minimal example:

for($n=0;$n<250;++$n){
        $t = time();
        $s = date("Y-m-d H:i:s");
        $tod = gettimeofday();
        echo "$t,$s,{$tod['sec']},{$tod['usec']},".
          date("Y-m-d H:i:s")."\n";
    usleep(10000);  //0.01s
    }

It is getting the time 100 times a second, and comparing with time(), date() and gettimeofday(). I’ll just show the interesting bit:

1398601641,2014-04-27 21:27:21,1398601641,972424,2014-04-27 21:27:21
1398601641,2014-04-27 21:27:21,1398601641,982545,2014-04-27 21:27:21
1398601641,2014-04-27 21:27:21,1398601641,992669,2014-04-27 21:27:21
1398601641,2014-04-27 21:27:21,**1398601642**,2793,2014-04-27 21:27:21
1398601642,2014-04-27 21:27:22,1398601642,12919,2014-04-27 21:27:22
1398601642,2014-04-27 21:27:22,1398601642,23041,2014-04-27 21:27:22

I’ve highlighted the problem point. It appears gettimeofday() is slightly ahead of time() (and date() is always consistent with time()). More precisely, for the first 10000 microseconds of each second, time() is still in the previous second.

My unit test was watching gettimeofday() to wait for a new second to roll by. But the code being tested was using date to get a timestamp. (That timestamp was then used as a unique ID for a database insert, and the unit test asserted it was always an insert, not an update!)

Fascinating stuff, eh? I’ve no idea if this is a behaviour change in PHP, or if this new machine is simply a bit quicker, and the problem never cropped up before. However I doubt the latter as the unit test now fails 100% of the time, but never failed this way before.

Oh, the fix? Simply changed the calls to gettimeofday() to time(). To be honest I’ve no idea why I used the more complicated function in the first place.

Friday, April 11, 2014

Using RewriteRule instead of Alias in .htaccess

Subtitle: Why aren't Apache rewrites working?!?!?!?!

I had a few directories (e.g. each a mini website or webapp) sharing a fonts directory, and I was doing this with symlinks. But symlinks are a pain to rsync, so I thought let's use Apache to do the symlinking, i.e. use Alias. I put this in .htaccess:

  Alias /mysite/fonts/ ../shared/fonts/

500 Internal Server Error.

Or, in other words, Alias cannot be used in .htaccess, only in httpd.conf. I could've done it that way (added the Alias to httpd.conf) but as I was already using RewriteRule in the .htaccess file I decided to persevere finding a way to do it using just the .htaccess file.

This was my first attempt:

   RewriteRule ^/mysite/fonts/(.*)$ /shared/fonts/$1  [L]

Completely ignored. Now, when you start googling, or searching on StackOverflow, for "Apache rewrite does not work", 99% of the hits will tell you how to turn it on. I.e. you need AllowOverride All in your httpd.conf, which allows you to use .htaccess files, and you need Options FollowSymLinks so that the rewrite engine will work. And you need RewriteEngine On in your .htaccess file.


Unfortunately I'd done all those things. I knew the rewrite engine was working, as I had other rules in this same .htaccess file, and commenting them out changed behaviour. My problem was that my new rewrite rule was just being ignored, even though it looked correct.


I'll spare you the pain of seeing the 187 other experiments I tried. The forehead-slap moment came when I realized the first parameter of the rewrite rule is relative to the directory the .htaccess file is in. I.e. my .htaccess file is in the /mysite/ directory. And, thus, the correct rule turned out to be:

   RewriteRule ^fonts/(.*)$ /shared/fonts/$1  [L]

Note the lack of leading forward slash. Very important. Incidentally, the second parameter is relative to the server root if you start it with a forward slash! In other words, it is only the first parameter that doesn't work that way.

The [L] at the end of the RewriteRule means "last", and is normally what you want for this kind of rewrite rule. Use [R] ("redirect") instead if you wanted the URL to be rewritten (i.e. an HTTP redirect to be sent back). Useful troubleshooting tip: if you set the flag to [F] then you can immediately see if the first parameter to the rule is being recognized: if it is getting matched, you get a 403 (and if you don't then you have to fix the first parameter). If you get a 403 with an [F], but don't get the expected results when you change the the [F] to an [L] then it is the second parameter you need to fix.

So, to summarize, if your .htaccess file is in a subdirectory, remember that the RewriteRule has to be relative to that subdirectory.