Php – Apache’s mod_rewrite and PHP’s REQUEST_URI variable

apache-2.4Apache2mod-rewritePHPrewrite

I have an issue with Apache passing to the PHP $_SERVER['REQUEST_URI'] variable the URL after it has been rewritten rather than the original one requested.

I am doing this rewriting because I had a WordPress website and wanted to move it to a subdirectory rather than having it in a root path, but still wanted to keep its URL to be the root URL.

This does not happen consistently. If I request www.xyz.com/wp-admin it populates the PHP REQUEST_URI variable with www.xyz.com/wordpress/wp-admin (which is the URL after it has been rewritten), but if I request www.xyz.com/wp-admin/ (with a trailing slash) it actually populates the PHP REQUEST_URI variable with www.xyz.com/wp-admin/ (the original URL, before the rewriting). What I want is for the REQUEST_URI to be populated with the URL before it has been rewritten.

My .htaccess file is below:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www.)?xyz.com$
RewriteCond %{REQUEST_URI} !^/wordpress/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /wordpress/$1
RewriteCond %{HTTP_HOST} ^(www.)?xyz.com$
RewriteRule ^(/)?$ wordpress/index.php [L]
</IfModule>`

PHP version is 5.3. Apache version is 2.4 (Win32).


UPDATE: I looked into it more and when I type in the URL www.xyz.com/wp-adminthen there is a 301 redirect first to www.xyz.com/wordpress/wp-admin/ but this does not happen for www.xyz.com/wp-admin/ (with a trailing slash). For the one with the trailing slash there is only the rewrite, as expected.
So the question now is why the 301 redirect happens in the first place for the URL without the trailing slash.

To clarify, there is no actual folder /wp-admin/, but there is a folder /wordpress/wp-admin/.

Best Answer

The "problem" is related to mod_dir (although mod_dir isn't strictly "the" problem - it's doing the correct thing and "fixing" the URL).

Since wp-admin is a physical directory, mod_dir "fixes" the URL by appending a trailing slash (required in order to correctly serve the directory index, eg. index.php). It does this with a 301 redirect. The problem is that this is occurring after your internal rewrite which results in the rewrite being turned into a redirect, thus exposing your /wordpress subdirectory.

So, the correct URL is strictly /wp-admin/ (with a trailing slash). If you didn't have your /wordpress subdirectory and everything was in the document root then mod_dir would simply redirect /wp-admin to /wp-admin/, which would then result in /wp-admin/index.php being served (as an internal subrequest).

What you need to do is append the trailing slash manually. If the requested URL does not end in a slash but would otherwise map to a physical directory within the /wordpress subdirectory then append the trailing slash (via an external redirect). This redirect should occur before your internal rewrites.

So, try the following before your existing directives:

# Append a slash if it is omitted and would map to a directory
RewriteCond %{REQUEST_URI} !^/wordpress/
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{DOCUMENT_ROOT}/wordpress/$1 -d
RewriteRule (.*) /$1/ [R=302,L]

What this does is, for any request that does not already start /wordpress/ and does not end in a slash but does map to a physical directory within the /wordpress subdirectory then redirect and append a slash. So, a request for /wp-admin gets redirected /wp-admin/, providing /wordpress/wp-admin exists as a directory. (Your internal rewrites then route the URL as before.)

You will need to make sure your browser cache is cleared (or test with the browsers object inspector open and disable the cache) since the earlier 301 redirects (by mod_dir) will have been cached.

When you are sure it's working OK then change the 302 (temporary) redirect to a 301 (permanent) - if that is the intention. 302s are cached by the browser, so makes testing that bit easier.


Aside:

Apache's REQUEST_URI vs PHP's $_SERVER['REQUEST_URI']

...Apache passing to the PHP $_SERVER['REQUEST_URI'] variable the URL after it has been rewritten rather than the original one requested.

Apache doesn't actually pass the REQUEST_URI variable from mod_rewrite to PHP directly. Whilst these two variables have the same name, as far as I can tell, PHP itself populates this variable from the request.

The Apache REQUEST_URI server variable and PHP's $_SERVER['REQUEST_URI'] superglobal actually contain different information:

  • PHP's $_SERVER['REQUEST_URI'] contains the query string, Apache's REQUEST_URI server variable does not; it contains the URL-path only.
  • PHP's $_SERVER['REQUEST_URI'] contains the original URL-path from the request, not the rewritten URL. Whereas Apache's REQUEST_URI server variable is updated (throughout the request) to contain the rewritten URL.
  • The URL-path component of $_SERVER['REQUEST_URI'] is not URL decoded (ie. %-decoded). Whereas the Apache REQUEST_URI server variable is %-decoded. (NB: The query string portion of the URL always remains %-encoded in both Apache and PHP. If you need to examine the %-encoded URL-path in Apache then check THE_REQUEST Apache server variable.)
Related Topic