home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam    


Chapter 8. Redirection

Few things are ever in exactly the right place at the right time, and this is as true of most web servers as of anything else. Alias and Redirect allow requests to be shunted about your filesystem or around the Web. Although in a perfect world it should never be necessary to do this, in practice it is often useful to move HTML files around on the server Ч or even to a different server Ч without having to change all the links in the HTML document.[1] A more legitimate use Ч of Alias, at least Ч is to rationalize directories spread around the system. For example, they may be maintained by different users and may even be held on remotely mounted filesystems. But Alias can make them appear to be grouped in a more logical way.

A related directive, ScriptAlias, allows you to run CGI scripts, discussed in Chapter 16. You have a choice: everything that ScriptAlias does, and much more, can be done by the new Rewrite directive (described later in this chapter), but at a cost of some real programming effort. ScriptAlias is relatively simple to use, but it is also a good example of Apache's modularity being a little less modular than we might like. Although ScriptAlias is defined in mod_alias.c in the Apache source code, it needs mod_cgi.c (or any module that does CGI) to function Ч it does, after all, run CGI scripts. mod_alias.c is compiled into Apache by default.

Some care is necessary in arranging the order of all these directives in the Config file. Generally, the narrower choices should come first, with the "catch-all" versions at the bottom. Be prepared to move them around (restarting Apache each time, of course) until you get the effect you want.

Our base httpd1.conf file on ... /site.alias, to which we will add some directives, contains the following:

User webuser
Group webgroup


<VirtualHost www.butterthlies.com>
ServerName www.butterthlies.com
DocumentRoot /usr/www/APACHE3/site.alias/htdocs/customers
ErrorLog /usr/www/APACHE3/site.alias/logs/error_log
TransferLog /usr/www/APACHE3/site.alias/logs/access_log

<VirtualHost sales.butterthlies.com>
DocumentRoot /usr/www/APACHE3/site.alias/htdocs/salesmen
ServerName sales.butterthlies.com
ErrorLog /usr/www/APACHE3/site.alias/logs/error_log
TransferLog /usr/www/APACHE3/site.alias/logs/access_log

Start it with ./go 1. It should work as you would expect, showing you the customers' and salespeople's directories.

8.1 Alias

One of the most useful directives is Alias, which lets you store documents elsewhere. We can demonstrate this simply by creating a new directory, /usr/www/APACHE3/somewhere_else, and putting in it a file lost.txt, which has this message in it:

I am somewhere else

httpd2.conf has an extra line:

Alias /somewhere_else /usr/www/APACHE3/somewhere_else

Stop Apache and run ./go 2. From the browser, access http://www.butterthlies.com/somewhere_else/. We see the following:

Index of /somewhere_else
. Parent Directory
. lost.txt

If we click on Parent Directory, we arrive at the DocumentRoot for this server, /usr/www/APACHE3/site.alias/htdocs/customers, not, as might be expected, at /usr/www/APACHE3. This is because Parent Directory really means "parent URL," which is http://www.butterthlies.com/ in this case.

What sometimes puzzles people (even those who know about it but have temporarily forgotten) is that if you go to http://www.butterthlies.com/ and there's no ready-made index, you don't see somewhere_else listed.

8.1.1 A Subtle Problem

Note that you do not want to write:

Alias /somewhere_else/ /usr/www/APACHE3/somewhere_else

The trailing / on the alias will prevent things working. To understand this, imagine that you start with a web server that has a subdirectory called fred in its DocumentRoot. That is, there's a directory called /www/docs/fred, and the Config file says:

DocumentRoot /www/docs

The URL http://your.webserver.com/fred fails because there is no file called fred. However, the request is redirected by Apache to http://your.webserver.com/fred/, which is then handled by looking for the directory index of /fred.

So, if you have a web page that says:

<a href="/fred">Take a look at fred</a>

it will work. When you click on "Take a look at fred," you get redirected, and your browser looks for:


as its URL, and all is well.

One day, you move fred to /some/where/else. You alter your Config file:

Alias /fred/ /some/where/else

or, equally ill-advisedly:

Alias /fred/ /some/where/else/

You put the trailing / on the aliases because you wanted to refer to a directory. But either will fail. Why?

The URL http://your.webserver.com/fred fails because there is no file /www/docs/fred anymore. In spite of the altered line in the Config file, this is what the URL still maps to, because /fred doesn't match /fred/, and Apache no longer has a reason to redirect.

But using this Alias (without the trailing / on the alias):

