The Apache HTTP Server (HTTPD) is extremely configurable and powerful. However, its modules are a collection of disparate and inconsistent tools. In this article, I’d walk you through a recent problem I encountered when I wanted to combine two modules.
This article assumes some basic familiarity with the HyperText Transfer Protocol (HTTP) requests and the HTTPD server configuration syntax.
The problem I wanted to solve: The
REQUEST_URI variable is supposed to contain the request path as it was supplied in the HTTP request (unless it has been rewritten by
mod_rewrite). However, it was suffixed with “index.html” in
mod_headers when requesting a directory (such as /).
Early on I realized that the value in the suffix came from the
DirectoryIndex directive. When you request a directory, HTTPD tries to fulfill it using an internal subrequest that fetches the file specified in that directive. I understood the underlying problem but not how to retrieve the original
I tried solving this using
mod_setenvif. This was the wrong approach, but I want to walk you through it:
Test1 response header was set to /index.html and
Test2 returned (null). Neither of these was the expected value (/).
A value of null means that the variable hasn’t being set (as opposed to being empty). The code worked as expected when requesting files but fails when requesting directories.
The inconsistencies in how
mod_headers handle the
REQUEST_URI are frustrating. It makes it difficult to use the former module to copy and correct the variable for use with the latter.
If you’re a seasoned HTTPD admin — or an attentive reader — you may have spotted the problem or at least noticed that something is wrong. I’m expecting a response header to appear when requesting directories but the header is gated by a conditional that only acts when the request is a file! But wait — why is the response header being sent at all?
There’s a lot of different moving parts here. I’ve actually explained what’s gone wrong already, but I’ll walk you through it step by step.
First of all, the conditional block isn’t met for the initial request. It’s for a directory so no headers are added. However, to fulfill the request, HTTPD issues a subrequest to request the
DirectoryIndex file. This subrequest fulfills the requirement for running the conditional block and the headers are appended to the subrequest. The headers are attached to the response to the initial request.
So why are the two test headers different, though? The second paragraph in the
mod_setenvif documentation has the answer:
Knowing all of this, we can construct a better workaround to achieve the desired goal. The three differences from the above code example are marked in blue.
This time around when requesting
/ we get different responses. The
Test1 response header still responds with /index.html. The
Test2 response header is now / as desired!
So what is different this time around? The conditional is run for both the initial request (for the directory) and the subrequest for the
DirectoryIndex file. The
PATH variable is set correctly for the initial request, and it isn’t overwritten by the subrequest because the
PATH variable isn’t being set there.
SetEnvIf’s conditional (
REQUEST_URI) is null so the subrequest won’t overwrite the value set for the initial request.
I said I wanted the
REQUEST_URI variable without the index.html suffix. However, the above code example still include it when explicitly requesting
/index.html. You can remove it from the variable in this situation using regular expressions (RegEx). Replace the
SetEnvIf line from the above code example with the following (or adjust to your
DirectoryIndex directive as needed):
There are many types of subrequests, though. You’ll e.g. be running into the same problem again if you’re setting custom error pages with the
ErrorDocument directive. The
REQUEST_URI variable will disclose the location of the error document. You can work around this error specifically by checking the request status code before sending the response header.
The following example combines the above method (checking to see if the variable is empty) and a check to see if the request was successful (that it generated a
HTTP 200 OK successfully responses):
These aren’t the only cases where you could run into subrequests overwriting variables. It should be the most common ones, however. You must read HTTPD module documentation top-to-bottom and read all the associated documentation. All the information you’ll need is [most often] there. It can be a pain to configure it sometimes because it doesn’t behave as you think it does. All the inconsistencies between modules make it hard to apply things you’ve learned about one module on another.