1

I have an application that routes all requests through index.php.

Here's my setup:

  1. I access the application at http://www.example.com/sample/
  2. On the filesystem, the application sits in /home/chris/www/sample/
  3. The web-accessible directory of the application lives at /home/chris/www/sample/app/web.
  4. The DocumentRoot is set to /home/chris/www

/home/chris/www/sample/.htaccess is configured as follows:

RewriteEngine on

RewriteCond   %{REQUEST_URI}    ^/sample/(.+)$
RewriteCond   %{DOCUMENT_ROOT}/sample/app/web/%1  -f
RewriteRule   ^/sample/(.*)$    %{DOCUMENT_ROOT}/sample/app/web/%1 [L]

RewriteRule   ^(.+)$            index.php?ws_page=$1 [L,NC,QSA]

I've tried multiple configurations, but haven't figured out why I keep getting 404's on calls to "real" files.

Sample 404:

`http://www.example.com/sample/_css/960/reset.css`

(which I want to have rewritten to /home/chris/www/sample/app/web/_css/960/reset.css)

EDIT

I have already tried

RewriteCond %{REQUEST_URI} !-f
RewriteCond %{REQUEST_URI} !-d
RewriteCond %{REQUEST_URI} !-l

and they did not work, because the %{REQUEST_URI} prefix does not match the filesystem prefix of these files.

EDIT 2

To clarify, I want requests of the form

`http://www.example.com/sample/foo/bar`

to be rewritten to the filesystem object /home/chris/www/sample/app/web/foo/bar, but only if that filesystem object exists.

Chris Tonkinson
  • 13,823
  • 14
  • 58
  • 90

4 Answers4

1

Have your code like this:

Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
RewriteBase /sample

RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s/+sample/(.+)\s [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond /home/chris/www/sample/app/web/%1 -f [NC]
RewriteRule ^ /sample/app/web/%1 [L]

# If the request is not for a valid file
RewriteCond %{REQUEST_FILENAME} !-f
# If the request is not for a valid symlink
RewriteCond %{REQUEST_FILENAME} !-l
# If the request is not for a valid directory
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ index.php?page=$1 [L,QSA]
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Are you using relative paths in css, js, images etc by any chance? Also can you copy/paste your 404 error log files here. – anubhava Mar 18 '12 at 04:48
  • And actually, mixed. I've been trying one CSS file and one JS file. One was relative, one was absolute. Both failed. – Chris Tonkinson Mar 18 '12 at 04:52
  • And, if it helps, this actually results in an infinite redirect loop (which may not be entirely mod_rewrite's doing, to be fair - I have some redirect logic in my application as well). – Chris Tonkinson Mar 18 '12 at 04:57
  • First thing you should do is to replace all relative paths with absolute ones. Secondly I didn't understand this 404 `http://www.example.com/sample/_css/960/reset.css` since you wrote `All of my assets (css, images, etc) are in under app/web/`. Finally I suggest you to enable RewriteLog in httpd.conf and see the generated output. – anubhava Mar 18 '12 at 05:00
  • The assets exist within `app/web` on the filesystem, relative to the `.htaccess` file. However, they are requested with the web root `/sample/`. – Chris Tonkinson Mar 18 '12 at 05:01
  • That might very well be the reason of 404. Your resource files exist in `/sample/app/web/_css/960/reset.css` however your code is actually trying to find them in `/sample/_css/960/reset.css`. In your browser can you enter this URL: `http://www.example.com/sample/app/web/_css/960/reset.css` and see if css is loaded or not. – anubhava Mar 18 '12 at 05:05
  • Yes, I could very well re-path assets to include the app/web prefix, however if I can get mod_rewrite to do the replacement for me, this becomes unnecessary. My **goal** is to have `GET /sample/_css/screen.css` retrieve the filesystem resource `/home/chris/www/sample/app/web/_css/screen.css`. – Chris Tonkinson Mar 18 '12 at 05:08
  • The whole point is for mod_rewrite to figure out if the file `_css/screen.css` exists under `app/web/`. If so, rewrite and serve that asset. If not, pass the request off to `index.php`. – Chris Tonkinson Mar 18 '12 at 05:11
  • Yes sure this translation of paths can be done by mod_rewrite however I didn't see this part of code in your .htaccess code. If you want I can suggest a RewriteRule to you for translating this path. – anubhava Mar 18 '12 at 05:13
  • Yes, that's what I'm after. I want to get mod_rewrite to do this for me. – Chris Tonkinson Mar 18 '12 at 05:15
  • Alright just give me few minutes and I will edit my answer for that translation. – anubhava Mar 18 '12 at 05:17
  • Hmm add `R` in the first RewriteRule I suggested and see what final URL you get after redirection. I will be back in an hour to check your comment. – anubhava Mar 18 '12 at 06:09
  • `http://www.example.com/home/chris/www/sample/app/web/_css/960/reset.css`, and it seems like no configuration allows me to avoid these errors. – Chris Tonkinson Mar 18 '12 at 06:26
  • That's really strange, it should have been `http://www.example.com/sample/app/web/_css/960/reset.css` instead of `http://www.example.com/home/chris/www/sample/app/web/_css/960/reset.css`. I have create directory structure exactly like you and tested this code myself before posting. Is your .htaccess exactly same as the one in my answer or yo have something else as well? – anubhava Mar 18 '12 at 07:13
1

One of the biggest bear-pits in reading the mod_rewrite documentation in the difference in behaviour in a system (that this the main and vhost configs that apache reads on start-up and those directive processes in a per-directory context. See the Per-directory Rewrites subsection of the RewriteRule documentation for further details.

When using the rewrite engine in .htaccess files the per-directory prefix (which always is the same for a specific directory) is automatically removed for the RewriteRule pattern matching and automatically added after any relative (not starting with a slash or protocol name) substitution encounters the end of a rule set. See the RewriteBase directive for more information regarding what prefix will be added back to relative substitions.

and later

The removed prefix always ends with a slash, meaning the matching occurs against a string which never has a leading slash. Therefore, a Pattern with ^/ never matches in per-directory context. What you've done is to code around this.

Incidentally, this is why it is always safer to specify a RewriteBase, as the engine gets this wrong without this.

BTW, this second quote can be wrong because the prefix add-back occurs at the then of the rule set execution, and if you have a successful rule which is to a different relative branch (that is the target starts with a /) but without the [L] flag set, then the engine falls through to any subsequent rules with a leading / set. Most confusing, so my general advice is never rely on fall-through rules. Always force an immediate internal or external redirect on a successful substitution in a per-directory context as the engine has this and a couple of other bugs in this fall-through processing.

TerryE
  • 10,724
  • 5
  • 26
  • 48
  • Wow... thoroughly enlightening. Thanks! I'm not sure how I went so long not realizing that the prefix gets removed in per-directory context. – Chris Tonkinson Mar 19 '12 at 17:10
  • 1
    Chris, debugging htaccess files where you don't have root access to Apache is a real bugger, hence [this Q&A](http://stackoverflow.com/questions/9153262/tips-for-debugging-htaccess-rewrite-rules). I also have a VM which mirrors my hosting providers shared service for this reason, as I described [here](http://blog.ellisons.org.uk/article-61). – TerryE Mar 19 '12 at 17:17
  • Great reference - I hadn't found that post yet. – Chris Tonkinson Mar 19 '12 at 19:20
0
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

These two rules will skip files (-f) and directories (-d) which actually exist.

Marc B
  • 356,200
  • 43
  • 426
  • 500
0

Mad bonus points to whomever can explain to me why my the EOS anchor ($) was to blame for the RewriteRule not working.

I wound up with this:

RewriteEngine on

RewriteCond   %{REQUEST_URI}    ^/sample/(.+)
RewriteCond   %{DOCUMENT_ROOT}/sample/app/web/%1  -f
RewriteRule   ^(.*)$            app/web/$1 [L,NC,QSA]

RewriteCond   %{REQUEST_URI}    !(^/sample/app/web)
RewriteRule   ^(.+)$            index.php?ws_page=$1 [L,NC,QSA]

Many thanks to all who helped me diagnose this weirdness.

Chris Tonkinson
  • 13,823
  • 14
  • 58
  • 90
  • 1
    Chris, its a case of RTFM. "The removed prefix always ends with a slash, meaning the matching occurs against a string which *never* has a leading slash. Therefore, a Pattern with `^/` never matches in per-directory context." You could have just dropped the leading /. What you've done is to code around this. – TerryE Mar 18 '12 at 12:53