Alias /fred /some/where/else

means that http://your.webserver.com/fred maps to /some/where/else instead of /www/docs/fred. It is once more recognized as a directory and is automatically redirected to the right place.

Note that it would be wrong to make Apache detect this and do the redirect, because it is legitimate to actually have both a file called fred in /www/docs and an alias for /fred/ that sends requests for /fred/* elsewhere.

It would also be wrong to make Apache bodge the URL and add a trailing slash when it is clear that a directory is meant rather than a filename. The reason is that if a file in that directory wants to refer visitors to a subdirectory .../fred/bill, the new URL is made up by the browser. It can only do this if it knows that fred is a directory, and the only way it can get to know this is if Apache redirects the request for .../fred to /fred/.

The same effect was produced on our system by leaving the ServerName directive outside the VirtualHost block. This is because, being outside the VirtualHost block, it doesn't apply to the virtual host. So the previously mentioned redirect doesn't work because it uses ServerName in autogenerated redirects. Presumably this would only cause a problem depending on IPs, reverse DNS, and so forth.


Script method cgi-script
Server config, virtual host, directory 
Script is only available in Apache 1.1 and later; arbitrary method use is only 
available with 1.3.10 and later.

This directive adds an action, which will activate cgi-script when a file is requested using the method of method. It sends the URL and file path of the requested document using the standard CGI PATH_INFO and PATH_TRANSLATED environment variables. This is useful if you want to compress on the fly, for example, or implement PUT.

Prior to Apache 1.3.10, method can only be one of GET, POST, PUT, or DELETE. As of 1.3.10, any arbitrary method name may be used. Method names are case sensitive, so Script PUT and Script put have two entirely different effects. (The uses of the HTTP methods are described in greater detail in Chapter 13.)

Note that the Script command defines default actions only. If a CGI script is called, or some other resource that is capable of handling the requested method internally, it will do so. Also note that Script with a method of GET will only be called if there are query arguments present (e.g., foo.html?hi). Otherwise, the request will proceed normally.


# For <ISINDEX>-style searching
Script GET /cgi-bin/search
# A CGI PUT handler
Script PUT /~bob/put.cgi

ScriptAlias url_path directory_or_filename 
Server config, virtual host

ScriptAlias allows scripts to be stored safely out of the way of prying fingers and, moreover, automatically marks the directory where they are stored as containing CGI scripts. For instance, see ...site.cgi/conf/httpd0.conf:

ScriptAlias /cgi-bin/ /usr/www/apache3/cgi-bin/

ScriptAliasMatch regex directory_or_filename 
Server config, virtual host

The supplied regular expression is matched against the URL; if it matches, the server will substitute any parenthesized matches into the given string and use them as a filename. For example, to activate the standard /cgi-bin, one might use:

ScriptAliasMatch ^/cgi-bin/(.*) /usr/local/apache/cgi-bin/$1

.* is a regular expression like those in Perl that match any character (.) any number of times (*). Here, this will be the name of the file we want to execute. Putting it in parentheses (.*) stores the characters in the variable $1, which is then invoked:


You can start the matching further along. If all your script filenames start with the letters "BT," you could write:

ScriptAliasMatch ^/cgi-bin/BT(.*) /usr/local/apache/cgi-bin/BT$1

If the visitor got here by following a link on the web page:

...<a href="/cgi-bin/BTmyscript/customer56/ice_cream">...

ScriptAliasMatch will run BTmyscript. If it accesses the environment variable PATH_INFO (described in Chapter 14), it will find /customer56/ice_cream.

You can have as many of these useful directives as you like in your Config file to cover different situations. For more information on regular expressions, see Mastering Regular Expressions by Jeffrey Friedl (O'Reilly, 2002) or Programming Perl by Larry Wall, Jon Orwant, and Tom Christiansen (O'Reilly, 2001).


ScriptInterpreterSource registry|script
Default: ScriptInterpreterSource script 
directory, .htaccess


This directive is used to control how Apache 1.3.5 and later finds the interpreter used to run CGI scripts. The default technique is to use the interpreter pointed to by the #! line in the script. Setting the ScriptInterpreterSource registry will cause the Windows registry to be searched using the script file extension (e.g., .pl) as a search key.


Alias url_path directory_or_filename
Server config, virtual host

Alias is used to map a resource's URL to its physical location in the filesystem, regardless of where it is relative to the document root. For instance, see .../site.alias/conf/httpd.conf:

Alias /somewhere_else/ /usr/www/APACHE3/somewhere_else/

There is a directory /usr/www/APACHE3/somewhere_else/, which contains a file lost.txt. If we navigate to www.butterthlies.com/somewhere_else, we see:

Index of /somewhere_else
    Parent Directory 

AliasMatch regex directory_or_filename
Server config, virtual host

Again, like ScriptAliasMatch, this directive takes a regular expression as the first argument. Otherwise, it is the same as Alias.


UserDir directory
Default: UserDir public_html
Server config, virtual host

The basic idea here is that the client is asking for data from a user's home directory. He asks for http://www.butterthlies.com/~peter, which means "Peter's home directory on the computer whose DNS name is www.butterthlies.com." The UserDir directive sets the real directory in a user's home directory to use when a request for a document is received from a user. directory is one of the following:

  • The name of a directory or a pattern such as those shown in the examples that follow.

  • The keyword disabled. This turns off all username-to-directory translations except those explicitly named with the enabled keyword.

  • The keyword disabled followed by a space-delimited list of usernames. Usernames that appear in such a list will never have directory translation performed, even if they appear in an enabled clause.

  • The keyword enabled followed by a space-delimited list of usernames. These usernames will have directory translation performed even if a global disable is in effect, but not if they also appear in a disabled clause.

If neither the enabled nor the disabled keyword appears in the UserDir directive, the argument is treated as a filename pattern and is used to turn the name into a directory specification. A request for http://www.foo.com/~bob/one/two.html will be translated as follows:

UserDir public_html     -> ~bob/public_html/one/two.html
UserDir /usr/web        -> /usr/web/bob/one/two.html
UserDir /home/*/www/APACHE3     -> /home/bob/www/APACHE3/one/two.html

