Semantic web development and publishing

Mobile Drupal (part 2): Site setup

A previous article covered some basic groundwork for mobile sites in Drupal. This article goes on to look at different ways to setup a mobile site in Drupal. It covers single site, multisite, single site with settings.php tweak and the Domain Access module. Caching strategies, redirect rules and other server side settings are also discussed.

RESTful design of URLS

REST defines the architecture of the World Wide Web. One of the principles of REST is that a single URI represents a resource and that resource is conceptually different from the representations returned to the client.

Representational State Transfer
“Representational State Transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web. The term Representational State Transfer was introduced and defined in 2000 by Roy Fielding in his doctoral dissertation.[1][2] Fielding is one of the principal authors of the Hypertext Transfer Protocol (HTTP) specification versions 1.0 and 1.1″

Here’s the passage from Roy Fielding’s thesis (emphasis added) which discusses the differences between resource and representation:

“This abstract definition of a resource enables key features of the Web architecture. First, it provides generality by encompassing many sources of information without artificially distinguishing them by type or implementation. Second, it allows late binding of the reference to a representation, enabling content negotiation to take place based on characteristics of the request. Finally, it allows an author to reference the concept rather than some singular representation of that concept, thus removing the need to change all existing links whenever the representation changes (assuming the author used the right identifier).”

A resource is named by a URI. The server chooses the best representation to provide to the client based on headers sent by the client. In this case we are looking at the User Agent.

If we were to follow RESTful principles then the mobile site should indeed be served from the some domain as the desktop site. ie. one resource, different representations. In this scenario the HTML returned to the mobile client is just a different respresentation to that provided to the desktop site. This is a natural way to design a web app as it means that there is only one “canonical” URI for the resource with no chance of nasty duplicate content issues. From an SEO point of view this is desireable. However…

Caching, the fly in the ointment

We’ve just seen that serving different representations from a single URI is a good thing from many perspectives: mobile first, progressive enhancement, REST and SEO. However, there is one reason why we may we may decide to go down the path of using two domains instead of one: caching.

Caching mechanisms, such as Drupal Core and Boost, used the fully qualified domain name of a URI to determine caching keys. This allows the cache to quickly serve content to different clients without knowing the criteria which decides the representation received by the client, ie. the cache just has to know about the URI, it doesn’t need to decipher the user agent. Currently, if different representations are served for the same resource then the cache will likely become populated with a mix of different representations, leading to chaos. For this reason it is generally accepted that having a separate mobile site on a sub domain is a good way to go. ie. we would have two sites:

  • http://example.com/about
  • http://m.example.com/about
Cache by theme for mobile sites
mikeytown2 offering some great advice on Apache and Boost rules.
.htaccess Mobile Browser Redirect
User Agent processing in Apache to redirect to mobile.

Some users have solved the caching problem AND manage to serve different representations from the same URI. Going mobile with a news site that Just Works describes how browser detection can be done in the caching layer, in this case Squid, before redirecting the request invisibly to another domain. This is the perfect setup as RESTful principles are maintained and the site is scalable. Hats off. Unfortunately not everyone is running a reverse proxy which allows for this kind of setup. A request looks like this:

  1. mobile client does GET http://example.com/about,
  2. Squid (port 80) looks at User Agent, determines device and sends to http://m.example.com/about,
  3. Boost finds “about” in /cache/normal/m.example.com/ -> Static HTML returned OR,
  4. Drupal serves from multisite -> Dynamic HTML returned.

mikeytown2 claims that it should be easy enough to add some logic into the Boost rules based on user agent, he just needs to know what they are. So there is a good chance that Boost user’s will be able to server both mobile and desktop from two sites with one URI space. From my understanding of the proposed approach it looks like a single domain will be all that is required.

  1. mobile client does GET http://example.com/about,
  2. Boost looks at User Agent, determines device and uses a different “mobile” device rather than “normal”,
  3. Boost finds “about” in /cache/mobile/example.com/ -> Static HTML returned OR,
  4. Drupal serves from single site -> Dynamic HTML returned.

