大多数动态网站的URL中都含有变量,以告知站点哪些信息须要展现给用户。好比像下面这个URL,会通知相关的脚本加载编号为7的产品:php
XHTML正则表达式
1api |
http://www.example.com/show_a_product.php?product_id=7浏览器 |
这种URL结构的问题在于它并不容易记忆。若是是在电话中也很难读出来(使人惊讶的是有不少人经过这种方式传递URL)。搜索引擎和用户都不能从URL中获得有用的内容信息。你没办法从URL看出在这个页面能买挪威的蓝鹦鹉(的羽毛)。这种是至关标准的URL——也就是通常你在CMS网站上看到的那种。相比下面的URL:服务器
XHTMLdom
1wordpress |
http://www.example.com/products/7/工具 |
这种URL更清晰,也更短,更容易记忆,也更容易被念出来。尽管如此,它仍是不能告诉别人它所指向的内容是什么。可是咱们能够更进一步:post
XHTML学习
1 |
http://www.example.com/parrots/norwegian-blue/ |
这就是咱们要的东西了。即便不看上下文,咱们也能够从这个URL中看出你要找的东西就在这个页面上。搜索引擎能够将这个URL分割成单词(搜索引擎会将URL中的连字符当作空格,可是下划线不是),而后根据这些信息更好地判断页面内容。这种URL是易于记忆和传递的。
不幸的是,要让服务器理解最后一种URL,须要咱们作一番工做。当URL发起一个请求,服务器须要知道如何处理URL,才能知道该返回给用户什么内容。URL重写就是这种将最后一种URL“翻译”成服务器能理解的语言的技术。
根据你的服务器上运行的软件,你可能已经有URL重写模块。若是没有,大多主机都提供启用或安装相关模块的功能,你能够尝试启用它。
Apache启用URL重写是最简单的,它一般带有本身的内建URL重写模块——mod_rewrite,启动和使用mod_rewrite就像上传和命名文件同样简单。
IIS是微软的服务器软件,标配并不包含URL重写的能力,可是有不少插件提供了这种功能。ISAPI_Rewrite 是我比较推荐一款插件,这是我发现的一款功能最接近mod_rewrite的插件。在这篇文章的最后附有ISAPI_Rewrite的安装和配置说明。
下面的代码是一些使用mod_rewrite的例子。
首先咱们来看一个简单的例子。咱们有一个网站,含有一个单独的PHP脚本,展现了一个单独的页面,URL以下:
XHTML
1 |
http://www.example.com/pet_care_info_07_07_2008.php |
咱们想要简化URL,理想的URL像这样:
XHTML
1 |
http://www.example.com/pet-care/ |
为了让这个URL有效,咱们须要让服务器在内部将全部的“pet-care”的请求重定向到“pet_care_info_07_07_2008.php”。咱们但愿这个工做在内部进行是由于咱们不但愿用户浏览器的地址栏发生改变。
为了达到这个目的,咱们首先须要创建一个名为“.htaccess”的文本文档来存储咱们的规则。这个文件必须命名成这样(不能是“.htaccess.txt”或者“rules.htaccess”)。这个文件应该放在服务器的根目录(本例中放在与 “pet_care_info_07_07_2008.php” 相同的目录中)。可能那里已经有一个.htaccess文件了,这时咱们就应该编辑这个文件而不是覆盖它。
.htaccess文件是服务器的配置文件。若是文件中有错误,服务器会提示错误信息(一般错误代码是500)。若是使用FTP协议向服务器发送文件,必须使用ASCII编码传输,而不是BINARY。在本例中,这个文件有两个做用:1. 通知Apache启动重写引擎; 2. 把咱们的重写规则告诉Apache。所以咱们须要在这个文件中加入如下内容:
1 |
RewriteEngine On # Turn on the rewriting engine RewriteRule ^pet-care/?$ pet_care_info_01_02_2008.php [NC,L] # Handle requests for "pet-care" |
有几个须要注意的地方:在.htaccess文件中,‘#’以后的文字都会被当作注释忽略,建议你们多使用注释;“RewriteEngine”这一行在每一个.htaccess文件中应当只使用一次(请注意在后面的代码中都不包括这一行)。
“RewriteRule”行是见证奇迹的地方。这一行能够分为五个部分:
这种规则是重写一个独立URL的简单方式,也是几乎全部URL重写的基础。
前面提到的规则使你能够重定向单独的URL,可是mod_rewrite的强大之处在于根据包涵的模式串,识别和重写成组的URL。
如今咱们要把站点上全部的URL改为像前面举得例子中说的那样。你如今的URL是这样的:
XHTML
1 |
http://www.example.com/show_a_product.php?product_id=7 |
你想把它改为这个样子:
XHTML
1 |
http://www.example.com/products/7/ |
咱们能够只写一条规则来管理全部产品id,而不用对每一个id都写一条规则。其实就是你想把这种类型的URL:
XHTML
1 |
http://www.example.com/show_a_product.php?product_id={a number} |
改为这种样子的:
XHTML
1 |
http://www.example.com/products/{a number}/ |
为了达到这个目的,你须要使用正则表达式。正则表达式是一种格式特殊的、方便服务器理解的pattern。一种典型的用来匹配数字的正则表达式像这样:
1 |
[0-9]+ |
方括号内含有一系列的字符,“0-9”表示全部数字。加号表示将会匹配任何加号前出现的pattern——本例中也就是“一个或多个数字”——也就是咱们要在URL中寻找的东西。
整个的模式部分通常都会被当作正则表达式进行处理——你不须要启动或激活它们。
1 |
RewriteRule ^products/([0-9]+)/?$ show_a_product.php?product_id=$1 [NC,L] # Handle product requests |
首先应该注意的是用括号括起来的模式串,这样咱们能够在接下来的Substitution中使用被括号中pattern匹配的URL进行“back-reference”(向后引用)。Substitution中的“$1”告诉Apache将以前被括号里面的pattern匹配到的URL字串放到这里。你能够有不少back-reference,他们按出现顺序编号。
像上面的RewriteRule语句,将会使Apache把全部domain.com/products/{number}/的请求重定向到show_a_product.php?product_id={same number}。
本文要讲的并非完整的正则表达式的指南。然而,须要注意的重点是整个pattern都会被当作正则表达式处理,要一直注意正则表达式中那些特殊的字符。
最典型的例子是在pattern中使用句号。在一个patter中,’.’ 表示“任意字符”,而不是一个普通的句号,因此当你想要匹配一个句号的时候,你须要对句号进行“转义”——就是在它前面加一个特殊的符号,反斜杠,它会让Apache将下一个字符当作普通字符处理。
好比,下面这条语句 ,将会匹配”rss1xml”、”rss-xml”这样的URL:
1 |
RewriteRule ^rss.xml$ rss.php [NC,L] # Change feed URL |
这样作通常不会有什么很严重的问题,可是对字符进行适当转义的习惯对深刻学习正则表达式有好处。因此最好的写法应该是这样:
1 |
RewriteRule ^rss.xml$ rss.php [NC,L] # Change feed URL |
这种状况只适用于模式比配,而不能用于替换。此外还有其余的字符(咱们称之为“元字符”)用于转义:
经过使用正则表达式,咱们能够匹配任意URL而且重写它们。如今回到咱们文章开头提到的例子,咱们但愿匹配并重写这条URL:
XHTML
1 |
http://www.example.com/parrots/norwegian-blue/ |
咱们想要把这条URL翻译成以下的格式交给服务器:
XHTML
1 |
http://www.example.com/get_product_by_name.php?product_name=norwegian-blue |
咱们能够用很简单一条规则完成这个工做:
1 |
RewriteRule ^parrots/([A-Za-z0-9-]+)/?$ get_product_by_name.php?product_name=$1 [NC,L] # Process parrots |
这个规则让咱们能够提取出URL中“parrot/”以后的任意字母、数字和连字符的组合([A-Za-z0-9-])(将连字符放在字符末尾,方括号的最后,使之被当作连字符处理,而不是分隔符),并将匹配到的产品名称替换为$1.
若是须要的话,咱们也可让规则更普适,使得无论产品出如今哪一个目录下,均可以发送给相同的脚本,就像:
1 |
RewriteRule ^[A-Za-z-]+/([A-Za-z0-9-]+)/?$ get_product_by_name.php?product_name=$1 [NC,L] # Process all products |
像这样,咱们将“parrots”替换为任意字母和连字符的组合。如今这条规则能够匹配任意在parrots目录下或任何以一个或多个字母和连字符组成的名称的目录下的产品了。
修正符跟在重写规则的最后,用来告知Apache如何解释和处理规则。好比能够告诉Apache处理规则时不区分大小写,当遇到第一个匹配时终止匹配,或其余更多的选项。修正符用逗号隔开,并写在方括号里。下面是一些修正符和他们的含义(关于这些符号有一份速查表,不须要所有记住)。
1 |
RewriteRule ^article/?$ http://www.new-domain.com/article/ [R,NC,L] # Temporary Move |
在修正符段添加R修正符能够改变RewriteRule的工做方式。这种状况下Apache会向浏览器返发送一条信息(一个HTTP头),告诉浏览器内容已经被临时移动到了替换块中的URL处,而不是在内部进行URL重写。替换块内能够是绝对的URL也能够是相对的。HTTP头中还包含了302代码,说明移动是临时的。
1 |
RewriteRule ^article/?$ http://www.new-domain.com/article/ [R=301,NC,L] # Permanent Move |
若是移动是永久的,给“R”修饰符添加“=301”字段,Apache会告诉浏览器内容被永久移动。与默认的R修饰符不一样的是,“R=301”也会使浏览器地址栏显示新的地址。
这是最多见的一种URL重写的方法来把内容移动到新的 URL(好比在本网站中就被普遍使用,当文章地址改变时,会将用户带到新的地址去)。
重写规则能够在一个或多个重写条件下进行,并且能够串联,借此咱们能够对一些请求只使用一部分重写。就我我的而言,我最常把这个规则应用到子域或替代域,它能够知足各类各样的标准,不只仅是URL的。举个例子:
1 |
RewriteCond %{HTTP_HOST} ^addedbytes.com [NC] RewriteRule ^(.*)$ http://www.addedbytes.com/$1 [L,R=301] |
上面这条重写规则重定向全部请求到“www.addedbytes.com”。若是没有这个条件,这个规则将会产生一个循环,全部匹配的请求都会被送回给本身。规则的目的是只重定向URL中缺乏“www”的请求,重写条件能够帮助咱们达成目的。
重写条件和重写规则的使用方法差很少。首先写“RewriteCond”告诉mod_rewrite这一行定义了一个条件。接下来是TestString和测试的模式串。最后是方括号内的修正符,跟RewriteRule的写法差很少。
TestString(条件语句的第二部分)能够表示不少不一样的东西。好比在上面的例子中,你能够检测被请求的域,能够检测用户使用的浏览器,能够检测引用URL(一般用来防止盗链),检测用户的IP地址,或者检测其余的东西(参考“服务器变量”一节了解其工做方式)。
模式串跟RewriteRule中的差很少同样,可是有几个小的例外。若是模式串的开始是一个特殊的字符(在“异常”一节定义的),那么模式串将不会被解释成一个匹配模式。这意味着若是你想要在正则表达式中使用用”<“,”>”,或者连字符开头的字符串,你得给他们加一个反斜线用来转义。
重写条件后面也能够像重写规则那样加修正符,可是只有两个。“NC”跟RewriteRule同样,告诉Apache处理条件时忽略大小写。另外一个修正符是“OR”,若是你想在有一两条条件匹配时就应用规则,而不是所有都知足,在第一条条件后加入“OR”修正符(只有两个条件的状况下),这样只须要有其中一条知足,规则就会被应用。默认行为是在多个条件下,只有全部都知足的状况下才可以应用规则。
重写条件有不少不一样的方式进行检测——并不须要当作正则表达式的模式串,虽然正则表达式很经常使用。下面是一些处理重写条件的方法选项:
服务器变量是在重写条件中能够被检测的一些项目。这使得你能够根据全部的请求参数——包括浏览器标识、引用URL或其余的字符串——来应用适当的规则。变量格式以下:
越复杂的网站,就会有越复杂的规则来管理。当规则产生冲突的时候,行为是不肯定的。每每在添加一条新的规则以后,会出现一些莫名其妙的问题,好比根本不起做用。若是这条规则自己是没有问题的,那多是以前有一条规则匹配到了这个URL,因此这条URL根本没有被匹配到新加入的规则。
1 2 |
RewriteRule ^([A-Za-z0-9-]+)/([A-Za-z0-9-]+)/?$ get_product_by_name.php?category_name=$1&product_name=$2 [NC,L] # Process product requests RewriteRule ^([A-Za-z0-9-]+)/([A-Za-z0-9-]+)/?$ get_blog_post_by_title.php?category_name=$1&post_title=$2 [NC,L] # Process blog posts |
在这个例子中,产品页面和blog页面有不一样的模式串,可是第二条规则将不会匹配到URL,由于全部能被匹配的模式都已经被第一条规则匹配到了。
解决这个问题的方式有不少。不少CMS(包括wordpress)经过在URL中增长一个表示请求类型的串来供规则匹配,好比:
1 2 |
RewriteRule ^products/([A-Za-z0-9-]+)/([A-Za-z0-9-]+)/?$ get_product_by_name.php?category_name=$1&product_name=$2 [NC,L] # Process product requests RewriteRule ^blog/([A-Za-z0-9-]+)/([A-Za-z0-9-]+)/?$ get_blog_post_by_title.php?category_name=$1&post_title=$2 [NC,L] # Process blog posts |
你也能够写一个单独的PHP页面来处理全部请求,它能够检查URL的第二个部分是否能匹配上一个blog或者一个产品。我一般这样作,虽然可能会给服务器带来一些额外的负担,可是他让URL更加简洁。
1 |
RewriteRule ^([A-Za-z0-9-]+)/([A-Za-z0-9-]+)/?$ get_product_or_blog_post.php?category_name=$1&item_name=$2 [NC,L] # Process product and blog requests |
还能够经过设计更精确的规则和对规则进行更合理的安排来解决这个问题。想象一个blog有两个分类集——主题和发布年份。
1 2 |
RewriteRule ^([A-Za-z0-9-]+)/?$ get_archives_by_topic.php?topic_name=$1 [NC,L] # Get archive by topic RewriteRule ^([A-Za-z0-9-]+)/?$ get_archives_by_year.php?year=$1 [NC,L] # Get archive by year |
上面这两条规则会冲突。固然,年份只由四位数字组成,因此你能够吧规则写的更精确,这样只有在主题名称也是四位数字的时候才会产生冲突。
1 2 |
RewriteRule ^([0-9]{4})/?$ get_archives_by_year.php?year=$1 [NC,L] # Get archive by year RewriteRule ^([A-Za-z0-9-]+)/?$ get_archives_by_topic.php?topic_name=$1 [NC,L] # Get archive by topic |
mod_rewrite
Apache的mod_rewrite模块在大多Apache托管中是标配,若是你使用共享的托管服务,你不须要作什么配置。可是若是你是在管理本身的空间,你须要启用mod_rewrite模块。若是你在使用Apache1,你得修改httpd.conf文件,去掉下面这行行首的”#”
1 |
#LoadModule rewrite_module modules/mod_rewrite.so #AddModule mod_rewrite.c |
若是在类Debian发行版上用Apache2,你只须要使用一下命令并重启Apache:
1 |
sudo a2enmod rewrite |
其余发行版或其余平台可能不太同样。若是上面这两种方法都不适用于你的系统,那就去google一下吧。可能须要修改Apache2的配置文件,把“rewrite”加入到APACHE_MODULES列表里,或者要修改httpd.conf,实在不行就下载mod_rewrite的源码本身编译安装。这些方法都不麻烦的。
ISAPI_Rewrite是IIS上一个基于mod_rewrite的插件,它跟mod_rewrite的功能差很少,并且还有一些高质量的ISAPI_Rewrite论坛用来交流释疑。由于ISAPI_Rewrite是IIS上的,安装也很是简单。
ISAPI_Rewrite的规则默认写在httpd.ini文件中,错误日志在httpd.parse.errors文件中。
在实际中我常常被URL重写中的正斜杠所困扰,无论在模式串中、RewriteRule的替换串中仍是RewriteCond的状态中,都会困扰我。这多是因为我常常面对不一样的URL重写引擎,然而我仍然建议你们——当一个规则无效时,先注意一下是否是正斜杠搞的鬼。我一般在mod_rewrite规则中避免使用斜杠,可是在ISAPI_Rewrite中会使用。
把旧的域重定向到一个新的域:
1 |
RewriteCond %{HTTP_HOST} old_domain.com [NC] RewriteRule ^(.*)$ http://www.new_domain.com/$1 [L,R=301] |
重定向缺乏“www”的请求(添加“www”):
1 |
RewriteCond %{HTTP_HOST} ^domain.com [NC] RewriteRule ^(.*)$ http://www.domain.com/$1 [L,R=301] |
重定向全部含有“www”的网页(去掉“www”):
1 |
RewriteCond %{HTTP_HOST} ^www.domain.com [NC] RewriteRule ^(.*)$ http://domain.com/$1 [L,R=301] |
把旧页面重定向到新页面:
1 |
RewriteRule ^old-url.htm$ http://www.domain.com/new-url.htm [NC,R=301,L] |