I am trying to write a rewrite rule to change path slugs to query parameters. It is for a web service, and should only rewrite this rule if the host starts with api. There are two slugs that I am trying to capture and rewrite. The first is optional and is a version (i.e. v1.2) and the second is the service domain (i.e. customers, transactions, etc.).
http://api.domain.com/v2.5/customers should rewrite to ?version=2.5&domain=customers
I also want to support a default version so that
http://api.domain.com/customers should rewrite to ?version=&domain=customers
Here is what my .htaccess file looks like:
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP_HOST} ^api\..*
RewriteRule ^v([\d\.]*)?\/?([^\/]*)$ ?version=$1&domain=$2
The first example above works fine, but I can't get the default version path to work. I have tried a ton of different things. I thought starting with ^.*v would help, but it didn't. Anybody know how to make it match when you don't know the starting characters?
Try:
RewriteCond %{HTTP_HOST} ^api\..*
RewriteCond %{QUERY_STRING} !version=
RewriteRule ^(v([\d\.]*))?\/?([^\/]*)$ /?version=$2&domain=$3 [L]
This makes the /v part optional:
/v2.5/foo -> /?version=2.5&domain=foo
/foo -> /?version=&domain=foo
/v/foo -> /?version=&domain=foo
Metadata
My server setup
A shared host with wildcard subdomains and optional preceding www.
I can't touch httpd.conf and have limited .htaccess directives, although RewriteRule and the likes apply.
I use per-directory .htaccess files.
My server layout
Most subfolders (read: some are for gfx and such) are standalone applications, f.ex: A URL shortener, a image upload site.
Usual PHP controller setup
To manipulate applications, f.ex. view a specific file that's been uploaded, I read the $_SERVER['QUERY_STRING'] in order to use URIs like http://s.domain.com/?image.jpg to retrieve it from where it's stored.
This setup may differ from application to application.
Problem
What I have
Examples
URL Shortener:
http://s.domain.com/?xy7r OR http://www.domain.com/s/?xy7r retrieves the hash from a database and redirects the user.
Image Uploader:
http://d.domain.com/?xy7r.png OR http://www.domain.com/d/?xy7r.png redirects to http://d.domain.com/u/xy7r.png
(Note: the www. is optional in all cases)
What I want
Adjust my existing applications to use Apache's mod_rewrite.
Examples
URL Shortener:
http://s.domain.com/xy7r OR http://www.domain.com/s/xy7r
Image Uploader:
http://d.domain.com/xy7r.png OR http://www.domain.com/d/xy7r.png
My approach
At first I was adding RewriteRules like a happy hacker and everything worked fine,
I then noticed that as they were designed for URIs like http://sub.domain.com they did not work for
URIs like http://www.domain.com/sub.
I decided to try and set up conditions so that the rules would work for both URI cases.
So I Google-FUd and read specifications, documentations and tutorials. I do not fully understand this directive but neither do I think I found any appropriate solutions nor similar problems on the net.
I then gave up and thought I'd instead redirect the second URI syntax (http://www.domain.com/sub/) to the preferred one (http://sub.domain.com) (Also http://www.sub.domain.com), to then apply my existing RewriteRule's
My .htaccess so far
(Only for the URL Shortener as I have not moved on until I get it working)
RewriteEngine On
RewriteCond %{HTTP_HOST} !^s\.domain\.com$ [NC] # Exclude correct URI
RewriteCond %{HTTP_HOST} !^$ # Exclude old HTTP requests
RewriteCond %{REQUEST_URI} ^/s($|/.*$) # Rewrite bad URI
RewriteRule ^.* http://s.domain.com/$1 [R=permanent] # Redirect to correct URI
RewriteCond %{REQUEST_URI} !^/fonts/ # Exclude system folder
RewriteCond %{REQUEST_URI} !^/index.php # Exclude system file
RewriteCond %{REQUEST_URI} !^/style.css # -||-
RewriteCond %{REQUEST_URI} !^/script.js # -||-
RewriteCond %{REQUEST_URI} !^/short.php # -||-
RewriteRule ^(.+)$ http://s.domain.com/?$1 # Rewrite to actual URI
What I get
URI's like http://s.domain.com/xy7r work, as do the same preceded by www., but
the http://www.domain.com/s/xy7r URI syntax just redirects to http://s.domain.com and ignores the xy7r part.
Question
Am I on the right track or is there a better/more correct way to do this?
I've been trying to understand the RewriteBase directive but I don't understand it at all, and trying values like RewriteBase /s/ or s or /s don't satisfy.
In any way I am at a fullstop, I do not know how to proceed.
Any help is appreciated!
Thank you!
</WOT>
For the first set of rules, replace
RewriteCond %{REQUEST_URI} ^/s($|/.*$) # Rewrite bad URI
RewriteRule ^.* http://s.domain.com/$1 [R=permanent] # Redirect to correct URI
with
RewriteRule ^s/?(.*) http://s.domain.com/$1 [L,R=302]
My software supports multiple domain names all pointed at the same directory on the server (a different database for each of course). So these domains...
www.example1.com
www.example2.com
www.example3.com
...all point to...
/public_html/
In the image directory...
/public_html/images/
I have directories that exactly match the host names for each website:
/public_html/images/www.example1.com/
/public_html/images/www.example2.com/
/public_html/images/www.example3.com/
I'm trying to get Apache to rewrite requests so that if you view the image directly and look at the address bar you only see the host name once.
So a request for...
http://www.example1.com/images/book.png
...is fetched by Apache at...
/public_html/images/www.example1.com/book.png
One of the things I've tried and have had success with in different circumstances is the following though it doesn't work in this situation:
RewriteRule ^[^/]*/images(.+) images/%{HTTP_HOST}/$1
Try adding the following to the .htaccess file in the root directory of your site (public_html)
RewriteEngine on
RewriteBase /
#prevent looping from internal redirects
RewriteCond %{ENV:REDIRECT_STATUS} !200
#only rewrite gif, jpg or png
RewriteRule ^(images)(/.+\.(gif|jpg|png))$ $1/%{HTTP_HOST}$2 [L,NC]
Your rule
RewriteRule ^[^/]*/images(.+) images/%{HTTP_HOST}/$1
did not work because you have a leading / before images. In .htaccess the leading / is removed, so the rule would never match.
Here's one of the things I've made for my high performance framework (see my bio).
I give you an advanced RewriteRule, I'm pretty sure you'll have enough material to finish:
Create static domains:
static.example1.com
static.example2.com
static.example3.com
Where all your images will be.
From now on, no more:
www.example1.com/images/www.example1.com/picture.jpg
www.example2.com/images/www.example2.com/picture.jpg
www.example3.com/images/www.example3.com/picture.jpg
but
static.example1.com/picture.jpg
static.example2.com/picture.jpg
static.example3.com/picture.jpg
Nice URLs uh?
Now create a vhost with all your static files:
<VirtualHost *>
ServerName static.example1.com
ServerAlias static.example2.com static.example3.com
</VirtualHost>
Set your document root to the base without the vhost name, so in your case:
DocumentRoot "/public_html/images"
And add this RewriteRule
RewriteCond %{HTTP_HOST} ^static\.([a-zA-Z0-9\-]+)\.com$
# Change the path, and add the request:
RewriteRule (.*) %{DOCUMENT_ROOT}/static.%1.com$1 [QSA,L]
So all in all:
<VirtualHost *>
ServerName static.example1.com
ServerAlias static.example2.com static.example3.com
RewriteCond %{HTTP_HOST} ^static\.([a-zA-Z0-9\-]+)\.com$
# Change the path, and add the request:
RewriteRule (.*) %{DOCUMENT_ROOT}/static.%1.com$1 [QSA,L]
</VirtualHost>
Ok that doesn't aswer exactly to your question so here's the short answer, but I don't like it because it doesn't help you to do a very (very) good job:
RewriteCond %{HTTP_HOST} ^www\.(example1|example2|example3)\.com$
# Change the path:
RewriteRule (.*)(\.(css|js|txt|htc|pdf|jpg|jpeg|gif|png|ico))$ %{DOCUMENT_ROOT}/www.%1.com$1$2 [QSA,L]
And if that's not enough:
Two hints:
If you're not in a hosted environment (= if it's your own server and you can modify the virtual hosts, not only the .htaccess files), try to use the RewriteLog directive: it helps you to track down such problems:
# Trace:
# (!) file gets big quickly, remove in prod environments:
RewriteLog "/web/logs/mywebsite.rewrite.log"
RewriteLogLevel 9
RewriteEngine On
My favorite tool to check for regexp:
http://www.quanetic.com/Regex (don't forget to choose ereg(POSIX) instead of preg(PCRE)!)
John,
I've just posted a separate Q on some of the challenges that you face. I would welcome your comments, but back to your challenge: one trick that you can use an environment variable to store your (preferably validated) host, for example:
RewriteCond %{HTTP_HOST} ^www\.(host1|host2|host3\.com
RewriteRule ^ - [E=HOST:%1]
You might also want to add [S] flags to implement if/then/else logic in your rules. And you can also use the HOST variable in following rule or condition strings (not regexp patterns) as %{ENV:HOST}.
You also need to take a clear look at a full phpinfo() report to understand whether you hosting service is using an mod_php or a mod_suPHP, ... interface and host it supports DNS multihoming. E.g my supplier sets up %{ENV:DOCUMENT_ROOT_REAL} which I need to use instead of %{DOCUMENT_ROOT} when examining file-space.
All of your URI "arrivals" at DOCROOT/ are of the form http://www.exampleX.com/images/book.png so if your .htaccess location is your DOCROOT then your base is /. So assuming the above ENV setting, these should work
RewriteBase /
RewriteCond %{HTTP_HOST} ^www\.(host1|host2|host3)\.com
RewriteRule ^ - [E=HOST:%1]
RewriteCond %{ENV:HOST}==%{REQUEST_URI} !^(.*?)==/image/\1
RewriteRule ^image/(.*) image/%{ENV:HOST}/$1 [L,NS]
The cond is a botch to stop the rewrite rule looping.
Generalised version
The above solution is an already generalised solution as you as for. Just replace the RewriteCond regexp with whatever pattern matches your own naming convention, and I agree that if it is (.*) then you may as well drop the first rule and replace %{ENV:HOST} by %{HTTP_HOST}. You need the RewriteCond guard to prevent the loop which results in a 500.
I've been reading multiple posts on here about htaccess folder rewriting but none seem to fit my question (properly).
My question is:
I have 2 sub folders on the server, website1 and website2.
When a user goes to www.foo.com I wish the visual url to remain the same but want the server URI to go to /website1/ where it will load the index.php for website1
I then want the same thing only when a user goes to www.bar.com again the url does not change but this time it links to /website2/ where it will load the index.php for the 2nd website.
Would really appreciate some help with this as I'm still learning about rewrites. Examples with explanations would be highly appreciated. Also any advice of best practice (if their is any) would also be appreciated.
KingCrunch is right -- the proper way to setup such environment is to use <VirtualHost> directive in Apache config file.
If, for whatever reason this needs to be dona via rewrite and .htaccess .. then you need mod_rewrite to be enabled and .htaccess files to be allowed to contain rewrite rule (AllowOverride directive).
Here are the rules:
Options +FollowSymLinks -MultiViews
RewriteEngine On
RewriteBase /
# rule #1
RewriteCond %{HTTP_HOST} =www.foo.com
RewriteCond %{REQUEST_URI} !^/website1/
RewriteRule (.*) /website1/$1 [L]
# rule #2
RewriteCond %{HTTP_HOST} =www.bar.com
RewriteCond %{REQUEST_URI} !^/website2/
RewriteRule (.*) /website2/$1 [L]
This code is to be placed in .htaccess file in root folder. If placed elsewhere (e.g. configuration or virtual host context) some tweaking may be required.
Fist rule is for www.foo.com and second for another domain name. These rules are pretty much the same. We tell Apache to check domain name (via {HTTP_HOST} request variable), and if it matches our domain rewrite (internal redirect) URL into one folder deeper. The second condition is to prevent a rewrite loop (to not to rewrite already rewritten URL). It is necessary as Apache, after executing rewrite, goes to the next rewrite iteration (that is how it works), and this condition is required to stop the loop.
Useful link: http://httpd.apache.org/docs/current/rewrite/
I believe that you need to use only RewriteCond and RewriteRule directives. Take a look 'Virtual User Hosts' at http://httpd.apache.org/docs/1.3/misc/rewriteguide.html.
The logical is the same. (I think.)
There seem to be a decent number of mod_rewrite threads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.
What other features / common issues have you run across using mod_rewrite?
Where to place mod_rewrite rules
mod_rewrite rules may be placed within the httpd.conf file, or within the .htaccess file. if you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccess file is called).
Logging mod_rewrite requests
Logging may be enabled from within the httpd.conf file (including <Virtual Host>):
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
Common use cases
To funnel all requests to a single point:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
Since Apache 2.2.16 you can also use FallbackResource.
Handling 301/302 redirects:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Note: external redirects are implicitly 302 redirects:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
Forcing SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
Common flags:
[R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
[R=301] or [redirect=301] - force a 301 permanent redirect
[L] or [last] - stop rewriting process (see note below in common pitfalls)
[NC] or [nocase] - specify that matching should be case insensitive
Using the long-form of flags is often more readable and will help others who come to read your code later.
You can separate multiple flags with a comma:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
Mixing mod_alias style redirects with mod_rewrite
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
Note: you can mix mod_alias with mod_rewrite, but it involves more work than just handling basic redirects as above.
Context affects syntax
Within .htaccess files, a leading slash is not used in the RewriteRule pattern:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
[L] is not last! (sometimes)
The [L] flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you're in the .htaccess context or the <Directory> section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand this, it often looks like your [L] flag had no effect.
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
Our rewrite log shows that the rules are run twice and the URL is updated twice:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
The best way around this is to use the [END] flag (see Apache docs) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent passes). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag.
For earlier versions, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine.
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the
RewriteCond %{ENV:REDIRECT_STATUS} ^$
condition, as discussed here.
The deal with RewriteBase:
You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:
RewriteBase /
Other Pitfalls:
1- Sometimes it's a good idea to disable MultiViews
Options -MultiViews
I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.
I'll explain:
Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.
What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepath and gladly took you there.
Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.
Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.
2- enable FollowSymlinks
Options +FollowSymLinks
That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.
Equation can be done with following example:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
Dynamic Load Balancing:
If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
A better understanding of the [L] flag is in order. The [L] flag is last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):
The [L] flag causes mod_rewrite to stop processing the rule set. In
most contexts, this means that if the rule matches, no further rules
will be processed. This corresponds to the last command in Perl, or
the break command in C. Use this flag to indicate that the current
rule should be applied immediately without considering further rules.
If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of
how the rules are processed. The simplified form of this is that once
the rules have been processed, the rewritten request is handed back to
the URL parsing engine to do what it may with it. It is possible that
as the rewritten request is handled, the .htaccess file or <Directory>
section may be encountered again, and thus the ruleset may be run
again from the start. Most commonly this will happen if one of the
rules causes a redirect - either internal or external - causing the
request process to start over.
So the [L] flag does stop processing any further rewrite rules for that pass through the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory> section, then your modifed request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand what happened, it looks like your first rewrite rule with the [L] flag had no effect.
The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:
They are like a key-value-replacement:
RewriteMap examplemap txt:/path/to/file/map.txt
Then you can use a mapping in your rules like:
RewriteRule ^/ex/(.*) ${examplemap:$1}
More information on this topic can be found here:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.
Conditionally set an environment variable:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
Return a 503 response:
RewriteRule's [R] flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:
RewriteRule .* - [R=503,L]
will return a 503 response (not a redirect per se).
Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPass directives:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
Opinion:
Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.
Take the time to learn it, it's well worth it! :)