A slightly different approach has been described in Mobile Detection with Varnish and Drupal where Varnish sets a header which can then be read in the webserver or Drupal. This is a neat approach as it means that device logic needn’t be repeated in settings.php. The flow described by Morten Fangel is as follows:

  1. mobile client does GET http://example.com/about,
  2. Varnish also sets a X-Device header for the device
  3. Varnish looks at User Agent, determines the device and appends it to the hash for the key
  4. Varnish “about” in cache -> Static HTML returned OR,
  5. Drupal serves from single site -> Dynamic HTML returned.

Assuming you don’t have Squid, Varnish or a patched Boost to hand you will probably have a setup as follows:

  1. mobile client does GET http://example.com/about,
  2. Apache rewrite looks at User Agent, determines device and redirects http://m.example.com/about,
  3. Drupal Core or Boost finds “about” in cache -> Static HTML returned OR,
  4. Drupal serves from multisite -> Dynamic HTML returned.

Sub domain vs Different Domain

If you are going to use a separate site to host the mobile site then you are free to chose whatever domain you like. eg. example.mobi. However, it is generally recommended to stick with using a sub domain of the desktop site. This confuses users less and it is possible to share cookies across sites on the same domain.

Different theme

As discussed in the previous article, it is possible to serve the same default theme to both mobile and desktop sites and then progressively enhance the desktop site with some extra CSS. The method proposed in Rethinking the Mobile Web at slide 106:

<link href=’default.css’ type=’text/css’ rel=’stylesheet’
media=’screen’ />
<link href=’desktop.css’ type=’text/css’ rel=’stylesheet’
media=’screen and (min-device-width:1024px) and (max-width:989px)’ />

This is a very cool way to design a site as it keep things very simple. Mobile is first and then comes the progressive enhancement. However, this isn’t a pattern which is adopted by most Drupal themes where the presumption is for the desktop theme. If we did take this approach it would preclude us from using the majority of themes designed for Drupal so far. Given this, I would say that our Drupal site will support two separate themes, one for desktop and one for mobile. The general approach is to use a multisite setup. define a desktop theme as default in the GUI and then to override that theme via a tweak in settings.php for the mobile site.

Multisite setup

Assume we are using two domains due to caching requirements. How do we serve this content? Drupal does have a multisite feature built in where a single Drupal “platform” can support many different site instances. These sites can share all data, no data or partial data, depending on how they are setup in settings.php. In the case of a mobile site we would want to share all data between the sites.

One possible setup is to create a directory for the desktop and mobile versions under sites/

sites/

  • all/
    • modules/
      • contrib/
        • cck/ etc
      • custom/
        • mw_mobile/
    • themes/
      • base_desktop/
      • base_mobile/
      • mw_desktop/
      • mw_mobile/
  • default/
    • files/
  • example.com/
    • settings.php
  • m.example.com/
    • settings.php

The only trick to get this work is to manually set the default theme for the mobile site in the sites/m.example.com/settings.php file. For every page request, the config in settings.php will override the default variables defined in the variables table in the database.

$conf = array(
‘theme_default’ => ‘mw_nokia_mobile’,
);

If you manually set a value like this you won’t be able to change it in the UI, naturally enough. Make sure the theme is active in the GUI.

Alternative 1: Single site with settings.php logic

The above multisite setup will work, however, there is something wrong with it. It will stop you from hosting a true multisite setup where the sites share code but have different databases. This may not worry you if you are only hosting a single site for the platform but it could be important if you want multisites. Imagine a site for Company X served on example.com and Company Y is on example.net. You couldn’t use multisites with the above settup because of the reliance on shared files in default/files.

