Server-Side Device Detection with Browscap

Ensuring that websites can adapt to multiple devices – particularly mobile devices – has become both increasingly important and complex. The variety of ways in which people browse the web having mushroomed. Much of this has been driven by the rise in the use of mobile devices to access the web, although it also applies to devices such as tablets, Internet TV, and so on.

In recent years, Responsive Web Design has become all the rage as a mechanism for adapting websites and applications to cater to multiple devices by using information client-side to adapt layouts accordingly. In a sense, this is a “one-size-fits-all” approach. However, there’s often a case for providing different versions of the same site or using a slightly different approach.

An alternative solution to the problem is to use server-side device detection and then take certain actions based on that information. One possibility is to simply forward requests for a mobile site to a different URL. Another possibility is to adapt the layout – or indeed content – programmatically as it’s generated on the server.

Taking a server-side approach is the basis of this article, which looks in detail at the Browser Capabilities Project, or Browscap for short, to provide the information on which to base these decisions.

An Introduction to User Agent Strings

Before we can look at server-side detection, we need to understand the information available to the web application which comes in the form of user agent strings.

When you make an HTTP request to retrieve a page from a web browser, it includes a piece of information called the user agent string. This provides information about who’s making the request and includes a variety of elements.

Typically the user agent string specifies what browser is used – it’s how analytics services can determine whether you’re using Chrome, Firefox, Safari, IE, or one of a multitude of other browsers. Furthermore, it can also provide information about the version of the requesting application. This is great for identifying the dwindling numbers of IE6 users, for example.

Of course a browser is really just a specific type of application. Remember, HTTP requests aren’t just issued when your web browser accesses a web page or other resource. The request may be from a web crawler, a feed reader (ie: RSS), a validator, a library such as cURL, or one of a potentially infinite number of web service clients.

The user agent string is also used to provide information about the operating system. Windows, Linux or Mac? iOS or Android? That, too is qualified with version numbers.

The user agent string will often also provide information about the physical device being used, whether it is a desktop, laptop, mobile, tablet, etc.

Let’s look at some examples of user agent strings:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0

- This says that the request has been made by Firefox (version 24, to be specific) from an Intel-based Mac.

Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25

- Safari on an iPad (running iOS6)

Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (S60; SymbOS; Opera Mobi/23.348; U; en) Presto/2.5.25 Version/10.54

- Version 9 of Opera Mini on a Java-based mobile phone

Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30

- Mobile Safari on an Android phone

You can view hundreds of user agent strings online at useragentstring.com/pages/useragentstring.php.

Accessing Browscap Data

Considering the number of variables, the number of possible versions, and of course the ever-expanding range of physical devices being used, you’ll realize that the number of different permutations is quite large and growing every day. The Browser Capabilities Project is an attempt to catalogue these permutations. You can think of it of a database of user agent strings which associates them with some of the information which we can glean from them.

In reality it’s not simply a database keyed by user agent string; it’s a series of rules and patterns used to extract relevant pieces of information, cross-referenced against known browsers, devices, and other information.

PHP offers a native wrapper to Browscap data in the form of the get_browser() function. (See php.net/get-browser). However, there are two key disadvantages to its use. Because PHP doesn’t ship with a browscap.ini data file, you’ll need to manually download one and set your php.ini to point to the appropriate location. More significantly, because the database is updated regularly you’ll need to manually replace it when it does.

Fortunately, there are libraries which take care of both of these issues and don’t require any additional configuration. There are a number of them available, but I recommend github.com/browscap/browscap-php.

Integrating a Browscap Library

I’m going to use the Slim Framework for the following examples. You might prefer a different framework, or indeed none at all, but as this article is specifically about Browscap I don’t want to get bogged down in fundamentals such as autoloading, routing, or middleware. The code for this article is available so you can follow along.

The easiest way to get started is to use Composer to download the dependencies. There are two to start with – the Slim framework and the Browscap library. Create a project directory, and in its root a composer.json file to specify these dependencies:

{
    "require": {
        "slim/slim": "2.2.*",
        "browscap/browscap-php": "1.0.*@dev"
    }
}

(Note: At time of writing, the current version of the Slim Framework is 2.3.0. However, I’m deliberately using 2.2.x for compatibility with a layout view library that we’ll be using later.)

Then run composer:

php composer.phar install

You should now have a vendor folder in your project root containing both libraries, as well as an autoloader. Create a public folder in the project root, and in that an index.php file. Start by including the autoloader:

<?php
require '../vendor/autoload.php';

Let’s now create the bare bones of a Slim application, which is beautifully simple:

$app = new SlimSlim();

$app->get('/', function () {
  print 'Hello World';
});

$app->run();

At this stage, you’ll probably want to verify that everything is installed properly by browsing to your new index page.

