PHP, WMI and Microsoft NLB

I’ve been playing with my NLB cluster, and wanted to find a way to evict nodes which were failing some test. This functionality exists in most hardware load balancers, but isn’t something Microsoft NLB does natively. Controlling NLB nodes is done using WMI, and the classes are fairly well documented. I was also fishing for an excuse to try ReactPHP, which is an event driven non-blocking library for PHP.

The result was my MicrosoftNLB class and the simplewatcher.php script. I used wmi2php to generate the boiler plate, and write a wrapper class called NLBNode. There is still a lot of work to be done, but it’s achieved MVP for me. Simplewatcher.php which will get all the peers in an NLB cluster, challenge them for a specific URL, and evict any node which fails. This is good for taking a node out of load (rename nlb.php to nlb.oos), or for stopping a node if IIS stops responding. If a node fails at the network layer (ie hardware or OS failure), it will stop participating in the cluster automagically. It will not put a node back into load, you must do that manually.

 

This is also my first composer project, and you’ll need to use it to get ReactPHP installed. To use the node watcher, just edit the first few lines of examples\simplewatcher.php, and run it from the command line.

PHP detect browser name and version with phpbrowscap

This is an old problem: how to programatically detect the visitors browser and version. There are some very good reasons for wanting to do this:

  • Known compatibility issues
  • Customizing download or support links
  • Leveraging advanced features in newer browsers

Googling around for this, you always find the objection “someone can use a fake User-Agent string, so don’t bother”. That’s an obnoxious and unhelpful remark which should be deleted from forums. The vast majority of users do not adulterate their UA string, and those who do have to accept the broken experience that comes with it.

PHP has a native function called get_browser() which does the job. It relies on the php_browscap.ini file downloaded from the Browser Capabilities Project. To enable it you set a directive in the php.ini file and you’re off to the races. There is one catch: it can only be configured system wide. So what happens if you’re on shared hosting? Or if you want to keep your own version in git and not use the system version? The solution is phpbrowscap.

Before I get into browscap, there is one other thing tip I want to cover. What if you want this information client side? The Navigator.appName property appears promising, except that Mozilla, Chrome and Safari all return the exceptionally useless “Netscape” value. Only MSIE actually tells you who it is. You can use a server side snippet like this to expose the data client side:

echo "<script type=text/javascript>var Browser=" . json_encode($browser) . ";</script>\n";

This will work for you whether you got $browser from get_browser() or from phpbrowscap.

Phpbrowscap is fully compatible with get_browser() and is usually faster. It processes the browscap.ini file and writes it out to native PHP code so that subsequent loads don’t have to parse the ini. The simplest possible usage is:

require 'Browscap.php';
use phpbrowscap\Browscap;
$bc = new Browscap('/tmp');
$browser = $bc->getBrowser();

The default behaviour is to automatically download the latest version of the browscap.ini file and store it in the cache directory. If you have no access to write anywhere, and do not want auto-update, you need to do something like this:

$browscap = new phpbrowscap\Browscap(__DIR__);
$browscap->doAutoUpdate=false;
$browscap->updateMethod=false;

For this to work, you must have a copy of browscap.ini and cache.php as generated by phpbrowscap. What I did was run the above version on some development box and copy the two files from /tmp to my source directory, then added them to my git.

The documentation isn’t great, so I’ll try to help out a little:

Directive Properties
doAutoUpdate true – default, also false
localFile Path to local copy of browscap. Used for offline updating.
updateMethod Browscap::UPDATE_LOCAL – use a local file specified in localFileBrowscap::UPDATE_FOPEN – Uses the fopen url wrapper (use file_get_contents)Browscap::UPDATE_FSOCKOPEN – Uses the socket functions (fsockopen)

Browscap::UPDATE_CURL – Uses the cURL extension

null or false – do not attempt update

cacheFilename Override the default cache file name of cache.php. Useful if auto-update is off.
iniFilename Override the default ini file name of browscap.ini. Useful if auto-update is off.

There are also some useful helper functions:

  • autodetectProxySettings() – gets settings from the *_proxy environment variables
  • addProxySettings($server, $port = 3128, $wrapper = ‘http’, $username = null, $password = null) – pretty obvious. If you’re wondering, 3128 is used by cntlm.
  • clearProxySettings() – I guess you might want this
  • updateCache() – Rebuild the cache file from ini

Praxis Institute in the future

Apparently the Praxis Institute is a south Florida massage therapy school, but as a Star Trek fan I couldn’t resist googling my way through video editing and cutting this together.

Enjoy

Creating a new composer packageist package

Composer is a package manager for PHP, similar in purpose to NodePM for NodeJS. The getting started section is pretty good at pulling in other packages, but I had a hard time getting to “ok, I want to manage my project with composer”. I finally found a post from Junior Grossi which got me started. Enthusiasts will probably have more to say, but it comes down to 4 parts:

  1. Your source
  2. Composer.json
  3. The autoloader
  4. Required packages

Your source

Your source, and the autoloader go together, but I’ll discuss them separately. Basically your source has to be laid out to work with the PSR-0 autoloader. You can’t use your own auto-loader anymore. Your classes must all be namespaced, with the directory structure following the namespace structure. If you had a class like MicrosoftNLB\NLBNode you would use something like this:

your-working-directory
 |-src
 | \-MicrosoftNLB
 |   \-NLBNode.php
 |-examples
 \-test

Remember your source must be namesapced so NLBNode.php will start out like this:

<?php
namespace MicrosoftNLB;
class NLBNode extends MicrosoftNLB_Node
{

composer.json

So now you have your source laid out in a way which will appease composer. You need to create a composer.json file. Here is one:

{
    "name": "robertlabrie/microsoftnlb",
    "description": "Classes and tools for Microsoft NLB",
    "license": "GPL V2",
    "authors": [
        {
            "name": "Robert Labrie",
            "email": "robert.labrie@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {
        "react/react": "0.3.*@dev"
    },
    "autoload": {
        "psr-0": {
            "MicrosoftNLB": "src/"
        },
        "classmap" :[ "src/MicrosoftNLB" ]
    }
}

Name, description, authors, all pretty straight forward. The minimum-stability setting is for your dependencies (see below).

The autoloader

This one tripped me up a bit. You need to use the composer auto-loader so that everyone’s packages can co-exist. Setup comes in the form of an array of namespace=path where path is the path where the namespace directory can be found. Junior’s directory layout which I shamelessly copied is what really made this make sense. So in the example above, the autoloader will search in src/CoolProject for classes. Remember your classes have to be namespaced or this thing won’t work.

Classmap

Or will it? The classmap section lets you bolt directories into PSR-0. Basically anything in here will be included for classes. The other case where you need to use a classmap is if classes have underscores in them. PSR-0 will replace underscores with slashes. Since my project uses boiler plate WMI classes, I had class names like MicrosoftNLB_Node, which was being turned into MicrosoftNLB/Node, which was then breaking. Thanks to Juan Treminio in #composer for that advise.

Required packages

This is also pretty straight forward and well explained at getcomposer.org.

Examples and testing

Your testing will probably use PHPUnit or similar. Examples can go in /examples, and will look something like this:

<?php
require_once __DIR__."../vendor/autoload.php";
$node = new MicrosoftNLB\NLBNode('localhost');

Including the autoloader from vendor directory lets composer do all the magic.