However, you can achieve a very similar effect with a single site by using a bit of conditional logic in settings.php for example.com and example.net. The idea is to set the theme based on the domain leading to only needing a single site to support desktop and mobile. Add this to sites/example.com/settings.php/

$parts = explode(‘.’, $_SERVER['HTTP_HOST']);
if ($parts[0] == ‘m’) {
$conf = array(
‘theme_default’ => ‘company_a_mobile’,
);
}

You could then support mobile with a pure sites setup with shared code and different databases/files. This is a good way to go.

sites/

  • all/
    • modules/
      • contrib/
        • cck/ etc
      • custom/
        • mw_mobile
    • themes/
      • base_desktop/
      • base_mobile/
  • default/
    • files/ -> empty
  • example.com/ -> company A
    • files
    • modules
    • themes
      • company_a_mobile
      • company_a_desktop
    • settings.php -> with conditional setting of default theme
  • example.net/ -> company B
    • files
    • modules
    • themes
      • company_b_mobile
      • company_b_desktop
    • settings.php -> with conditional setting of default theme
multi site a) standard theme, site b) mobile theme – same code and same tables?
Discussion of multisite setups.

Alternative 2: Domain Access

The Domain Access module, discussed later, can set a lot of this up for you including sub domains, domain aliases and themes. You may prefer to use it for convenience, especially if you like configuring stuff in a GUI rather than settings.php or custom modules.

Mobile global variable

Modules are going to want to access a global variable which tells them the device accessing the site: mobile or desktop. There are a variety of ways to do this, some set the variable early, others late:

  1. Custom “X-Device” header set in a reverse proxy
  2. Conf variable set in settings.php
  3. Global variable set by a module during hook_init()
  4. API function offered by a module

It is possible to do this through the use of hook_init() in a custom module. I tried this but ran into problems with timing and module weight. Sometimes you will want the mobile module to be heavy, sometimes light :) In the end I went with an “api” function in my mw_mobile module which stored a static variable. It should be pretty fast and not to cumbersome. Other contrib modules take an approach similar to this.

/**
* An API function in a custom module
* Efficiently returns whether the site is mobile.
* Other modules should call it as follows:
* $mobi = module_exists(‘mw_mobile’) && mw_mobile_is_mobile();
*/
function mw_mobile_is_mobile(){
static $out;
if (isset($out)) {
return $out;
}
// set and return
if (substr($_SERVER["SERVER_NAME"], 0, 2) == ‘m.’) {
$out = TRUE;
} else {
$out = FALSE;
}
return $out;
}

This approach is perhaps not the best. It may be better to set a global variable very early in the bootstrap process, in settings.php, so that it could be reliably used by all other Drupal code.

Cross site authentication

It is possible to set cookies up so that they will be sent no matter what the sub domain. In settings.php uncomment the $cookie_domain variable and set it to the domain, excluding the sub domain. Please note that this will not work if you are using different domains.

$cookie_domain = ‘example.com’;

Redirecting the user to mobile

When a mobile user hits the desktop version of the site you want them to be redirected to the mobile site. There’s at least three ways to do this:

  • PHP
  • JS
  • Apache

The first inclination maybe to go with PHP as afterall, we are PHP developers. However, this has the shortcoming of requiring that Drupal be bootstrapped before the PHP can be run, destroying the chance to safely cache the page for anonymous users. It’s slow and ineffective. Doing it in PHP therefore isn’t an option. This is the approach some of the mobile modules take but I think it’s something to be avoided.

You could of couse do a client side check in Javascript for the client’s user agent. This will allow for caching but has the downsides of forcing a full page download. Also, not every client will have JS enabled. Not really an option.

The final option of doing it in Apache (or your webserver) is the only viable alternative. I went with a recipe similar to the following in my .htaccess.

