How to Not Use mod_layout

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

  1. 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.
  2. 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:

FilterDeclare Sub
FilterProvider Sub SUBSTITUTE resp=Content-Type $text/html
FilterDeclare SSI
FilterProvider SSI INCLUDES resp=Content-Type $text/html
<LocationMatch "^/(?!(someurl)|(banners))">
# 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.)
Options +Includes
FilterChain +Sub +SSI
Substitute "s|(<body.*?>)|$1<!--#include virtual=\"/banners/header.html\"-->|i"
Substitute "s|(</body.*?>)|<!--#include virtual=\"/banners/footer.html\"-->$1|i"
</LocationMatch>

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.

You might reasonably question the sanity of this approach. One particular problem, you might note with fear, is that I’m parsing HTML with a regular expression. If “<body>” appears verbatim in some Javascript code, it will be replaced. If the tag has an attribute value containing “>”, it will be mis-parsed. Your fears are not misplaced. However, this configuration is still a suitable replacement for mod_layout … because mod_layout does the same thing! I was surprised, when I read the code, to discover that mod_layout doesn’t employ any actual HTML parsing; it just uses a nearly identical regexp search.

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.

12 thoughts on “How to Not Use mod_layout”

  1. It’s always nice to find someone working on the same obscure issue that you are. And at the moment I was struggling with mod_layout and started looking for alternatives. I’m using something similar to your example except I want to include a php file. The SSI include for the php is showing up in the source of my page but not executing. Maybe I don’t have SSI configured correctly? Do you know if what I am attempting should be possible or not?

  2. I’m very happy that I didn’t spend all this time writing up this page for nothing!

    It could be that you hit the same issue I just did, due to deprecation of AddOutputFilterByType. I just updated the example to use mod_filter instead. If you have further problems you might try switching to the form shown in the updated example.

  3. Ben,

    Thanks for this. It’s working on most pages but it’s not showing up on a couple, such as a Tomcat app which is proxied in and a Rails app on Passenger. Good starting point for me, though!

    Justin

  4. I just copy and paste the code into my apache.conf and didn’t work.

    I’m using a xampp server and every time e put the code into my apache.conf the xampp server doesn’t start.

    Should I do any thing else and not just copy and paste the code into my .conf file?
    There is any special location where I should put the code?

    Many thanks.

  5. I’m not familiar with XAMPP’s distribution of Apache. You should ask them how to use this configuration with their software. It’s possible that you need to enable mod_include, mod_filter, and mod_substitute.

  6. Hello Ben,

    I got one question:

    What I must change to make the banner appear in all pages from a specific domain?
    I don’t want the banner to appear in all pages but only in pages from a subdomain. Example: domain.example.com

    I would like to have the banner only in all pages from that domain. How can I do it?

  7. Hi,
    I tried implementing this code in my Apache config (cPanel – Previrtual Host Include) But I kept getting errors, do you think you could give me a hand to setter the mod Filter?

    1. I’m afraid I’m far from an Apache expert. You’d be better off showing the example to a cPanel expert and asking their advice.

  8. Hey Ben,

    I’ve been using your configuration settings for quite sometime and it works flawlessly for me using Apache 2.2. I recently upgraded to 2.4 and there were a few changes required like changing mod_filter: FilterProvider syntax in order to get the configuration to validate and run Apache. While there are no errors reported the expected results are missing. Have you been able to run this configuration successfully using Apache 2.4? Any insight would be helpful…

    1. Sorry, I haven’t actually tried anything like this in years. I’ll be happy to update the post for posterity if you do get it working.

Leave a Reply

Your email address will not be published.

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>