VirtualHostingContrib
Adds virtual hosting support for Foswiki.
Overview
VirtualHostingContrib provides the ability of using a single Foswiki
installation to serve differents sets of webs under different host names. Such
different hostnames by which users access different content are known as
virtual hosts.
For users, it will look like they have an exclusive Foswiki installation. The
system administrator, however, needs to maintain and upgrade a single Foswiki
installation. This solution allows for instance service providers to offer
Foswiki systems without having a linear increase in maintainance effort.
Installation Instructions
You do not need to install anything in the browser to use this extension. The following instructions are for the administrator who installs the extension on the server.
Open configure, and open the "Extensions" section. Use "Find More Extensions" to get a list of available extensions. Select "Install".
If you have any problems, or if the extension isn't available in
configure, then you can still install manually from the command-line. See
http://foswiki.org/Support/ManuallyInstallingExtensions for more help.
Configuration
VirtualHostingContrib works by providing an alternative entry point to the
classic Foswiki scripts interface. To use it, you need to configure your
webserver so that requests to e.g.
/foswiki/bin/view are actually handled by
the
/foswiki/bin/virtualhosts CGI script. Such script will play the role of
all the other scripts, but before serving each request it will setup the
appropriate environment for the virtual host being used.
Lighttpd
TODO
Apache
TODO
Using FastCGI
The
virtualhosts script is a regular CGI script, and as such it may exhibit
poor performance in sites with low computing power or high usage, just like
regular Foswiki.
VirtualHostingContrib provides a FastCGI version of the
script.
To use it:
- Make sure you have FastCGIEngineContrib installed and properly working on your system.
- In your web server configuration, replace the references to the
foswiki.fcgi script provided by FastCGIEngineContrib with virtualhosts.fcgi provided by VirtualHostingContrib.
- Reload your web server configuration to apply the changes.
Virtual hosts management
This section describes how to perform common maintenance activities on your
virtual hosts.
Settings
The following settings are available for this contrib in the Foswiki
configuration interface:
| Setting |
Meaning |
Default value |
VirtualHostsDir |
The directory in which you virtual hosts are stored |
$Foswiki::cfg{DataDir}/../virtualhosts |
The structure of a virtualhost
Each virtual host consists of a subdirectory of
$Foswiki::cfg{VirtualHostingContrib}{VirtualHostsDir}, named after the
hostname it is intended to serve. Take the following example:
virtualhosts/
example.com/
...
mydomain.net/
...
In this case, there are two virtual hosts, one for the
example.com domain and
other for the
mydomain.net domain.
Inside each virtual host directory, there are some directories that resemble
the ones in the main Foswiki installation:
| Directory |
Stores |
data/ |
topic data |
pub/ |
attachments and other files that must be directly accessible by clients |
templates/ |
custom templates for this virtual host only |
working/ |
all sorts of temporary data |
virtual host specific settings
In each virtual host root, the server administrator can put a file called
VirtualHost.cfg, which may contain Foswiki configuration variables. This way
you can have different settings for different virtual hosts. For example, you
can have some virtual hosts using the standard .htpasswd Foswiki user
management, and others using LDAP to authenticate users.
To create your local configuration, just create a file called
VirtualHost.cfg
inside the virtual host directory. Inside the file, you use the
$VirtualHost
hash the same way you would use
$Foswiki::cfg in the global Foswiki
configuration.
Example:
# disable FooPlugin in this virtual host
$VirtualHost{Plugins}{FooPlugin}{Enabled} = 0;
# use LDAP contrib in this virtual host
$VirtualHost{PasswordManager} = 'Foswiki::Users::LdapPasswdUser';
$VirtualHost{UserMappingManager} = 'Foswiki::Users::LdapUserMapping';
# ...
Note that
all occurrances of $Foswiki::cfg in the virtual host
configuration will be replaced by $VirtualHost. This way you can
still copy and paste configuration examples into your virtual host
configuration, but the virtual host configuration won't affect the global
configuration. A side effect of this is that you cannot do smart things such
as:
$Foswiki::cfg{AuthScripts} .= ',view';
Warning: just like Foswiki's global configuration file
LocalSite.cfg, virtual host configuration files are executed as Perl code.
%So the system administrator must be completely sure of what to put in that
%configuration file.
Creating/removing/renaming virtual hosts
To create a virtual host, you can use the provided script at
tools/virtualhosts-create.sh. To create a virtual host for the example.com
domain, you should run it like that:
$ ./tools/virtualhosts-create.sh example.com
If you run this script as a user different from the one that runs the Foswiki
code (e.g. you create the virtualhost as
root but your web server runs as
www-data or
nobody), the
data/ and
pub/ directories will be their
ownership properly set to the correct user (i.e.
www-data or
nobody).
If you have a special virtual host called
_template, then it will be copied
over to the new virtual host. If there is no
_template virtual host, the
script will create the virtual host by copying files from the main Foswiki
data; this is indicated if your installation contains only virtual hosts, so
the main Foswiki data will always be clean. You can also create a
_template
virtual host manually by copying the files from a Foswiki release tarball,
or by manually creating the data you want for every newly created virtual hosts. If
your installation is brand new and you plan to use the main Foswiki data
afterwards, you can also create the
_template virtual host using the script,
and that will
freeze a copy of the main Foswiki data for new virtual hosts
created later.
If you have a text file called
_template.conf in your virtual hosts
directory, when you create a virtual host for example.com you'll also get a
example.com.conf file which is just equal to the
_template.conf file,
except that all occurrences of
%VIRTUALHOST% will be replaced by the
virtual host name (
example.com in this example).
Note that
VirtualHostingContrib does not care about the contents of the file, it just
takes the content of
_template.conf, replaces all occurrences of
%VIRTUALHOST% by the virtual host name, and that's it.
To remove a virtual host, just remove the its files. If you are removing the
virtual host for
example.com, and your virtual hosts are stored in
/var/lib/foswiki/virtualhosts (see
#Settings), then it is enough to remove
the
/var/lib/foswiki/virtualhosts/example.com directory, plus any webserver
configuration you have for that virtual host. Your mileage may vary.
To rename a virtual host, you just have to rename it's directory, and if it's
the case, change the configuration files accordingly. Beware that after
renaming a virtual host its data will
no longer be available at the old
domain name.
Running command line tools against the virtual hosts
TODO
How it works
This section describes the internals of
VirtualHostingContrib, and is intended
for developers and system administrators. If it's not your case, please feel
free to skip it.
Tweaking the global configuration
In order to serve different sets of webs within the same Foswiki installation,
we need to tweak the Foswiki configuration to change some settings, such as
DataDir and
PubDir (the directories where topic text and attachments are
stored, respectively).
The Foswiki configuration is loaded during compilation time, but when using
persistent execution models (e.g. ModPerl, FastCGI), the Foswiki Perl code is
compiled only once for multiple requests. This wat, the needed configuration
tweaking must be done once for each request, and moreover must not not leave
any trail for the next request, which will possibly be handlind a different
virtual host (and thus different settings for
DataDir,
PubDir, etc.).
We need something like this:
+-----------------+
| |
+----------+ Foswiki startup +
| | |
| +-----------------+
| Main
| configuration
| loaded
|
| Configuration cleaned
| +---------------------------------+
| | |
| V |
+------------------+ +---------+---------+
| | | |
| Incoming Request + | Outgoing Response +
| | | |
+--------+---------+ +-------------------+
| ^
| Configuration ajusted |
| for current virtual host |
| |
| |
| +--------------------+ |
| | | |
+---->| Request processing +------+
| (conf. loaded) |
+--------------------+
Since all Foswiki-specific processing happens inside the "Request processing"
box, then if we manage to tweak the configuration before it is run and to
restore it after the request is processed, we are all set. For that, we hook in
the Foswiki engines system.
The Foswiki engines system
All requests are processes by the current Foswiki engine in the following way
(
$self is the engine object):
| 1 |
A Request object is prepared, |
$req = $self->prepare() |
| 2 |
The processing is delegated to the Foswiki::UI module. This module detects what was the called script (view, edit, attach etc.), instantiates a Foswiki session and does all of the actual processing to produce a result for the client. In special, this phase is the only one in which the vast majority of the Foswiki configuration matters. |
$res = Foswiki::UI::handleRequest($req) |
| 3 |
The engine does a cleanup and sends the response back to the client |
$self->finalize($req, $req) |
So, our objective is to wrap
step 2 as the figure above shows: we adjust the
configuration for the current request just before entering
Foswiki::UI::handleRequest, and restore the configuration just after it
finishes.
The Perl local() construct
The Perl
local() construct enables the programmer to set a global variable
until the end of the current block. After the current block exits, the value is
restored to its original value. This can be also used with hash elements: in
this case, only the elements changed with
local are restored when the scope
ends.
Example:
use Foswiki;
# Assume that DataDir is set initially to "/path/to/foswiki/data"
print $Foswiki::cfg{DataDir}, "\n"
{
local = $Foswiki::cfg{DataDir} = "/path/to/virtualhosts/example.com/data";
print $Foswiki::cfg{DataDir}, "\n";
}
print $Foswiki::cfg{DataDir}, "\n"
The above example will print the following:
/path/to/foswiki/data
/path/to/virtualhosts/example.com/data
/path/to/foswiki/data
This way we can override the settings we need, and also gain the cleanup step
for free since Perl will automatically revert all changes done with local when
the block ends.
For more information on
local(), please refer to "Temporary Values via
local()" in the perlsub(1) manpage (you must have Perl's documentation
installed in your system).
Wrapping it up
What
VirtualHostingContrib does is to wrap the Foswiki::UI::handleRequest so
that the configuration is tweaked just before actually handling the request,
using the approach described above. The two extra CGI scripts provided are
equal to the ones in Foswiki core and FastCGIEngineContrib, but after loading
Foswiki::UI, they load the
VirtualHostingContrib module that does the wrapping
around Foswiki::UI::handleRequest.
Info for developers
This section presents tips for developing of virtualhost-friendly code:
reading Foswiki configuration values
When you need to read Foswiki configuration options, make sure you do not do it
during compile Time. Since virtualhost requests temporarily redefine part of
the configuration, if you freeze a configuration value during a request for
virtualhost A, you'll probably have problems when using that value in a request
for virtualhost B.
Example: if you do something like this:
use vars qw($RE);
BEGIN {
$RE = qr/$Foswiki::cfg{SomeOption}/;
}
If this code runs under ModPerl, FastCGI or other persistent engine, Then the
$RE variable will be initialized in the first time that code is
loaded, and that may be in the context of a specific virtual host. In a future
request, $RE will still contain a value derived from the value of
$Foswiki::cfg{SomeOption} for the original virtualhost, even if
the current virtualhost redefines
$Foswiki::cfg{SomeOption}.
To fix this, you should avoid initializing values that depend on the
configuration during compile-time. Instead, compose the values you need during
runtime:
sub do_stuff {
my $RE = qr/$Foswiki::cfg{SomeOption}/;
// use $RE here ...
}
or in a OO context:
sub new {
// ...
$this->{RE} = qr/$Foswiki::cfg{SomeOption}/;
// ...
}
sub method {
my $this = shift;
// use $this->{RE} ...
}
Avoid using the 'o' regular expression flag together with configuration values
If you need a regular expression that is composed from values in the
configuration, make sure you don't use the 'o' flag, which causes the regular
expression to be compiled only once. For example, in the following code the
$Foswiki::cfg{SomeOption} will be expanded only in the first time
the expression is evaluated:
my $RE = qr/$Foswiki::cfg{SomeOption}/o;
Even is a virtualhost redefined
$Foswiki::cfg{SomeOption}, the
value used in that regular expression will be the value defined in the context
of the virtualhost that was being processed during the first time that
expression was evaluated.
So, avoid the 'o' flag when interpolating configuration values in regular
expressions. The following real code shows the problem:
use vars qw($GLOBAL);
$GLOBAL = 'Main';
sub f {
my $re = qr/(?:$GLOBAL\.)/o;
print $re, "\n";
}
f();
$GLOBAL = 'Users';
f();
The two calls to f() will print the same value, based on the value
$GLOBAL has in the first time the regular expression was
evaluated:
$ perl test.pl
(?-xism:(?:Main\.))
(?-xism:(?:Main\.))
If you remove the 'o' flag from the regular expresion, you will obtain the
desired result:
$ perl test.pl
(?-xism:(?:Main\.))
(?-xism:(?:Users\.))
Info
Many thanks to
Colivre for supporting this work.