# Mobile: force mobile clients across to the mobile site
RewriteCond %{HTTP_HOST} !^m\.(.*)$
RewriteCond %{HTTP_USER_AGENT} !ipad [NC]
RewriteCond %{HTTP_ACCEPT} “text/vnd.wap.wml|application/vnd.wap.xhtml+xml” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “acs|alav|alca|amoi|audi|aste|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “dang|doco|erics|hipt|inno|ipaq|java|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|opwv” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “palm|pana|pant|pdxg|phil|play|pluc|port|prox|qtek|qwap|sage|sams|sany” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|w3cs|wap-|wapa|wapi” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “wapp|wapr|webc|winw|winw|xda|xda-” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “up.browser|up.link|windowssce|iemobile|mini|mmp” [NC,OR]
RewriteCond %{HTTP_USER_AGENT} “symbian|midp|wap|phone|pocket|mobile|pda|psp” [NC]
RewriteCond %{HTTP_USER_AGENT} !macintosh [NC]
RewriteRule ^(.*)$ http://m.%{HTTP_HOST}/$1 [L,R=302]
.htaccess Mobile Browser Redirect
Outlines the approach taken above.

SSL issues

If you are using SSL on the desktop version of your site then you have a couple of extra hurdles to jump in order to get it working on the mobile site.

Firstly, as it isn’t possible to set up SSL for two different domains on the same IP address, you will probably need to rent a new IP address for the mobile version of the site. Sort this out with your ISP. It should cost you between $1 and $3 a month for another IP. They may have instructions for setting up A records, static routing for your IP addresses, etc.

Secondly, you will also need to sort out another certificate for the mobile site. You could purchase a wildcard certificate for the domain and all sub domains. These cost a fair bit more and will save you from buying a new cert. However, it is probably cheapest to get another cert for the mobile site along with a new IP. You will then need to install the certificate on your server and tweak your site config for the mobile site. This certainly is one of the pains of having two separate sites.

PositiveSSL from Comodo
$10 pa.
Gandi
Free first year then 12 Euro pa.

Custom version of robots.txt

A corollary of having a shared database and file system with a multisite install is that you can’t have custom versions of key files such as robots.txt which sits in the root of your Drupal platform. In the simple case I don’t believe that there is any need to have a different version, however, if you do need to support different versions then you can do it with a bit of .htaccess magic. Place the following code under the mobile redirect rule. Just be sure to add robots.txt to the /sites/%{HTTP_HOST}/files/robots.txt.

# robots.txt: solve multisite problem of only one robots.txt
# redirects to file in /sites/files/robots.txt
RewriteRule ^robots\.txt$ sites/%{HTTP_HOST}/files/robots.txt [L]
multi-site robots.txt
GDO discussion of this approach.

Duplicate content, Canonical URLs and robots.txt

You now have two sites where the mobile site replicates all the content of the desktop site. This is a major issue as search engines such as Google will treat is as duplicate content leading to declines in ranking. We need to sort this out. Google came up with the concept of a canonical URL which can be defined in a link element in the head of the HTML page. In our case the link points back to the desktop site.

<link rel=”canonical” href=”http://example.com/about” />
Specify your canonical
Google documentation on how to define a canonical URL.

We need every page in the mobile site to support this tag. This can be set in your mobile module:

