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 REQUEST_URI
variable.
I tried solving this using mod_setenvif
. This was the wrong approach, but I want to walk you through it:
When requesting /
, the 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_setenvif
and 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 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.