The following directives will send the redirects shown to their right to the client:

UserDir http://www.foo.com/users -> http://www.foo.com/users/bob/one/two.html
UserDir http://www.foo.com/*/usr -> http://www.foo.com/bob/usr/one/two.html
UserDir http://www.foo.com/~*/   -> http://www.foo.com/~bob/one/two.html

Be careful when using this directive; for instance, UserDir ./ would map /~root to /, which is probably undesirable. If you are running Apache 1.3 or above, it is strongly recommended that your configuration include a UserDir disabled root declaration.


Under Win32, Apache does not understand home directories, so translations that end up in home directories on the righthand side (see the first example) will not work.


Redirect [status] url-path url
Server config, virtual host, directory, .htaccess

The Redirect directive maps an old URL into a new one. The new URL is returned to the client, which attempts to fetch the information again from the new address. url-path is a (%-decoded) path; any requests for documents beginning with this path will be returned a redirect error to a new (%-encoded) URL beginning with url.


Redirect /service http://foo2.bar.com/service

If the client requests http://myserver/service/foo.txt, it will be told to access http://foo2.bar.com/service/foo.txt instead.

Redirect directives take precedence over Alias and ScriptAlias directives, irrespective of their ordering in the configuration file. Also, url-path must be an absolute path, not a relative path, even when used with .htaccess files or inside of <Directory> sections.

If no status argument is given, the redirect will be "temporary" (HTTP status 302). This indicates to the client that the resource has moved temporarily. The status argument can be used to return other HTTP status codes:


Returns a permanent redirect status (301) indicating that the resource has moved permanently.


Returns a temporary redirect status (302). This is the default.


Returns a "See Other" status (303) indicating that the resource has been replaced.


Returns a "Gone" status (410) indicating that the resource has been permanently removed. When this status is used, the url argument should be omitted.

Other status codes can be returned by giving the numeric status code as the value of status. If the status is between 300 and 399, the url argument must be present, otherwise it must be omitted. Note that the status must be known to the Apache code (see the function send_error_response in http_protocol.c).


RedirectMatch regex url
Server config, virtual host, directory, .htaccess

Again, RedirectMatch works like Redirect, except that it takes a regular expression (discussed earlier under ScriptAliasMatch) as its first argument.

In the Butterthlies business, sad to relate, the salespeople have been abusing their powers and perquisites, and it has been decided to teach them a lesson by hiding their beloved secrets file and sending them to the ordinary customers' site when they try to access it. How humiliating! Easily done, though.

The Config file is httpd3.conf :

<VirtualHost sales.butterthlies.com>
ServerAdmin sales_mgr@butterthlies.com
Redirect /secrets http://www.butterthlies.com
DocumentRoot /usr/www/APACHE3/site.alias/htdocs/salesmen