/**
* Implementation of hook_init().
*/
function mw_mobile_init() {
if (!mw_mobile_is_mobile()) { return; }
// Add a canonical URL back to the main site. We just strip “m.” from the
// domain. We also change the https to http. This allows us to use a standard
// robots.txt. ie. no need to noindex the whole of the mobile site.
$atts = array(
‘rel’ => ‘canonical’,
‘href’ => str_replace(‘://m.’, ‘://’, _mw_mobile_url(FALSE)),
);
drupal_add_link($atts);
}

/**
* Current URL, considers https.
;* http://www.webcheatsheet.com/PHP/get_current_page_url.php
*/
function _mw_mobile_url($honour_https = TRUE) {
$u = ‘http’;
if ($_SERVER["HTTPS"] == “on” && $honour_https) {$u .= “s”;}
$u .= “://”;
if ($_SERVER["SERVER_PORT"] != “80″) {
$u .= $_SERVER["SERVER_NAME"].”:”.$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
} else {
$u .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
}
return $u;
}

The final thing to resolve is whether to set “noindex” on the mobile site. This definitely an area where there is some confusion on the web. After sniffing around I came to the conclusion that it was OK to allow Google to index the mobile site, so long as the canonical links have been specified. This means that any page rank given to the mobile site will flow to the desktop site and you won’t be punished for duplicate content.

The outcome is that you can go with the same robots.txt for both sites, ie. robots are free to index the mobile site. There is no need to specify a different robots.txt for mobile. You want the same stuff indexed for the mobile as you do with the desktop.

The one exception to this would be the files/ directory. A recent core update to 6.20 allowed files/ to be indexed. Fair enough, you want your public images to be indexed. However, you could raise the case that files/ shouldn’t be indexed in the mobile site, given that there is no way to specify a canonical link for these binary files. So, you may well want to support a different robots.txt for each site by blocking access to files on the mobile site. This is a very minor issue and probably not worth worrying about.


This article is the second in a four part series on mobile sites in Drupal.

  1. Mobile Drupal (part 1): The groundwork
  2. Mobile Drupal (part 2): Site setup
  3. Mobile Drupal (part 3): The code
  4. Mobile Drupal (part 4): Conclusions

Feel free to comment if you have any other tips, recommendations or corrections. The example code presented is for Drupal 6 only and not necessarily production ready.

11 Comments

  1. mikeytown2
    Posted January 13, 2011 at 2:26 am | Permalink

    After reading this section; you can set an environmental variable in apache that can then be used in PHP. So if the htaccess rules detect a mobile device then it can pass that along to PHP.
    http://drupal.org/node/629520#comment-3941580 – How to do with with boost!

  2. Posted January 13, 2011 at 4:03 am | Permalink

    Hey, hey. Good stuff. That’s pretty cool if (some of) the Boost side of things can be solved by a couple of lines like that in settings.php.

  3. Nadir H
    Posted June 28, 2011 at 10:03 am | Permalink

    Hi,
    This is an amazing article and I think this should be part of a mobile 101 for any organization planning to work on a mobile website or say mobile webapp for their desktop site. It answers so many questions I had.
    Drupal camps please do cover these topics.
    Thanks a ton.
    :)

  4. tim
    Posted July 12, 2011 at 4:41 am | Permalink

    Awesome article, thanks!

    Was testing the “Multisite setup”, this works great for the mobile devices that i have tried and it works fine in firefox when I use that url (on my localhost_) site.local , but when i tried this same url in chrome it did not work at first.

    I had to clear the browsers cache to get it working sweetly.
    (..for anyone who is getting a bit stuck with chrome.)

    ThaNKS AGIAN!

  5. Posted September 9, 2011 at 2:53 pm | Permalink

    Hi. Read your article and found it very insightful and helpful. We are in the midst of converting our site to mobile.

    We are a kids site that uses primarily Flash to build educational games and are trying to find a way to hide that section from iPhone and iPad users and only use one theme for mobile via domain access module.

    Is there a way to set a global variable via browser detection per user to hide 2 elements of an navigation or load a different drupal menu call?

    So the navigation would be:

    iPhone iPad user
    Homepage | Apps | Schedule | Contact

    Android, RIM Users
    Homepage | Games | Videos | Apps | Schedule | Contact

    I keep running into the fact that whoever hits the page caches the device variable which doesn’t work if a user comes in with an iPad but the user who cached it had an Android device.

    Any suggestions would be great.

  6. Posted September 11, 2011 at 4:44 am | Permalink

    Hi Derek. If you want to customize the page output depending on the device AND use page caching then you are going to have to cache different versions for each site. The examples I give above assume that you have two sites: mobile and desktop. In your case it sounds like you have three. Following the recipe above I’d say that you would need to have each on different sub domains and make sure each site is cached separately. This should stop the problem you are describing.

    You might want to consider a more lightweight approach and hide menu options using CSS and JS. If the two sites are different only for the menu options, you could hide the options for iPhone/iPad users by testing the device client side and then setting display:none for the menu items. This would stop you from having to cache two mobile sites which were essentially the same.

    Hope that helps.

  7. Posted January 16, 2012 at 11:27 am | Permalink

    I run a multisite where each site has a subdomain – site1.somedomain.com. Each site has it’s own database, but shares code files.

    1. Is it possible to set up a subdomain of a subdomain for a mobile site? m.site1.somedomain.com.
    2. If that’s not advisable, any suggestions on how to best set up the mobile sites?

  8. Posted February 11, 2012 at 12:11 am | Permalink

    I’m struggling to get the mobile site working well and have learned a lot form this article here. The last step is now to implement the canonical URL in the mobile site to avoid duplicated content.
    With Node Words, my normal URLs have the canonical URL, but these do not appear in the mobile URLs head. Thus I suppose I need this code that is mentioned above to insert somewhere. But where?
    Where exactly in the “mobile module” do I insert the code mentioned above to avoid duplicated content and what parts of the code need to be adapted to work on m.example.com.
    Do I insert it in the mobile theme files (where?) or somewhere else?

  9. Posted February 13, 2012 at 8:37 pm | Permalink

    This set of articles was instrumental to define the mobile solution for my sites that work with Boost.
    Now I have a SEO and user-friendly mobile site solution ready for Drupal 6 that works in both a single site and multi-site (shared code base and separate databases for main sites) configuration. It uses standard Drupal modules (Mobile Tools, Domain Access, Domain Meta Data, Nodewords), a very similar (but slightly different) htaccess recipe as here and a small modification of the Nokia Mobile theme.

    Find here the instructions and tutorial for this solution:
    http://www.drupalinternetbusiness.com/content/drupal-6-mobile-site-tutorial-theme-tools-boost-domain-access

    Thanks for pointing me with your site in the right direction.

  10. Posted February 15, 2012 at 7:50 am | Permalink

    Hi Van Beek. Thanks for the kind words. Glad the articles were a help.

    Sorry it has taken me so long to get around to replying to your original post. When it came to getting the canonical metatag in I’m pretty sure I just hacked something up. It might have gone in a hook_init or it could have easily gone into the theme layer as well. You could write your own “mysite_mobile” theme and put the logic into mysite_mobile_init() function.

    I think that the Drupal community has moved on a bit since I wrote these articles. Responsive design is all the rage. If I was to do a mobile site these days I would try to do it with a single theme and make it responsive. I still like the idea of a device global variable which could be used for placing “desktop” blocks when needed. Many frown on this but I don’t see it as a big problem. You have the caching issues still to deal with.

  11. Posted February 15, 2012 at 7:56 am | Permalink

    Hi Katie. Sorry for the slow replay again :) You can set up any domain you like. Once you have DNS and Apache setup it is just a matter of making sure that Drupal knows about your site. D7 and D6 do this in slightly different ways.

    Any advice for setting up mobile sites? Well I tried my best to share my experiences in these articles. These days I would:
    - write a single responsive theme (based on Omega)
    - use responsive images
    - use a device global variable for getting out of sticky issues server side. ie block placement with context using the device variable as a condition. Mobile Tools does this I believe.
    - use that device variable as a part of a cache key in Varnish
    - serve both sites from a canonical domain and forgo the use of m.example.com

    This would put you in a good position for a multisite or single site install.

One Trackback

  1. [...] This post was mentioned on Twitter by SanjeevJain. SanjeevJain said: Cruncht: Mobile Drupal (part 2): Site setup http://bit.ly/gNLL6W [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>