mod_layout is an Apache module designed for inserting dynamic banners (headers and footers) into pages. That’s what I needed to do, so mod_layout seemed heaven-sent. Unfortunately, when I tried to use it, I was blocked by two bugs
- mod_layout misbehaves when the root (/) has a 301 redirect and the banner is specified by URL. I have a wiki in the root (actually /index.php) which creates a 301 redirect to /index.php/Main_Page, and as a result mod_layout’s LayoutHeader directive was failing with errors indicating that it couldn’t find “/var/www/banners/header.html/Main_Page”. Unless I wanted to reorganize the whole server layout, that meant I couldn’t use mod_layout directly.
- I came up with a (slightly nutty) workaround: use mod_layout to insert a static banner containing a Server-Side Include, which when parsed includes the actual banner URL. This requires mod_layout to run first, transform the page, and then pass it to mod_include for additional processing. Apache lets you specify the ordering of these kinds of filters, e.g. “AddOutputFilterByType LAYOUT;INCLUDES text/html”. Unfortunately, mod_layout doesn’t seem to obey the ordering directive, and so the mod_include filter never runs.
I reported these problems to the author, who replied
We have[n't] supported it [(mod_layout)] in years. The apache 2.0 version never could be made to work very well because of changes that kept occurring in the internal Apache API.
That seems perfectly fair to me, but it means these bugs aren’t likely to be fixed any time soon. Accordingly, I drew up a plan for how to not use mod_layout.
The solution is best described by the relevant piece of Apache configuration:
FilterProvider Sub SUBSTITUTE resp=Content-Type $text/html
FilterProvider SSI INCLUDES resp=Content-Type $text/html
# Apply the banners to the whole server except for someurl/, which doesn't
# need them, and banners/, which contains the banners. (It doesn't
# break anything to allow the banners to be processed, but it would cause
# two unnecessary additional SSI passes on every page load.)
FilterChain +Sub +SSI
Substitute "s|(<body.*?>)|$1<!--#include virtual=\"/banners/header.html\"-->|i"
Substitute "s|(</body.*?>)|<!--#include virtual=\"/banners/footer.html\"-->$1|i"
The idea, then, is to use mod_substitute to insert a Server-Side Include immediately after <body> and before </body>. The include directive is then parsed by mod_include to insert the actual banner from a file. We apply all of this inside a LocationMatch section, which allows us to exclude certain subtrees from the processing. In the list of paths that shouldn’t be processed we include the location of the banners, because (at least in my case) they are known not to contain any SSI directives, so parsing them for SSI would just waste time.
The other obvious downside of this approach is that it could be slower than mod_layout. I don’t think this is going to be too much of a problem. The regexps are compiled once when the server starts, and executed using the highly optimized PCRE engine. Apache SSI is also highly optimized. mod_substitute is not especially tuned for speed. Unless your server is truly CPU-limited, I think any additional processing is likely to be irrelevant.
So in conclusion, if mod_layout works for you, great! If not, you might try this instead.
TO DO: Devise insane regexps that are safer but not too slow, and/or Fix mod_layout. Contributions welcome.
EDIT: AddOutputFilterByType is deprecated in Apache 2.2, and was misbehaving for me after an upgrade (disobeying the filter ordering). Example updated to follow Apache’s advice and switch to mod_filter, which fixed the behavior.