Next, we’re going to set up the Browscap library. This takes care of three key tasks:

  • downloading the actual data file (browscap.ini)
  • processing the data to make lookups more efficient
  • keeping the data up-to-date

Since the library needs to download and create some files, we need a cache directory. Let’s keep it simple and create a cache directory in the project root – just don’t forget to make it writeable by the web server.

We specify the cache directory when we construct an instance of the Browscap class:

use phpbrowscapBrowscap;

// create a new Browscap object (loads or creates the cache)
$bc = new Browscap('../cache');

Let’s check it out by grabbing the information and dumping it straight to the screen:

// get information about the current browser's user agent
$current_browser = $bc->getBrowser();

echo '<pre>';
print_r($current_browser);
echo '</pre>';

This might take a little while to run first time as the library needs to download the INI file and perform some processing on it – thankfully, subsequent requests will be considerably faster.

If you take a look in the cache directory you’ll notice two files; browscap.ini is the original data file, and cache.php is the same data but optimized for quicker lookups. Different libraries process and optimise the INI data in different ways – you might wish to try a few and perform benchmark tests to see which is the most efficient.

Interpreting Browscap Data

Here’s example output from the code in the previous section:

stdClass Object
(
    [browser_name] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36
    [browser_name_regex] => ^mozilla/5.0 (.*intel mac os x.*) applewebkit/.* (khtml, like gecko).*chrome/27..*safari/.*$
    [browser_name_pattern] => Mozilla/5.0 (*Intel Mac OS X*) AppleWebKit/* (KHTML, like Gecko)*Chrome/27.*Safari/*
    [Parent] => Chrome 27.0
    [Platform] => MacOSX
    [Win32] => 
    [Comment] => Chrome 27.0
    [Browser] => Chrome
    [Version] => 27.0
    [MajorVer] => 27
    [MinorVer] => 0
    [Beta] => 1
    [Frames] => 1
    [IFrames] => 1
    [Tables] => 1
    [Cookies] => 1
    [JavaScript] => 1
    [JavaApplets] => 1
    [CssVersion] => 3
    [Platform_Version] => unknown
    [Alpha] => 
    [Win16] => 
    [Win64] => 
    [BackgroundSounds] => 
    [VBScript] => 
    [ActiveXControls] => 
    [isMobileDevice] => 
    [isSyndicationReader] => 
    [Crawler] => 
    [AolVersion] => 0
)

What you actually see here will of course depend on your machine, the browser you’re using, and what versions. But notice that key pieces of information have been extracted into properties of the returned object, for example:

  • browser_name is the original user agent string. Notice the ambiguity – it mentions both Chrome and Safari; which one is it?
  • Browser tells us I’m using Chrome
  • Version is the version of Chrome I’m using
  • Platform tells us I’m using an Apple Macintosh

Furthermore there are a number of characteristics of my setup that are identified.

Tables, Cookies, Frames, IFrames, Javascript, JavaApplets, BackgroundSounds, VBScript, and ActiveXControls provide some information about the capabilities of my browser – perhaps some more useful than others nowadays!

We can glean even more information about the browser’s capabilities from CssVersion, which specifies the maximum version of the CSS specification the browser supports.

isSyndicationReader and Crawler are useful for identifying specific types of application. For example, were you to write an analytics service, you may wish to use Crawler to exclude those “hits” from your visitor data. Alternatively, an application or website that caches heavily may wish to ensure it’s fresh when a request has been identified by a crawler that’s likely to be a search engine.

isMobileDevice is an interesting one – and arguably the most useful. What actually constitutes a mobile device is open to interpretation, but generally speaking it will give us that all important distinction between, say, a desktop computer and a mobile phone.

Dealing with the “edge cases” of isMobileDevice is outside the scope of this article, and furthermore often depends on the project. For example, should an iPad or Android tablet get the desktop of mobile version of a site – or should there be a specific tablet version?

We’ve called getBrowser() without any additional arguments in the example, which means that the user agent string will be taken from the request. If you want to play around or do some additional testing, you can pass a user agent string as the first parameter, for example:

$current_browser = $bc->getBrowser(
    "Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (S60; SymbOS; Opera Mobi/23.348; U; en) Presto/2.5.25 Version/10.54"
);

Additionally, passing true as a third argument will cause the function to return the Browscap data as an array, rather than an object, as its default format.

Using Browscap to Redirect to a Mobile Site

Now let’s look at some practical examples of how you might use Browscap data. Suppose you have a separate mobile website, perhaps on a subdomain – e.g. m.example.com – or perhaps using a different TLD, e.g. example.mobi. You could simply intercept all requests to the desktop site, and redirect mobile visitors to the corresponding site by using a hook. In this case we use slim.before, which is invoked before the Slim application is run:

$app->hook('slim.before', function () use ($app) {
    // create a new Browscap object (loads or creates the cache)
    $bc = new Browscap('../cache');

    // get information about the current browser's user agent
    $current_browser = $bc->getBrowser();

    // redirect to the mobile site if this is mobile
    if ($current_browser->isMobileDevice) {		
        $url = 'http://m.example.com';
        $app->response()->redirect($url, 301);
    }
});

Of course this isn’t terribly useful in iteslf; a request for, say, http://example.com/about would be redirected to http://m.example.com, that is to say the mobile site’s homepage. However if you know that the site structure of the mobile site matches that of the desktop site, you can make it a little more useful by analyzing the requested URL:

if ($current_browser->isMobileDevice) {
    $path = $app->request()->getResourceUri();
    $url = 'http://m.example.com' . $path;
    $app->response()->redirect($url, 301);
}

Using Browscap for Layout Switching

Another common use for Browscap data is to perform server-side layout switching for desktop vs. mobile sites. Let’s take our bare-bones Slim-based application, and do exactly that.

The first thing to do is update the composer.json file to specify a new dependency, a handy little library we’ll be using to implement layouts: github.com/petebrowne/slim-layout-view.

{
    "require": {
        "slim/slim": "2.2.*",
        "browscap/browscap-php": "1.0.*@dev",
        "petebrowne/slim-layout-view": "0.1.*"
    }
}

Now run:

php composer.phar update

This should install the code for a new View class which will wrap the output of our application in a layout view.

Now in your project root, create a templates directory. Within that a layouts directory, and in that two files – desktop.php and mobile.php.

Keeping it simple for now, desktop.php would look something like this:

<!DOCTYPE html>
<html>
 <body>
  <h1>This is the Desktop Layout</h1>
  <?php echo $yield ?>
 </body>
</html>

mobile.php would look something like this:

<!DOCTYPE html>
<html>
 <body>
  <h1>This is the Mobile Layout</h1>
  <?php echo $yield ?>
 </body>
</html>

These are extremely simple examples, but they’re enough to test out layout switching.

Now change the instantiation of Slim to:

$app = new SlimSlim(array(
  'view' => 'SlimLayoutView',
  'templates.path' => '../templates',
  'layout' => 'layouts/desktop.php'
));

What we’re doing here is telling Slim to use layouts, specifying the templates directory we created a moment ago, and defaulting to our desktop layout.

Now create a file in templates called home.php, for example:

<p>Hello World!</p>

And change your route to:

$app->get('/', function () use ($app) {	
  $app->render('home.php');
});

Visit the page and you should see the heading “This is the Desktop Layout” followed by “Hello World!”.

Okay, now we need to add the ability to dynamically switch layouts for mobile. To do this we’re going to build some middleware. This middleware will be run during the request life cycle and will be responsible for detecting a mobile visitor and switching the layout accordingly.

To create middleware, we simply extend SlimMiddleware and define a call() method. Keep in mind it’s also the responsibility of each item of middleware to call the next one in Slim.

Here’s our skeleton middleware:

class LayoutSwitcherMiddleware extends SlimMiddleware
{
    public function call() {
        // get reference to application
        $app = $this->app;

        // call the next middleware
        $this->next->call();
    }
}

Now we need to add this to the application, so right after we instantiate the Slim framework, add this line:

$app->add(new DeviceSwitcherMiddleware());

If you visit the page, the device switcher should run – feel free to echo something out to verify that this is the case.

Switching layouts is straightforward; all we need to do is grab the device information from the Browscap library, try and identify a mobile visitor, and if that’s the case then override the default layout.

To do this, add these lines before the line $this->next->call():

// create a new Browscap object (loads or creates the cache)
$bc = new Browscap('../cache');

// get information about the current browser's user agent
$current_browser = $bc->getBrowser();

// switch to the corresponding layout if this is mobile
if (!$current_browser->isMobileDevice) {
    $app->config('layout', 'layouts/mobile.php');
}

If you visit the page using a mobile device, you should see the mobile layout being used.

In practice, your desktop and mobile layouts can include separate stylesheets, handle navigation differently, include or exclude regions, and so on.

The eagle-eyed among you will notice that I’ve made a big but all-to-common oversight. It’s my view – a common one, I hope – that whilst it’s all good-and-well to serve a mobile website by default, it should always be possible for a mobile user to visit the desktop version of a site if they so wish. I won’t go into too much detail, but one approach might be to provide a link with a flag – e.g. ?desktop=true, which sets a cookie that overrides the automatic layout-switching behaviour.

Combining Server-Side Layout Switching with RWD

Although server-side layout switching and responsive web design are two different approaches to the same problem, there’s no reason they can’t be used in unison. For example, there’s no reason why you cannot have different layouts for different groups of devices, but keep these layouts responsive too – after all, even among what you might classify mobile devices, the possible variation in things like screen resolution can be huge.

Another Example – a Software Website

Knowing what operating system someone is using when browsing a website can be useful too. Let’s suppose you’re building a simple website to promote an application which is available for Windows, Mac, and Linux. It’s common on such websites to simplify such a website’s download page to try and guess which version the user is looking for. For example, if you access the site using a Mac, it’d show the corresponding download link most prominently – not forgetting, of course, that people may still wish to download alternate versions.

Here’s one way in which you might do this in a template file:

<?php if (!strncmp($current_browser->Platform, 'Win', 3)): ?>

<p><a href="/downloads/windows.zip" class="btn btn-large btn-primary">Download for Windows</a></p>
<p>Alternatively, download for <a href="/downloads/mac.dmg">Mac</a> or <a href="/downloads/linux.tar.gz">Linux</a>.</p>

<?php elseif (!strncmp($current_browser->Platform, 'Mac', 3)): ?>

<p><a href="/downloads/mac.dmg" class="btn btn-large btn-primary">Download for Mac</a></p>
<p>Alternatively, download for <a href="/downloads/windows.zip">Windows</a> or <a href="/downloads/linux.tar.gz">Linux</a>.</p>

<?php elseif ($current_browser->Platform == 'Linux'): ?>

<p><a href="/downloads/linux.tar.gz" class="btn btn-large btn-primary">Download for Linux</a></p>
<p>Alternatively, download for <a href="/downloads/windows.zip">Windows</a> or <a href="/downloads/mac.dmg">Mac</a>.</p>

<?php else: ?>

<p>Select your version:</p>
<ul>
 <li><a href="/downloads/windows.zip">Windows</a></li>
 <li><a href="/downloads/mac.dmg">Mac</a></li>
 <li><a href="/downloads/linux.tar.gz">Linux</a></li>
</ul>

<?php endif; ?>

You’ll find an implementation of this (with styles and icons) in the accompanying code.

A Windows machine could be indicated by a variety of strings, e.g. Win2000, WinXP, WinVista, Win7… As such, we attempt to identify Windows by looking at the start of the string. Another way would be to examine the Win16, Win32 and Win64 flags in Browscap’s device data. Of course if you’re targeting different versions of Windows, for example separating Vista and Windows 7 versions, you can check for WinVista and Win7 respectively.

A Final Example

This one’s pretty self-explanatory!

$current_browser = $bc->getBrowser();
if (($current_browser->Browser == 'IE') && ($current_browser->MajorVer == 6)) {
    header('Location: http://www.ie6countdown.com/');
    exit;
}

Summary

In this article I’ve introduced the idea of server-side device detection, looking in detail at the Browser Capabilities Project (Browscap). I’ve shown how it can be used as an alternative to – or even to supplement – client-side techniques for adapting websites for multiple devices, such as Responsive Web Design. While my examples use the Slim framework, the principles remain the same whatever your preferred approach.

I’ve also given you a few ideas about how else you might use Browscap data – if you can think of others, be sure to let me know in the comments!

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • David

    Thankyou for this overview of Browscap – the examples given are excellent, and I’m already steaming ahead

  • Chris Rosillo

    Warning! The sole maintainer of browscap (R Moose) appears to be in a financially bad place and is no longer able to maintain the browscap.ini it would seem.

    See the Google group on the relaunch progress and the guys Google Plus. It’s sad, but guess this can happen with open source projects maintained by volunteers :(
    https://groups.google.com/forum/m/#!topic/browscap/pk_dkkqdXzg
    https://plus.google.com/app/basic/stream/z12vgzk50uzdgb4xm04ccjowipinz5upxr00k
    We recently got burnt by this because a product relied on browscap; it failed to detect a new version of Chrome.

    A possible alternative…?
    https://github.com/tobie/ua-parser/tree/master/php

  • http://www.jamestitcumb.com/ James Titcumb

    Chris & everyone else – there is now more than one maintainer of the Browscap project. We are making an effort now to open the Browscap Project up to allow public contributions to ensure there is no “single point of failure”. Bear with us! :)

  • Steve

    Of course the useragent can be set to anything by the browser owner and so should not be used for anything other than statistics. My Opera browser uses a useragent that includes content that results in some places identifying it as Chrome and others identifying it as Firefox. Sometimes it gets identified as running on a mac even though it is actually running on Windows.

    My IE browser has a useragent of “Eat at Joes”.

    Why do I set the useragent’s that way – because it allows me to uniquely identify my visits in the stats for my web sites – which is what the useragent is for.