The exact placing of the Redirect doesn't matter, as long as it is somewhere in the <VirtualHost> section. If you now access http://sales.butterthlies.com/secrets, you are shunted straight to the customers' index at http://www.butterthlies.com /.

It is somewhat puzzling that if the Redirect line fails to work because you have misspelled the URL, there may be nothing in the error_log because the browser is vainly trying to find it out on the Web.

An important difference between Alias and Redirect is that the browser becomes aware of the new location in a Redirect, but not in an Alias, and this new location will be used as the basis for relative hot links found in the retrieved HTML.


RedirectTemp url-path url
Server config, virtual host, directory, .htaccess

This directive makes the client know that the Redirect is only temporary (status 302). This is exactly equivalent to Redirect temp.


RedirectPermanent url-path url
Server config, virtual host, directory, .htaccess

This directive makes the client know that the Redirect is permanent (status 301). This is exactly equivalent to Redirect permanent.

8.2 Rewrite

The preceding section described the Alias module and its allies. Everything these directives can do, and more, can be done instead by mod_rewrite.c, an extremely compendious module that is almost a complete software product in its own right. But for simple tasks Alias and friends are much easier to use.

The documentation is thorough, and the reader is referred to http://www.engelschall.com/pw/apache/rewriteguide/ for any serious work. You should also look at http://www.apache.org/docs/mod/mod_rewrite.html. This section is intended for orientation only.

Rewrite takes a rewriting pattern and applies it to the URL. If it matches, a rewriting substitution is applied to the URL. The patterns are regular expressions familiar to us all in their simplest form Ч for example, mod.*\.c, which matches any module filename. The complete science of regular expressions is somewhat extensive, and the reader is referred to ... /src/regex/regex.7, a manpage that can be read with nroff -man regex.7 (on FreeBSD, at least). Regular expressions are also described in the POSIX specification and in Jeffrey Friedl's Mastering Regular Expressions (O'Reilly, 2002).

It might well be worth using Perl to practice with regular expressions before using them in earnest. To make complicated expressions work, it is almost essential to build them up from simple ones, testing each change as you go. Even the most expert find that convoluted regular expressions often do not work the first time.

The essence of regular expressions is that a number of special characters can be used to match parts of incoming URLs. The substitutions available in mod_rewrite can include mapping functions that take bits of the incoming URL and look them up in databases or even apply programs to them. The rules can be applied repetitively and recursively to the evolving URL. It is possible (as the documentation says) to create "rewriting loops, rewriting breaks, chained rules, pseudo if-then-else constructs, forced redirects, forced MIME-types, forced proxy module throughout." The functionality is so extensive that it is probably impossible to master it in the abstract. When and if you have a problem of this sort, it looks as if mod_rewrite can solve it, given enough intellectual horsepower on your part!

The module can be used in four situations:

  • By the administrator inside the server Config file to apply in all contexts. The rules are applied to all URLs of the main server and all URLs of the virtual servers.

  • By the administrator inside <VirtualHost> blocks. The rules are applied only to the URLs of the virtual server.

  • By the administrator inside <Directory> blocks. The rules are applied only to the specified directory.

  • By users in their .htaccess files. The rules are applied only to the specified directory.

The directives look simple enough.


RewriteEngine on_or_off
Server config, virtual host, directory

Enables or disables the rewriting engine. If off, no rewriting is done at all. Use this directive to switch off functionality rather than commenting out Rewrite-Rule lines.


RewriteLog filename
Server config, virtual host

Sends logging to the specified filename. If the name does not begin with a slash, it is taken to be relative to the server root. This directive should appear only once in a Config file.


RewriteLogLevel number
Default number: 0
Server config, virtual host

Controls the verbosity of the logging: 0 means no logging, and 9 means that almost every action is logged. Note that any number above 2 slows Apache down.


RewriteMap mapname {txt,dbm,prg,rnd,int}: filename
Server config, virtual host

Defines an external mapname file that inserts substitution strings through key lookup.Keys may be stored in a variety of formats, described as follows. The module passes mapname a query in the form:

$(mapname : Lookupkey | DefaultValue) 

If the Lookupkey value is not found, DefaultValue is returned.

The type of mapname must be specified by the next argument:


Indicates plain-text format Ч that is, an ASCII file with blank lines, comments that begin with #, or useful lines, in the format:


