3

I was looking at two different variants of redirects:

RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]

RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

And noticed that in the first there is a slash between domain and URI, but in the second variant it's not explicitly inserted. So I tried looking for documentation, but couldn't find any on why/when/how they are automatically inserted.

I did some tests with a test tool and got the following results.

^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI}  ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}/%{REQUEST_URI} ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}/$1             ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}$1              ->  https://sub.domain.comtest/folder/file.txt

Where the variants with variables seem to correct for missing/excess slashes, while the variants with regex don't.

But what's weird is that if I use only the domain, minus the URI,

^(.*)$ https://%{HTTP_HOST}/               ->  https://sub.domain.com/
^(.*)$ https://%{HTTP_HOST}                ->  https://sub.domain.com/

The slash IS added, so it looks like the http_host variable includes a slash regardless of whether or not a URI is included. I.E. the slash is part of the domain name?

And potentially even weirder; that not including a domain at all and using only regex, DOES automatically add the slash and ends up with a workable URL.

^(.*)$ /$1                                ->  http://sub.domain.com/test/folder/file.txt
^(.*)$ $1                                 ->  http://sub.domain.com/test/folder/file.txt

In short, using only variables automatically adds slashes, using only one variable adds slashes, using only regex adds slashes, but using regex after variables does not?

How exactly are these slashes added and what's the logic behind them?

1
  • 3
    I wouldn't expect a test tool to 100% mimic the behavior of mod_rewrite here. These test tools can be useful, but have some known differences from the real thing. I'd recommend testing on an actual server. Commented Jan 6, 2022 at 10:48

1 Answer 1

3

In addition to Apache/mod_rewrite, some of this behaviour can be the result of the browser and how the OS maps the request to the filesystem. But as noted in comments, a "testing tool" can also result in (incorrect/unclear) behaviour that is unique to that tool.

To clarify, this is .htaccess in the document root (a directory context). If these rules are in the main server config then behaviour could again be different due to what is actually matched in the different contexts.

RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]

RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

And noticed that in the first there is a slash between domain and URI, but in the second variant it's not explicitly inserted.

The $1 backreference in this example does not contain the slash prefix, so it needs to be explicitly included after the hostname in the substitution string (first example). (It doesn't contain a slash prefix, because the URL-path that is matched by the RewriteRule pattern in .htaccess does not contain a slash prefix.)

Whereas the REQUEST_URI server variable always contains the full root-relative URL-path, starting with a slash. At the very minimum, this var will contain a single slash (the document root). So if you use this variable in the substitution string then you would need to omit the slash (second example).

Nothing is "automatically inserted" here.

Reference: See the Apache docs for the RewriteRule directive under the heading "Per-directory Rewrites": https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#rewriterule

I did some tests with a test tool and got the following results.

^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI}  ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}/%{REQUEST_URI} ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}$1              ->  https://sub.domain.com/test/folder/file.txt
^(.*)$ https://%{HTTP_HOST}/$1             ->  https://sub.domain.comtest/folder/file.txt

Where the variants with variables seem to correct for missing/excess slashes, while the variants with regex don't.

There might be some inconsistencies in the "test tool" here and the results of your 3rd and 4th examples seem to be the wrong way round? However...

  • The first example is as expected.

  • The second example will result in a double slash in the resulting URL after the hostname (UPDATE: the "testing tool" is wrong in this respect*1). As mentioned above, the REQUEST_URI server variable already contains a slash prefix. HOWEVER, when the requested URL is mapped to the file system, multiple contiguous slashes are reduced to single slashes in the file-path, so a request for https://example.com//test/folder/file.txt will still serve the static file /test/folder/file.txt. Examining the URL-path matched by the RewriteRule pattern (which has already had multiple slashes reduced) sees only a single slash. However, there are still two slashes in the URL-path (and this could break some URL routers).

    (*1 For some reason that testing tool always reduces multiple slashes at the start of the URL-path only. Other instances of multiple slashes in the URL-path remain unchanged. This is incorrect behaviour.)

  • In the 3rd and 4th examples (which appear to be reversed in the results - as mentioned) you need to include the slash prefix because there is no slash prefix on the $1 backreference in this instance.

But what's weird is that if I use only the domain, minus the URI,

^(.*)$ https://%{HTTP_HOST}/               ->  https://sub.domain.com/
^(.*)$ https://%{HTTP_HOST}                ->  https://sub.domain.com/

The slash IS added, so it looks like the http_host variable includes a slash regardless of whether or not a URI is included. I.E. the slash is part of the domain name?

No, there is no trailing slash on the HTTP_HOST server variable (the hostname used in the request).

In the case of an external redirect, if you examine the Location header in the redirect response, there is no trailing slash after the hostname in the second example above.

However, the trailing slash after the hostname is automatically added by the browser (as part of the second/redirected request) in order to "correct" the request. There is always a slash after the hostname in a valid HTTP request (whether you see this in the browser's address bar or not).

Note that this is the slash immediately after the hostname, at the start of the URL-path. Not the slash that is sometimes appended to the end of the URL-path (the "trailing slash"). The two must not be confused.

See this related Webmasters question that asks this specific question:

And potentially even weirder; that not including a domain at all and using only regex, DOES automatically add the slash and ends up with a workable URL.

^(.*)$ /$1                 ->  http://sub.domain.com/test/folder/file.txt
^(.*)$ $1                  ->  http://sub.domain.com/test/folder/file.txt

This relates to external redirects, not internal rewrites. (As with the previous examples.)

In the first example (that explicitly starts with a slash - a root-relative URL) then mod_rewrite will construct an absolute URL from the request, prefixing the scheme + hostname.

The second example will only result in a valid redirect (as stated) if RewriteBase / is defined elsewhere in the .htaccess file. This controls the URL-path that is added back to relative substitutions at the end of the rewriting process. If RewriteBase was not defined then this would result in a malformed redirect as it would add back the directory-prefix and a result in something like /var/www/user/public_html/test/folder/file.txt. However, omitting the slash prefix is OK (and often preferred) when performing internal rewrites (as opposed to external redirects).

In short, using only variables automatically adds slashes, using only one variable adds slashes, using only regex adds slashes, but using regex after variables does not?

What you are referring to as a "regex" here is really a backreference (of the form $n) to a captured subgroup in the preceding RewriteRule pattern (regex). The backreference itself is not part of the regex syntax.

There is no difference between using a server variable (ie. REQUEST_URI) and using a backreference (ie. $1) in the substitution string, except with what they contain. It's just that in the examples above, REQUEST_URI contains a slash prefix and the backreference does not.

To take this one step further... the URL-path matched by the RewriteRule pattern is relative to the directory that contains the .htaccess file (the "directory-prefix"). The directory-prefix that is first removed always ends with a slash, so the URL-path that is matched never starts with a slash.

This becomes very significant when the .htaccess file is not in the document root. For example, if the following .htaccess file was located at /test/folder/.htaccess:

RewriteRule ^(.*)$ https://example.com/$1 [R,L]

RewriteRule ^ https://example.com%{REQUEST_URI} [R,L]

Given a request for /test/folder/file.txt then:

  • The first rule would result in a redirect to:
    https://example.com/file.txt

  • Whereas the second rule would result in a redirect to: https://example.com/test/folder/file.txt

Aside: When used in a server (or virtualhost) context, as opposed to .htaccess (a directory context) then the two rules above would behave exactly the same. Because in a server context, the URL-path matched by the RewriteRule pattern contains the full root-relative URL-path - the same as REQUEST_URI.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.