Nov 06

So the company I work for is in the process of rolling out a project for Service Oriented Architecture to provide some kind of interface between all our disparate applications; currently sitting as a mix of Java, ASP.NET, classic ASP and Drupal. Wow. I’m currently working with our Drupal based sites and oh no… I have to integrate with our “mega transformation and messaging engine”, also known as an Enterprise Service Bus using web services.

SOAP, REST or XML-RPC?

This was very much a contract first, code after project, so SOAP was needed. Yes, that meant lots of WSDL docs and XSDs… the syntax is frightening in places but it’s great in it’s own way. The stuff I’ll be discussing covers SOAP 1.1 using document/literal encoding.

You could indeed use REST… and if you’re working with XML-RPC, I would imagine you’re either supporting a legacy system or you’re really into punishing yourself!

Drupal and web services?

Two factors spring to mind with Drupal…. loose typing and the unique manner of it’s URL mapping.

Loose typing is an inconvenience. When a SOAP contract is defined it defines an element and it’s data type. So how does your underlying PHP code understand that should be treated as a boolean? The short answer is…. it doesn’t. Bummer.

What tends to happen is a lot of assumption takes place. The SOAP contract will not provide anything other than a boolean value, so you can, usually, cast your data into the required data type and help the process along. There are other issues with complex types but I will come to those later. If you’re accustomed, like I am, to the excellent capabilities of .NET to generate a class based on a WSDL you’ll find yourself stressing a bit with PHP. No matter… onwards.

URL mapping, a bit like the controller in an MVC framework, binds a given path to a module and a function, or page callback. The problem is how do you get Drupal to work with SOAP requests/posts to a given URL and if it’s possible, how does it perform?

I looked at the services module for Drupal and found it to be incomplete, slow and difficult to tailor for my project’s needs. That said, I think the concept of their module is excellent and would like to see it develop into a more polished product. In the end, something of a compromise was realised; PHP does SOAP servers and clients, Drupal is PHP – so why not create something that takes advantage of the two and avoids many of the complications?

SOAP server or SOAP client

This depends on the nature of your application. For my task I had to create both – most data going in across a SOAP server and some data being pushed out via a SOAP client. The SOAP server is more interesting, so let’s look at that…

I am aware of the NuSOAP libraries for PHP4, but as I work with PHP5 we’re lucky enough to have the native SoapServer and SoapClient objects at our disposal. The SoapServer client is easily set up with a few lines of code:


< ?php
// see http://www.php.net/manual/en/soapserver.soapserver.php for a comprehensive examination
$server = new SoapServer("some.wsdl");
$server->addFunction("YourFunctionName");
$server->handle();
?>

That’s it. The hard part comes with writing your function and getting your WSDL generated, or written, correctly.

Interpreting your request

Ok, let’s assume we have a WSDL doc which defines a quick function to add data about a user. It defines the following elements – I’ll use pseudo-code for this as I haven’t got a WSDL generator at hand:

* email (string)
* active (boolean)
* roles (array of strings)


/**
* function accept and process a SOAP request
*/
function AddData($request) {
$email = (string)$request->email;
$active = (bool)$request->active;
$roles = (array)$request->roles;
}

Note how elements within the SOAP request are always accessed as you would access object properties. You might be able to use array syntax, but I’m not sure if this would work reliably… if it all.

Most common gotchas when processing SOAP requests

  • Is it a string, or an array!? – the answer is… it depends. Say $request->roles only contains a single element – PHP doesn’t know the type, only sees a single element and assumes it’s a string. If there is more than a single element, PHP assumes it’s an array. This has caused me some serious ball aches when I’m expecting an array with a single element.
  • Booleans… yes, 1, or true? – sadly another area of ambiguity. I would argue, for consistency, people use the string representation (ie: true/false). However, that’s not always guaranteed and PHP evaluates “Yes” and “No” to 1 and 1 respectively. Not sure why, but quickly solved with a function to force “Yes”/”No” into the right form.

Integrating with Drupal

You could do what I did at first by writing some independent PHP which happens to interact with the same database as your Drupal site. That’s not so great as it doesn’t allow you to use many of good things Drupal has – such as the database abstraction library, variable system or access to your module code (code re-use!!).

A chap I work with (http://codeofrob.com) suggested bootstrapping Drupal to a minimal level to achieve this. So my SoapServer started to look like this:


< ?php
require_once './includes/bootstrap.inc';
/**
* Sixth bootstrap phase: load bootstrap.inc and module.inc, start
* the variable system and try to serve a page from the cache.
*/
drupal_bootstrap(DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE);
require_once './includes/common.inc';
$serviceName = $_GET['s'];
include_once './services/' . $serviceName . '/service.php';

Let's look at what's going on here. This file lives in the root of my site and acts as an interface to all my disparate services. More importantly, this file is bootstrapping Drupal up to the DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE level which allows me to take advantage of many aspects of the system. Ultimately - this is a process which is comprised of several server side includes (for the various modules) and a few global variable loads. It stops short of fully bootstrapping Drupal so there is a performance bonus. Of course, if I needed access to something which requires a full bootstrap I would have to increase this.

Integration with Drupal as a fully fledged module

I've not looked into this fully, but I believe this would be the nicest, most usable format for a web services module to reside in. There are a number of concerns I have about this:

  1. Speed. The services module is not good (at the time of writing this) in terms of performance and functionality.
  2. Authorisation. Can you use anything other than forms based authentication? I currently use HTTP basic authentication or IP based access - I'm not sure this would be particularly easy to do directly in Drupal.
  3. Focus. Web services are massively varied - both in terms of messaging formats, protocols and payloads. Could a module with a fancy admin interface abstract some of this away? Difficult job if you ask me!

Wrapping up

I have to admit I've only glossed over this topic for now as I'm still understanding some parts of it. My intention is to provide an alternative look at integrating your Drupal site with some of the more monolithic pieces of an enterprise scale system.

Another great resource is this book: Pro PHP XML & Web Services (Books for Professionals by Professionals) - it was a real help in understanding SOAP, web services and more importantly - how I could get PHP to work with it all.

Tagged with:
Aug 31

One of the fun tasks I had to look at recently was establishing a way of serving up multiple sites from a single installation of Drupal on a machine. This is incredibly useful for developers as it allows them a great deal of control over their local development sites with the convenience of not exclusively dealing with IP addresses… unless they want to :)