Indicates DBM hashfile format Ч that is, a binary NDBM (the "new" dbm interface, now about 15 years old, also used for dbm auth) file containing the same material as the plain-text format file. You create it with any ndbm tool or by using the Perl script dbmmanage from the support directory of the Apache distribution.


Indicates program format Ч that is, an executable (a compiled program or a CGI script) that is started by Apache. At each lookup, it is passed the key as a string terminated by newline on stdin and returns the substitution value, or the word NULL if lookup fails, in the same way on stdout. The manual gives two warnings:

  • Keep the program or script simple because if it hangs, it hangs the Apache server.

  • Don't use buffered I/O on stdout because it causes a deadlock. In C, use:


    In Perl, use:

    select(STDOUT); $|=1;]


Indicates randomized plain text, which is similar to the standard plain-text variant but has a special postprocessing feature: after looking up a value, it is parsed according to contained "|" characters that have the meaning of "or". In other words, they indicate a set of alternatives from which the actual returned value is chosen randomly. Although this sounds crazy and useless, it was actually designed for load balancing in a reverse-proxy situation, in which the looked-up values are server names Ч each request to a reverse proxy is routed to a randomly selected server behind it. See also Section 12.6 in Chapter 12.


Indicates an internal Apache function. Two functions exist: toupper( ) and tolower( ), which convert the looked-up key to all upper- or all lowercase.


RewriteBase BaseURL
directory, .htaccess

The effects of this command can be fairly easily achieved by using the rewrite rules, but it may sometimes be simpler to encapsulate the process. It explicitly sets the base URL for per-directory rewrites. If RewriteRule is used in an .htaccess file, it is passed a URL that has had the local directory stripped off so that the rules act only on the remainder. When the substitution is finished, RewriteBase supplies the necessary prefix. To quote the manual's example in .htaccess:

Alias /xyz /abc/def"
RewriteBase   /xyz
RewriteRule   ^oldstuff\.html$  newstuff.html

In this example, a request to /xyz/oldstuff.html gets rewritten to the physical file /abc/def/newstuff.html. Internally, the following happens:



Internal processing
/xyz/oldstuff.html     -> /abc/def/oldstuff.html  (per-server Alias)
/abc/def/oldstuff.html -> /abc/def/newstuff.html  (per-dir    RewriteRule)
/abc/def/newstuff.html -> /xyz/newstuff.html      (per-dir    RewriteBase)
/xyz/newstuff.html     -> /abc/def/newstuff.html  (per-server Alias)



RewriteCond TestString CondPattern
Server config, virtual host, directory

One or more RewriteCond directives can precede a RewriteRule directive to define conditions under which it is to be applied. CondPattern is a regular expression matched against the value retrieved for TestString, which contains server variables of the form %{NAME_OF_VARIABLE}, where NAME_OF_VARIABLE can be one of the following list:








































These variables all correspond to the similarly named HTTP MIME headers, C variables of the Apache server, or the current time. If the regular expression does not match, the RewriteRule following it does not apply.


RewriteLock Filename
Server config

This directive sets the filename for a synchronization lockfile, which mod_rewrite needs to communicate with RewriteMap programs. Set this lockfile to a local path (not on a NFS-mounted device) when you want to use a rewriting map program. It is not required for other types of rewriting maps.


RewriteOptions Option
Default: None
Server config, virtual host, directory, .htaccess

The RewriteOptions directive sets some special options for the current per-server or per-directory configuration. Currently, there is only one Option:


This forces the current configuration to inherit the configuration of the parent. In per-virtual-server context this means that the maps, conditions, and rules of the main server are inherited. In per-directory context this means that conditions and rules of the parent directory's .htaccess configuration are inherited.


RewriteRule Pattern Substitution [flags]
Server config, virtual host, directory

This directive can be used as many times as necessary. Each occurrence applies the rule to the output of the preceding one, so the order matters. Pattern is matched to the incoming URL; if it succeeds, the Substitution is made. An optional argument, flags, can be given. The flags, which follow, can be abbreviated to one or two letters:


Force redirect.


Force proxy.


Last rule Ч go to top of rule with current URL.


Apply following chained rule if this rule matches.

type|T= mime-type

Force target file to be mime-type.


Skip rule if it is an internal subrequest.


Set an environment variable.


Append a query string.


Pass through to next handler.

skip|S= num

Skip the next num rules.


Next round Ч start at the top of the rules again.


Returns HTTP response 410 Ч "URL Gone."


Returns HTTP response 403 Ч "URL Forbidden."


Makes the comparison case insensitive.

