Getting Started (as of v0.1.2)
Before you can set up virtual hosting in Zope, you need to answer two questions:
- What is the URI of each site's logical root?
- What is the corresponding path to the physical root?
Suppose, for example, that you want to use Zope to host the domain www.hotsite.com,
and you want 'http://www.hotsite.com' to publish the Zope object '/hotsite/index_html'.
Then 'http://www.hotsite.com' is the URI of your logical root, and '/hotsite' is the
path to your physical root.
Example 1: One Site
Put a SiteRoot in '/hotsite', your physical root, and accept the default Path.
Make a DTML Method in the Zope root folder containing
Is the first-level domain 'hotsite.com'? Ignore sub-domains and port number.
<dtml-if "_.string.split(_.string.split(HTTP_HOST, ':')[0], '.')[-2:]==['hotsite', 'com']">
Add physical root:
<dtml-call "REQUEST.path.append('hotsite')">
</dtml-if >
Use "Set Access Rule" with the DTML Method's Id. Want to understand this? Read on.
Getting Physical
The first half of virtual hosting is rewriting incoming URIs into physical paths.
Many people run ZServer behind Apache, or another HTTP server with rewriting
capabilities. In these cases, you can tell the front-end HTTP server to rewrite
'http://www.hotsite.com/(.*)' to '/blah/cgi/Zope.cgi/hotsite/$1', for example.
This works perfectly well, but if your clients are connecting directly to ZServer, or
if you would like to keep all of the virtual hosting logical in Zope, you will need
to do your rewriting in an Access Rule.
An Access Rule is just a regular method (DTML Method or Document, External Method,
Python Method, etc.) on which you have used SiteAccess' "Set Access Rule" method. In
this case, the method will examine the incoming request and decide how to rewrite it.
The example DTML Method above is the simplest kind of rewrite rule, forcing
all requests to traverse the 'hotsite' object before any others in the URI.
Getting Logical
The second, and more difficult, half of virtual hosting is getting your Zope objects to
generate correct logical URIs for links and images. For example, if you are rewriting
hotsite as described above, then a standard DTML snippet such as
<a href="<dtml-var URL>/hottopics">
in object '/hotsite/forum' will generate
<a href="http://www.hotsite.com/hotsite/forum/hottopics">
rather than
<a href="http://www.hotsite.com/forum/hottopics">
To prevent this, all of the URLn and BASEn request variables and the absolute_url()
method need to be told to strip off '/hotsite'. That's what SiteRoot objects do.
A SiteRoot object should be placed in the physical root folder ('/hotsite', in this case)
and told the logical URL at which to base all requests passing through this folder. You
tell it by setting its Path property, which in this case should have the value '/'. For
flexibility's sake, this can also be set as a property 'SiteRootPATH' of the '/hotsite'
folder or of the root folder, or it can
be set in the rewriting Access Rule with a call to "REQUEST.set('SiteRootPATH', '/')", or
it can be set in Apache's rewrite rules as an environment variable. You can also provide
a Base ('SiteRootBASE') value, which will then replace the host:port/script portion of URIs.
Example 2: Multiple Sites
Suppose we are hosting 'hotsite.net', 'fooflowers.com', and 'openmouths.org' from
'/hotsite', '/foof', and '/openm' respectively.
We are distinguishing requests via HTTP_HOST, and we don't care what subdomain or port was specified.
Put a SiteRoot in each of '/hotsite', '/foof', and '/openm'.
In each one, erase the default Path and leave Base blank.
Make a DTML Method in the root folder containing
Extract the part of HTTP_HOST we care about, and declare our rewrite dictionary.
<dtml-let hostname="_.string.join(_.string.split(_.string.split(HTTP_HOST, ':')[0], '.')[-2:], '.')"
sitemap="{'hotsite.net': 'hotsite',
'fooflowers.com': 'foof',
'openmouths.org': 'openm'}">
Do we have a match?
<dtml-if expr="sitemap.has_key(hostname)">
Set the logical root: <dtml-call "REQUEST.set('SiteRootPATH', '/')">
Add physical root: <dtml-call "REQUEST.path.append(sitemap[hostname])">
</dtml-if>
</dtml-let>
Use "Set Access Rule" with the DTML Method's Id. An almost identical method can be
used to distinguish sites by SERVER_ADDRESS and SERVER_PORT instead of HTTP_HOST. In
that case, though, you would probably add a line to set the appropriate SiteRootBASE.
If you wanted all of these virtual hosts' root folders to live in the folder 'vhosts',
you could add the line:
Add vhost root: <dtml-call "REQUEST.path.append('vhosts')">
after the 'Add physical root' line. If you wanted to add multiple path elements
for each site, you could use path.extend instead of path.append and map 'fooflowers.org',
for example, to ['foof', 'f', 'comsites']. This would place the root of fooflowers in
folder '/comsites/f/foof/'.
Minor Notes
- The return value of an Access Rule is ignored and discarded. This allows embedded
string comments such as in the examples above, and the use of <dtml-return " 'ignored' ">.
It also means that if you want to redirect within an Access Rule, you must use
<dtml-raise type="Redirect"> instead of "RESPONSE.redirect()"
- A SiteRoot object is essentially an Access Rule consisting of
<dtml-call "REQUEST.setURL(base=SiteRootBASE, path=SiteRootPATH)">
and you can use setURL() in your own code.
- You might want to exempt management access from being affected by the virtual hosting.
One way to do this is to wrap the rest of your Access Rule code in something like:
Is there a path, and does it end in manage*?
<dtml-unless "REQUEST.path and REQUEST.path[0][:6]=='manage'">
...
</dtml-unless>
or provide a special global-access prefix such as 'Zope':
Is there a path, and does it start with Zope?
<dtml-if "REQUEST.path and REQUEST.path[-1]=='Zope'">
Get rid of 'Zope': <dtml-call "REQUEST.path.pop()">
Put it back logically: <dtml-call "REQUEST.setURL(path='Zope')">
<dtml-else>
...
</dtml-if>
- If a SiteRooted folder is ever accessed through URLs with
a base or path that does not get rewritten to match the Base and Path of the SiteRoot,
you should make the SiteRoot's Base and Path blank and dynamically create SiteRootPATH/SiteRootBASE
variables. For example, if you made a 'Zope' global-access prefix as described
above, then the 'else' part should contain something like
<dtml-call "REQUEST.set('SiteRootPATH', '/')">.