For those who might be new to this kind of concept, it’s fairly similar to how IIS might serve up multiple websites on a single IP address by analysing the host headers. However, we’re using Apache so we don’t need to worry about IIS’s interesting interpretation of GUI design.

Drupal.org has a fairly detailed overview of the process, but it’s missing a fairly useful step I believe which is how to alias another IP to your local loopback adapter. I’m running a copy of OS X 10.5 (Leopard to most) and if I ping the address 127.0.0.2 I don’t get a response. “That’s strange” you might say… and indeed in terms of convention, it is. If I do the same on a Windows or Linux box it immediately comes back with a chirpy:

64 bytes from 127.0.0.2: icmp_seq=3 ttl=64 time=0.059 ms

Lovely. So why does OS X not respond on this? It could well be down to Apple developers’ interpretation of Special use IP v4 Addresses.

Either way, it doesn’t respond by default. A temporary solution is this sudo ifconfig lo0 alias 127.0.0.x up – this assigns an additional address to the loopback adapter (lo0). It isn’t persistent and I haven’t found out exactly why at this stage, but it’s on the to do list :)

Configuring Apache

I’m assuming you’ve already got Apache set up with the prerequisites for Drupal. If not, get this done first as I’m not covering clean URLs, PHP configuration or installation basics…

Once you’ve got an IP address to listen on, you’ll need to get Apache listening on that address and serving up the right stuff. Your Apache config may differ to mine as implementations are different (sometimes considerably!) from distro to distro. Broadly speaking, you will want to find the place where you can configure your VirtualHosts. Sometimes that’s at the bottom of your httpd.conf file, other times it’ll be in a totally seperate directory – it varies depending on your server version and distro. Eeek.

Boiler plate VirtualHost config

Assuming you’re using MAMP on OS X – you can find the VirtualHosts section at the bottom of the /conf/apache/httpd.conf file. All you have to do is add the following declaration:

<VirtualHost 127.0.0.x:80>
DocumentRoot /path/to/your/drupal/root/
ServerName whatever.name.you.like
</VirtualHost>

Adding a DNS entry, or editing your local hosts file

If you can do this part through DNS on your network, that’s ace. If not, you can edit a file on your machine that binds a name to a given IP address. This little beauty lives under:

/private/etc/hosts

under OS X. I know, what a great path name! For more information, see Wikipedia’s entry for the hosts file.

On a new line, add your 127.0.0.x address plus the corresponding name you’re using for your VHost. Save the file, ping whatever.name.you.like and you should get a chirpy response on 127.0.0.x. If not, make sure you’ve not got any spelling mistakes or haven’t bound the alias improperly. If you screwed up aliasing… using the ifconfig -alias command to remove it.

Once that’s done – reboot Apache for the changes to kick in. Point your browser to your site name to check Apache is doing what it’s meant to do.

Configure Drupal

Ok, so by now you should have a web site that is serving up Drupal which should be prompting you to install a new site. Nice.

The next step is to add an entry into the /drupal/sites/ folder. Create a folder in this for your site, eg: whatever.name.you.like – it’s really important you’re consistent as if the folder name doesn’t match what’s in the host header Drupal won’t know which site to serve up and defaults to the /drupal/sites/default/ folder.

Once you’ve created your folder, all you need to do is copy the settings.php file from another site, or the default.settings.php file under /drupal/sites/default/ into whatever.name.you.like/settings.php.

After that, edit the file, complete the $db_url (and if necessary – the $base_url) for your specific database, save the file and browse to http://whatever.name.you.like

If all has been done correctly you should now be looking at a separate site being served up by a single instance of Drupal.

Summary

This approach is really useful, because if you have a lot of sites and/or clients you can run all those sites locally. All you need is:

  • A database per site
  • A free IP address on your loopback adapter
  • An entry in your hosts file or DNS for your site name
  • An Apache VirtualHost
  • A sites folder for Drupal with the same name

Other considerations

  • You don’t have to use a loopback adapter. It could be another network card in your machine, or indeed another machine.
  • You don’t have to use site names – you can stick with IP addresses too.
  • Drupal settings.php files are just that – PHP files. If you’ve got loads of sites, consider centralising all site settings into a single file and using the require_once() function to control which files are loaded, depending on which site is being requested.

Tagged with:
preload preload preload