For example, say we want to rewrite URLs of the form:




We take the rewrite map file and save it under /anywhere/map.real-to-user. Then we only have to add the following lines to the Apache server Config file:

RewriteLog   /anywhere/rewrite.log 
RewriteMap   real-to-user  txt:/anywhere/map.real-to-host 
RewriteRule  ^/([^/]+)/~([^/]+)/(.*)$   /u/${real-to-user:$2|nobody}/$3.$1

8.2.1 A Rewrite Example

The Butterthlies salespeople seem to be taking their jobs more seriously. Our range has increased so much that the old catalog based around a single HTML document is no longer workable because there are too many cards. We have built a database of cards and a utility called cardinfo that accesses it using the arguments:

cardinfo cardid query

where cardid is the number of the card and query is one of the following words: "price," "artist," or "size." The problem is that the salespeople are too busy to remember the syntax, so we want to let them log on to the card database as if it were a web site. For instance, going to http://sales.butterthlies.com/info/2949/price would return the price of card number 2949. The Config file is in ... /site.rewrite :

User webuser
Group webgroup
# Apache requires this server name, although in this case it will 
# never be used.
# This is used as the default for any server that does not match a
# VirtualHost section.
ServerName www.butterthlies.com


<VirtualHost www.butterthlies.com>
ServerAdmin sales@butterthlies.com
DocumentRoot /usr/www/APACHE3/site.rewrite/htdocs/customers
ServerName www.butterthlies.com
ErrorLog /usr/www/APACHE3/site.rewrite/logs/customers/error_log
TransferLog /usr/www/APACHE3/site.rewrite/logs/customers/access_log

<VirtualHost sales.butterthlies.com>
ServerAdmin sales_mgr@butterthlies.com
DocumentRoot /usr/www/APACHE3/site.rewrite/htdocs/salesmen
Options ExecCGI indexes
ServerName sales.butterthlies.com
ErrorLog /usr/www/APACHE3/site.rewrite/logs/salesmen/error_log
TransferLog /usr/www/APACHE3/site.rewrite/logs/salesmen/access_log
RewriteEngine on
RewriteLog logs/rewrite
RewriteLogLevel 9
RewriteRule ^/info/([^/]+)/([^/]+)$   /cgi-bin/cardinfo?$2+$1 [PT]
ScriptAlias /cgi-bin /usr/www/APACHE3/cgi-bin

In real life cardinfo would be an elaborate program. However, here we just have to show that it could work, so it is extremely simple:

echo "content-type: text/html"
echo sales.butterthlies.com
echo "You made the query $1 on the card $2"

To make sure everything is in order before we do it for real, we turn RewriteEngine off and access http://sales.butterthlies.com/cgi-bin/cardinfo. We get back the following message:

The requested URL /info/2949/price was not found on this server.

This is not surprising. We now stop Apache, turn RewriteEngine on and restart with ./go. Look at the crucial line in the Config file:

RewriteRule ^/info/([^/]+)/([^/]+)$ /cgi-bin/cardinfo?$2+$1 [PT]

Translated into English, this means the following: at the start of the string, match /info/, followed by one or more characters that aren't /, and put those characters into the variable $1 (the parentheses do this; $1 because they are the first set). Then match a /, then one or more characters aren't /, and put those characters into $2. Then match the end of the string, and pass the result through [PT] to the next rule, which is ScriptAlias. We end up as if we had accessed http://sales.butterthlies.com/cgi-bin/cardinfo?<card ID>+<query>.

If the CGI script is on a different web server for some reason, we could write:

RewriteRule ^/info/([^/]+)/([^/]+)$ http://somewhere.else.com/cgi-bin/
    cardinfo?$2+$1 [PT]

Note that this pattern won't match /info/123/price/fred because it has too many slashes in it.

If we run all this with ./go and access http://sales.butterthlies.com/info/2949/price from the client, we see the following message:

You made the query price on card 2949

8.3 Speling

A useful module, mod_speling,[2] has been added to the distribution. It corrects miscapitalizations Ч and many omitted, transposed, or mistyped characters in URLs corresponding to files or directories Ч by comparing the input with the filesystem. Note that it does not correct misspelled usernames.

8.3.1 CheckSpelling

The CheckSpelling directive turns spell checking on and off.

CheckSpelling [on|off]

[1]  Too much of this kind of thing can make your site difficult to maintain.

[2]  Yes, we did spel that correctly. Another of those programmer's jokes, we're afraid.