服务器上一直用着mysql_pconnect,之前听说它会出现各种各样的问题,但服务器一直没事,也就没去管。今天看这篇文章,才知道原来mysql_pconnect有这么多的道道。

原文地址:http://www.cnblogs.com/funlake/archive/2011/09/08/2171822.html

  php的mysql持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之。这到底是为啥么。近距离观察后发现,这家伙也不容易啊,要看apache的脸色,还得听mysql指挥。

  对于做为apache模块运行的php来说,要实现mysql持久化连接,首先得取决于apache这个web服务器是否支持Keep-Alive。

  Keep-Alive

  Keep-Alive是什么东西?它是http协议的一部分,让我们复习一下没有Keep-Alive的http请求,从客户在浏览器输入一个有效url地址开始,浏览器就会利用socket向url对应的web服务器发送一条tcp请求,这个请求成功一次就得需要来回握三次手才能确定,成功以后,浏览器利用socket tcp连接资源向web服务器请求http协议,发送以后就等着web服务器把http返回头和body发送回来,发回来后浏览器关闭socket连接,然后做http返回头和body的解析工作,最后呈现在浏览器上的就是漂亮的页面了。这里面有什么问题呢?tcp连接需要三次握手,也就是来回请求三次方能确定一个tcp请求是否成功,然后tcp关闭呢?来回需要4次请求才能完成!每次http请求就3次握手,4次拜拜,这来来回回的不嫌累啊,多少时间和资源都被浪费在socket连接关闭上了,能不能一次socket tcp连接发送多次http请求呢?于是Keep-Alive就应运而生,http/1.0里需要客户端自己在请求头加入Connection:Keep-alive方能实现,在这里我们只考虑http1.1了,只需要设置一下apache,让它默认就是Keep-Alive持久连接模式(apache必须1.2+才能支持Keep-Alive).在httpd.conf里找到KeepAive配置项,果断设置为On,MaxKeepAliveRequests果断为0(一个持久tcp最多允许的请求数,如果过小,很容易在tcp未过期的情况下,达到最大连接,那下次连接就又是新的tcp连接了,这里设置0表示不限制),然后对于mysql_pconnect最重要的选项KeepAliveTimeout设置为15(表示15秒).

  好了,重启apache,测试一下,赶紧写行东西

<?php
    echo "Apache进程号:".getmypid();
?>

很简单,获取当前php执行者(apache)的进程号,用浏览器浏览这个页面,看到什么?对,有看到一串进程号数字,15秒内,连续刷新页面,看看进程号有无变化?木有吧?现在把手拿开,交叉在胸前,度好时间,1秒,2秒,3,…15,16。好,过了15秒了,再去刷新页面,进程号有没有变化?变了!又是一个新的apache进程了,为什么15秒后就变成新的进程了?记得我们在apache里设置的KeepAliveTimeout吗?它的值就是15秒.现在我们应该大致清楚了,在web服务器默认打开KeepAlive的情况下,客户端第一次http成功请求后,apache不会立刻断开socket,而是一直监听来自这一客户端的请求,监听多久?根据KeepAliveTimeout选项配置的时间决定,一旦超过这一时间,apache就会断开socket了,那么下次同一客户端再次请求,apache就会新开一个进程来相应。所以我们之前15内不停的刷新页面,看到的进程号都是一致的,表明是浏览器请求给了同一个apache进程。

  浏览器是怎么知道不需要重新进行tcp连接就可以直接发送http请求呢?因为http返回头里就会带上Connection:keep-alive,Keep-alive:15两行,意思就是让客户端浏览器明白,这次socket连接我这边还没关闭呢,你可以在15内继续使用这个连接,并发送http请求,于是乎浏览器就知道应该怎么做了.

  php怎么做

  那么,php的mysql连接资源是怎么被hold住的呢,这需要查看php的mysql_pconnect的函数代码,我看了下,大概的做法就是根据当前apache进程号,生成hash key,找hash表内有无对应的连接资源,没有则推入hash表,有则直接使用。有些代码片段可以说明(具体可查看php5.3.8源码ext/mysql/php_mysql.c文件690行php_mysql_do_connect函数)

    #1.生成hash key
        user=php_get_current_user();//获取当前php执行者(apache)的进程唯一标识号
        hashed_details_length = spprintf(&hashed_details, 0, "mysql__%s_", user);//hashed_details就是hash key
    #2.如果未找到已有资源,就推入hash表,名字叫persistent_list,如果找到就直接使用
         /* try to find if we already have this link in our persistent list */
         if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) {  /* we don't */
            ...
            ...
            /* hash it up(推入hash表) */
            Z_TYPE(new_le) = le_plink;
            new_le.ptr = mysql;
            if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
                ...
                ...
            }

         }else{/* The link is in our list of persistent connections(连接已在hash表里)*/
            ...
            ...
            mysql = (php_mysql_conn *) le->ptr;//直接使用对应的sql连接资源
            ...
            ...

         }

zend_hash_find比较容易看明白,原型是zend_hash_find(hash表,key名,key长,value);如果找到,value就有值了。

      mysql的wait_timeout和interactive_timeout

  说完Keep-Alive,该到mysql家串串门了,说的是mysql_pconnect,怎么能绕开mysql的设置。

  影响mysql_pconnect最重要的两个参数就是wait_timeout和interactive_timeout,它们是什么东西?先撇一边,首先让我们把上面的代码改动一下php代码

<?php
    $conn = mysql_pconnect("localhost","root","123456") or die("Can not connect to mysql");
    echo "Mysql线程号:".mysql_thread_id($conn)."<br/>";
    echo "Apache进程号".getmypid();
?>

以上的代码没啥好解释的,让我们用浏览器浏览这个页面,看到什么?看到两个显眼的数字。一个是mysql线程号,一个是apache进程号,好了,15秒后再刷新这个页面,发现这两个id都变了,因为已经是新的apache进程了,进程id是新的,hash key就变了,php只好重新连接mysql,连接资源推入persistent list。如果15内刷新呢?apache进程肯定不变,mysql线程号会变吗?答案得问mysql了。首先这个mysql_thread_id是什么东西?shell方式登录mysql后执行命令’show processlist’,看到了什么?

mysql> show processlist;
+-----+------+-----------+------+---------+------+-------+------------------+
| Id  | User | Host      | db   | Command | Time | State | Info             |
+-----+------+-----------+------+---------+------+-------+------------------+
| 348 | root | localhost | NULL | Query   |    0 | NULL  | show processlist |
| 349 | root | localhost | NULL | Sleep   |    2 |       | NULL             |
+-----+------+-----------+------+---------+------+-------+------------------+

,发现了很重要的信息,这个processlist列表就是记录了正在跑的线程,忽略Info列为show processlist那行,那行是你当前shell登录mysql的线程。php连接mysql的线程就是Id为349那行,如果读者自己做测试,应该知道这个Id=349在你的测试环境里是另外一个值,我们把这个值和网页里输出的mysql_thread_id($conn)做做比较,对!他们是一样的。接下来最重要的是观察Command列和Time列,Command = Sleep,表明什么?表明我们mysql_pconnect连接后就一直在sleep,Time字段就告诉我们,这个线程Sleep了多久,那么Sleep了多久这个线程才能作废呢?那就是wait_timeout或者interactive_timeout要做的工作了,他们默认的值都是8小时,天啊,太久了,所以如果说web服务器关掉KeepAlive支持,那个这个processlist很容易就被撑爆,就爆出那个Too many connections的错误了,max_connectiosns配置得再多也没用。为了观察这两个参数,我们可以在mysql配置文件my.cnf里设置这两个值,找到[mysqld]节点,在里面设置多两行

interactive_timeout = 60
wait_timeout        = 30

配置完后,重启mysql,shell登录mysql,这时候show processlist可以发现只有当前线程。然后运行那个带有mysql_pconnect的php页面,再回来mysql端show processlist可发现,多了一个Commond为Sleep的线程,不停的show processlist(方向键上+enter键)观察Time列的变化2,5,10..14!,突然那个Sleep线程程被kill掉了,咋回事,还没到30秒呢,噢!忘了修改一下apache keepalive的参数了,把KeepAliveTimeOut从15改成120(只为观察,才这么改),重启apache.刷新那个页面,好,开始不停的show processlist,2..5..10..14,15,..20…26….28,29!线程被kill,这次是因为wait_timeout起了作用,浏览器那边停了30秒,30秒内如果浏览器刷新,那这个Time又会从0开始计时。这种连接不属于interactive connection(mysql shell登录那种连接就属于interactive connection),所以采用了wait_timeout的值。如果mysql_pconnect的第4个参数改改呢

<?php
$conn = mysql_pconnect('localhost','root','123456',MYSQL_CLIENT_INTERACTIVE);
echo "Mysql线程号:".mysql_thread_id($conn)."<br/>";
echo "Apache进程号:".getmypid();
?>

刷新下页面,mysql那边开始刷show processlist,这回Time > 30也不会被kill,>60才被kill了,说明设置了MYSQL_CLIENT_INTERACTIVE,就会被mysql视为interactive connection,那么这次php的mysql连接在120秒内未刷新页面的情况下,何时作废将取决于mysql的interactive_timeout的配置值。

  总结

  #1.php的mysql_pconnect要达到功效,首先必须保证apache是支持keep alive的,然后KeepAliveTimeOut应该设置多久呢,要根据自身站点的访问情况做调整,时间太短,keep alive没啥意义,时间太长,就很可能为一个闲客户端连接牺牲很多服务器资源,毕竟hold住socket监听进程是要消耗cpu内存的.

  #2.apache的KeepAliveTimeOut配置得和mysql的time out配置要有个平衡点,联系以上的观察,假设mysql_pconnect未带上第4个参数,如果apache的KeepAliveTimeOut设置的秒数比wait_timeout小,那真正对mysql_pconnect起作用的是apache而不是mysql的配置.这时如果mysql的wait_timeout偏大,并发量大的情况下,很可能就一堆废弃的connection了,mysql这边如果不及时回收,那就很可能Too many connections了.可是如果KeepAliveTimeOut太大呢,又回到之前的问题,所以貌似Apache.KeepAliveTimeOu不要太大,但比Mysql.wait_timeout 稍大,或者相等是比较好的方案,这样可以保证keep alive过期后,废弃的mysql连接可以及时被回收. 

  后记

  Pdo数据库的长连接机制是否和mysql_pconnect一样?经过试验观察和源码探究,发现也是一样的处理方式。

大多人浏览网页如果超过3秒打不开就会离开,Google为此推出了一款apache加速模块,可以有效将网页加载速度提高50%.
网页提速的问题是一个复杂多样的问题,有很多解决方法,往往关系到网站系统程序,服务器硬件,网络传输速度等方面,而Google这款加速模块简单的解决了许多复情况的问题:

•如果你的网站采用apache构建服务器,不需要对网站CMS系统进行处理即可应用
•加速模块可以自行对网络传输的html字节优化及对图象,css进入压缩优化传输
•智能缓存是一大亮点,它可以自动智能缓存,加速下载
下面介绍一下所有特点功能

优化缓存
•缓存扩展
•压缩处理CSS
•优化javascript最大限度的减少重复请求
•自动缓存CSS
•JavaScript内嵌技术
有效载荷尺寸最小化
•压缩空白
•合并头信息
•附加属性
•内建核心Javascript
•优化图像下载
•跳地非法字符
•重写优化CSS

访问:mod_pagespeed

车东很早就写了一篇文章来介绍利用mod_rewrite模块来达到用静态页面形式的链接隐藏后台的动态页面
  Apache的rewrite模块,提供了一个基于规则的重写(rewrite,也许译为重构更为合适)引擎,来实时重写发送到Apache的请求URL。因功能极其强大,被称为URL重写的“瑞士军刀”。

  这个模块使用一个基于正则表达式解析器开发的重写引擎,根据web管理员定义的规则来实时(on the fly)重写请求URL。它支持任意数目的重写规则,以及附加到一条规则上的任意数目的规则条件,从而提供了一套非常灵活和功能强大的URL处理机制。URL处理操作的实施与否,依赖于各种各样的条件检查,如检查服务器变量、环境变量、HTTP头字段、时间戳的值,甚至外部数据库的检索结果。这个模块可以在服务器范围内(http.conf)、目录范围内(.htaccess)或请求串(query-string)的一部分处理有关的URL。重写的结果URL,可以指向一个站内的处理程序、指向站外的重定向或者一个站内的代理。与灵活和功能强大相随的是设置的复杂,别指望一天内弄明白整个模块。(所以,这个学习笔记也分了几部分:)
  

内部处理过程

API阶段
  首先,Apache处理HTTP请求是分阶段进行的,Apache API为每个阶段提供了一个钩子(hook)。Mod_rewrite使用了其中的两个钩子:一个用来在HTTP请求被读取但还没有访问授权验证之前进行URL_to_filename转换,一个用来在授权验证完成且目录设置文件(.htaccess)读取之后、但内容处理器(content handler)被调用之前激化,进行修补(fixup).因此,当一个请求到达,Apache决定了相关的服务器(或虚拟服务器)以后进行URL_to_filename阶段,重写引擎(rewrite engine)开始处理服务器设置中的重写指令(mod_rewrite directives).接下来几个阶段过后进入修补阶段,此时最终的数据所在的物理目录已经找到,目录配置中的重写指令开始执行。在这两个阶段,mod_rewrite都是将URL重写为新的URL或文件名,所以看起来并没有明显的区别。对API的这种应用,并不是一开始就是这样设计的,而是Apache1.x不得已而为之。为了搞清这个问题,以下两点需要记住。
  1)虽然mod_rewrite能进行URL到URL、URL到文件名字甚至文件名字到文件名字的转换,API(1.x)目前提供了一个URL_to_filename转换。在Apache2.0中,这两个钩子会被加进去,整个过程会更加清晰。一个事实必须清楚的记得:Apache在URL_to_filename钩子中,做得比API设计的功能更多。
  2)不可思议的是,mod_rewrite能在目录范围内(如根据.htaccess文件的指令配置)进行URL处理,虽然URL很早就已经被转换为文件名字了。只所以会如此,是因为.htaccess文件存在于文件系统中。也就是说,在这个阶段来进行URL处理,是非常晚的时候了。为了解决这个”先有鸡还是先有蛋”的问题,mod_rewrite用了一个小技巧:当在目范围内处理URL/filename时,mod_rewrite先将文件名逆转回相关的URL(虽然通常是不可能的,但请参见下面用以实现这个技巧的RewriteBase指令),然后据这个新URL生成一个站内的子请求(internal sub-request),这又重开始了API进程。Mod_rewrite尽量使这些复杂的步骤对用户透明,但应要记住:虽然目录范围URL的真正处理过程很快很高效,但这一阶段会因为这个”鸡和蛋”的问题而变得很慢和低效。从另一方面来看,这也是mod_rewrite提供给普通用户进行目录范围内的URL处理的唯一途径.
规则集(RewriteRule指令集合)处理过程
  当mod_rewrite在上述的两个API阶段被激活时,它会从它的配置数据结构(在开始服务器上下文(per-server context)或目录上下文(per-directory context)时创建的)中读取配置的规则集,然后URL重写引擎启动来执行包含的规则集(一个或多条规则以及它们的条件)。两种上下文中的处理过程都是一样的,差别只是在最后的结果处理过程上。
  规则集中规则的顺序是非常重要的,因为重写引擎以特定的顺序来处理它们。重写引擎顺序遍历规则集,当一条规则匹配时,引擎会去遍历与它相关的条件集(RewriteCond指令集合).由于历史的原因,条件集先被列出来,因此控制流流程有点曲折(long-winded).如图一所示:mod_rewrite_fig1.gif
  正如所看到的,首先URL会与每条规则的模板(pattern)比较,当匹配失败时,立即停止对当前规则的处理进入下一条规则。当匹配成功时,mod_rewrite寻找相关的规则条件。如果找不到相关的条件,则直接执行规则中定义的替换,然后回到规则遍历的过程。如果找到了相关的条件,则启动一个内部循环,依次检查各个条件。对于检查,我们不是拿一个模板来匹配当前的URL,而是先创建一个TestString串,将串内的变量、后向引用(bakc-reference)、查询结果(map lookups)等展开,然后用这个TestString和条件式中的CondPattern进行匹配,如果匹配失败,则整个条件集且这个规则都不再执行,重要回到规则遍历中;如果匹配成功,则检查下一个条件,如果所有的条件都满足,则执行规则中定义的替换动作。  
特殊字符的转义
  既然基于正则式,则当然会有特殊字符的问题。在1.3.20版本的Apache中,通过在特殊字符前加一个“”来将TestString或Sustitution串的特殊字符转义。
正则式的后向引用
mod_rewrite_fig2.gif  有一点需要记住:一旦在模板(pattern)或条件模板(CondPattern)中使用了括号,则后向引用已经自动产生了,你可以在Sustitution或TestString中通过$N或%N来引用相关的值。如图,描述了后向引用的值可以传到的位置。

配置指令(Configuration Directives)

 

指令 语法 默认值 说明 备注
RewriteEngine RewriteEngine on|off Off 开关重构引擎 默认时不能继承,故每个虚拟主机都要有自己的开关指令。
RewriteOptions RewriteOptions Option MaxRedirects=10 设置一些特殊参数 inherit:配置是否继承,MaxRedirects=number:内部重定向次数
RewriteLog RewriteLog file-path None 设定重写log文件 用RewriteLogLevel 0来禁止日志
RewriteLogLevel RewriteLogLevel Level RewriteLogLevel 0 设置日志级别 0表示没有,2以上用于debug,9及以上表示全部信息
RewriteLock RewriteLock file-path None 设置RewriteMap程序的同步锁文件 要求是本地文件,此文件只对rewriting map-program有效。
RewriteMap RewriteMap MapName MapType:MapSource Notused per default 定义重写影射 具体说明参见文档
RewriteBase RewriteBase URL-path physical directory path 设置目录范围内重写的基本URL 具体说明参见文档
RewriteCond RewriteCond TestString CondPattern None 定义规则条件 具体说明参见文档
RewriteRule RewriteRule Pattern Substitution None 定义重写规则 具体说明参见文档

 

 

  今天学习重写规则的语法。

RewriteRule
Syntax: RewriteRule Pattern Substitution [flags]
  一条RewriteRule指令,定义一条重写规则,规则间的顺序非常重要。对Apache1.2及以后的版本,模板(pattern)是一个POSIX正则式,用以匹配当前的URL。当前的URL不一定是用记最初提交的URL,因为可能用一些规则在此规则前已经对URL进行了处理。
  对mod_rewrite来说,!是个合法的模板前缀,表示“非”的意思,这对描述“不满足某种匹配条件”的情况非常方便,或用作最后一条默认规则。当使用!时,不能在模板中有分组的通配符,也不能做后向引用。
  当匹配成功后,Substitution会被用来替换相应的匹配,它除了可以是普通的字符串以外,还可以包括:

  1. $N,引用RewriteRule模板中匹配的相关字串,N表示序号,N=0..9
  2. %N,引用最后一个RewriteCond模板中匹配的数据,N表示序号
  3. %{VARNAME},服务器变量
  4. ${mapname:key|default},映射函数调用

这些特殊内容的扩展,按上述顺序进行。
  一个URL的全部相关部分都会被Substitution替换,而且这个替换过程会一直持续到所有的规则都被执行完,除非明确地用L标志中断处理过程。
  当susbstitution有”-”前缀时,表示不进行替换,只做匹配检查。
  利用RewriteRule,可定义含有请求串(Query String)的URL,此时只需在Sustitution中加入一个?,表示此后的内容放入QUERY_STRING变量中。如果要清空一个QUERY_STRING变量,只需要以?结束Substitution串即可。
  如果给一个Substitution增加一个http://thishost[:port]的前缀,则mod_rewrite会自动将此前缀去掉。因此,利用http://thisthost做一个无条件的重定向到自己,将难以奏效。要实现这种效果,必须使用R标志。
  Flags是可选参数,当有多个标志同时出现时,彼此间以逗号分隔。

  1. ‘redirect|R [=code]’ (强制重定向)
      给当前的URI增加前缀http://thishost[:thisport]/, 从而生成一个新的URL,强制生成一个外部重定向(external redirection,指生的URL发送到客户端,由客户端再次以新的URL发出请求,虽然新URL仍指向当前的服务器). 如果没有指定的code值,则HTTP应答以状态值302 (MOVED TEMPORARILY),如果想使用300-400(不含400)间的其它值可以通过在code的位置以相应的数字指定,也可以用标志名指定: temp (默认值), permanent, seeother.
      注意,当使用这个标志时,要确实substitution是个合法的URL,这个标志只是在URL前增加http://thishost[:thisport]/前缀而已,重写操作会继续进行。如果要立即将新URL重定向,用L标志来中重写流程。
  2. ‘forbidden|F’ (强制禁止访问URL所指的资源)
      立即返回状态值403 (FORBIDDEN)的应答包。将这个标志与合适的RewriteConds 联合使用,可以阻断访问某些URL。
  3. ‘gone|G’ (强制返回URL所指资源为不存在(gone))
      立即返回状态值410 (GONE)的应答包。用这个标志来标记URL所指的资源永久消失了.
  4. # ‘proxy|P’ (强制将当前URL送往代理模块(proxy module))
      这个标志,强制将substitution当作一个发向代理模块的请求,并立即将共送往代理模块。因此,必须确保substitution串是一个合法的URI (如, 典型的情况是以http://hostname开头),否则会从代理模块得到一个错误. 这个标志,是ProxyPass指令的一个更强劲的实现,将远程请求(remote stuff)映射到本地服务器的名字空间(namespace)中来。
      注意,使用这个功能必须确保代理模块已经编译到Apache 服务器程序中了. 可以用“httpd -l ”命令,来检查输出中是否含有mod_proxy.c来确认一下。如果没有,而又需要使用这个功能,则需要重新编译“httpd”程序并使用mod_proxy有效。
  5. ‘last|L’ (最后一条规则)
      中止重写流程,不再对当前URL施加更多的重写规则。这相当于perl的last命令或C的break命令。
  6. ‘next|N’ (下一轮)
      重新从第一条重写规则开始执行重写过程,新开的过程中的URL不应当与最初的URL相同。 这相当于Perl的next命令或C的continue命令. 千万小心不要产生死循环。
  7. # ‘chain|C’ (将当前的规则与其后续规则綑绑(chained))
      当规则匹配时,处理过程与没有綑绑一样;如果规则不匹配,则綑绑在一起的后续规则也不在检查和执行。
  8. ‘type|T=MIME-type’ (强制MIME类型)
      强制将目标文件的MIME-type为某MIME类型。例如,这可用来模仿mod_alias模块对某目录的ScriptAlias指定,通过强制将该目录下的所有文件的类型改为 “application/x-httpd-cgi”.
  9. ‘nosubreq|NS’ (used only if no internal sub-request )
      这个标志强制重写引擎跳过为内部sub-request的重写规则.例如,当mod_include试图找到某一目录下的默认文件时 (index.xxx),sub-requests 会在Apache内部发生. Sub-requests并非总是有用的,在某些情况下如果整个规则集施加到它上面,会产生错误。利用这个标志可排除执行一些规则。
  10. ‘nocase|NC’ (模板不区分大小写)
      这个标志会使得模板匹配当前URL时忽略大小写的差别。
  11. ‘qsappend|QSA’ (追加请求串(query string))
      这个标志,强制重写引擎为Substitution的请求串追加一部分串,则不是替换掉原来的。借助这个标志,可以使用一个重写规则给请求串增加更多的数据。
  12. ‘noescape|NE’ (不对输出结果中的特殊字符进行转义处理)
      通常情况下,mod_write的输出结果中,特殊字符(如’%’, ‘$’, ‘;’, 等)会转义为它们的16进制形式(如分别为’%25′, ‘%24’, and ‘%3B’)。这个标志会禁止mod_rewrite对输出结果进行此类操作。 这个标志只能在 Apache 1.3.20及以后的版本中使用。
  13. ‘passthrough|PT’ (通过下一个处理器)
      这个标志强制重写引擎用filename字段的值来替换内部request_rec数据结构中uri字段的值。. 使用这个标志,可以使后续的其它URI-to-filename转换器的Alias、ScriptAlias、Redirect等指令,也能正常处理RewriteRule指令的输出结果。用一个小例子来说明它的语义:如果要用mod_rewrite的重写引擎将/abc转换为/def,然后用mod_alas将/def重写为ghi,则要:
    RewriteRule ^/abc(.*) /def$1 [PT]
    Alias /def /ghi
    如果PT标志被忽略,则mod_rewrite也能很好完成工作,如果., 将 uri=/abc/… 转换为filename=/def/… ,完全符合一个URI-to-filename转换器的动作。接下来 mod_alias 试图做 URI-to-filename 转换时就会出问题。
    注意:如果要混合都含有URL-to-filename转换器的不同的模块的指令,必须用这个标志。最典型的例子是mod_alias和mod_rewrite的使用。
  14. ‘skip|S=num’ (跳过后面的num个规则)
      当前规则匹配时,强制重写引擎跳过后续的num个规则。用这个可以来模仿if-then-else结构:then子句的最后一条rule的标志是skip=N,而N是else子句的规则条数。
  15. ‘env|E=VAR:VAL’ (设置环境变量)
      设置名为VAR的环境变量的值为VAL,其中VAL中可以含有正则式的后向引用($N或%N)。这个标志可以使用多次,以设置多个环境变量。这儿设置的变量,可以在多种情况下被引用,如在XSSI或CGI中。另外,也可以在RewriteCond模板中以%{ENV:VAR}的形式被引用。

注意:一定不要忘记,在服务器范围内的配置文件中,模板(pattern)用以匹配整个URL;而在目录范围内的配置文件中,目录前缀总是被自动去掉后再进行模板匹配的,且在替换完成后自动再加上这个前缀。这个功能对很多种类的重写是非常重要的,因为如果没有去前缀,则要进行父目录的匹配,而父目录的信息并不是总能得到的。一个例外是,当substitution中有http://打头时,则不再自动增加前缀了,如果P标志出现,则会强制转向代理。
注意:如果要在某个目录范围内启动重写引擎,则需要在相应的目录配置文件中设置“RewriteEngine on”,且目录的“Options FollowSymLinks”必须设置。如果管理员由于安全原因没有打开FollowSymLinks,则不能使用重写引擎。

 

 

  今天学习重写规则的条件。

RewriteCond
Syntax: RewriteCond TestString CondPattern [flags]
  RewriteCond指令定义一条规则条件。在一条RewriteRule指令前面可能会有一条或多条RewriteCond指令,只有当自身的模板(pattern)匹配成功且这些条件也满足时规则才被应用于当前URL处理。
  TestString是一个字符串,除了包含普通的字符外,还可以包括下列的可扩展结构:

  1. $N,RewriteRule后向引用,其中(0 <= N <= 9)
      $N引用紧跟在RewriteCond后面的RewriteRule中模板中的括号中的模板在当前URL中匹配的数据。
  2. %N,RewriteCond后向引用,其中(0 <= N <= 9)
      %N引用最后一个RewriteCond的模板中的括号中的模板在当前URL中匹配的数据。
  3. ${mapname:key|default},RewriteMap扩展.
    具体参见RewriteMap
  4. %{ NAME_OF_VARIABLE } ,服务器变量。
    变量的名字如下表(分类显示)

    HTTP headers: connection & request: server internals: system stuff:
    HTTP_USER_AGENT REMOTE_ADDR DOCUMENT_ROOT TIME_YEAR
    HTTP_REFERER REMOTE_HOST SERVER_ADMIN TIME_MON
    HTTP_COOKIE REMOTE_USER SERVER_NAME TIME_DAY
    HTTP_FORWARDED REMOTE_IDENT SERVER_ADDR TIME_HOUR
    HTTP_HOST REQUEST_METHOD SERVER_PORT TIME_MIN
    HTTP_PROXY_CONNECTION SCRIPT_FILENAME SERVER_PROTOCOL TIME_SEC
    HTTP_ACCEPT PATH_INFO SERVER_SOFTWARE TIME_WDAY
      QUERY_STRING   TIME
      AUTH_TYPE    
    specials: 说明
    API_VERSION Apache与模块间的接口的版本号
    THE_REQUEST 客户端发送到来的HTTP请求行的整行信息,不含其它的头字段信息,如(”GET /index.html HTTP/1.1″)
    REQUEST_URI HTTP请求行中请求的资源
    REQUEST_FILENAME 请求中对应的服务器本地文件系统中全路径文件名
    IS_SUBREQ 根据是否为SubRequest,分别值为”true”或”false”

    特别说明:

    • SCRIPT_FILENAME和REQUEST_FILENAME变量含有相同的值,也就是Apache服务器内部数据结构request_rec的filename字段的值。第一个变量是一个CGI变量,而第二个则与REQUEST_URI(含有request_rec数据结构中uri字段的值)保持一致。
    • %{ENV:variable}中的variable可以是任何环境变量的名字。对其值的查找,先通过Apache内部的数据结构,(如找不到)再在Apache服务器进程中通过getenv()查找。
    • %{HTTP:header}中的header可以是任何HTTP MIME-header的名字,其值通过查找HTTP请求信息而得。  

       

    • %{LA-U:variable} 用来引用后续API阶段中定义的、当前还不知道的值,具体实现是通过执行一个基于URL的内部的sub-request来决定的variable的最终的值。例如,假如你想在服务器范围内利用REMOTE_USER的值来完成重写,但这个值是在验证阶段设置的,而验证阶段是在URL转换阶段的后面。从另一方面讲,由于mod_rewrite在修补(fixup)API阶段进行目录范围的重写,而修补阶段在验证阶段的后面,所以此时只要用%{REMOTE_USER}就可以取得该值了。
    • %{LA-F:variable},执行一个基于文件名字(filename)的内部sub-request来决定variable的最终的值。大多数时间内,这和LA-U相同。

  CondPattern是一个条件模板,也就是说,是一个扩展正则式(extended regular expression),用与跟TestString进行匹配。作为一个标准的扩展正则式,CondPattern有以下补充:

  1. 可以在模板串前增加一个!前缀,以用表示不匹配模板。但并不是所有的test都可以加!前缀。
  2. CondPattern中可以使用以下特殊变量:
    • ‘ 将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 的字符小于CondPattern为真.
    • ‘>CondPattern’ (大于)
      将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 的字符大于CondPattern为真.
    • ‘=CondPattern’ (等于)
      将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 与CondPattern完全相同时为真.如果CondPattern只是 “” (两个引号紧挨在一起) 此时需TestString 为空字符串方为真.
    • ‘-d’ (是否为目录)
      将testString当作一个目录名,检查它是否存在以及是否是一个目录.
    • ‘-f’ (是否是regular file)
      将testString当作一个文件名,检查它是否存在以及是否是一个regular文件.
    • ‘-s’ (是否为长度不为0的regular文件)
      将testString当作一个文件名,检查它是否存在以及是否是一个长度大于0的regular文件
    • ‘-l’ (是否为symbolic link)
      将testString当作一个文件名,检查它是否存在以及是否是一个 symbolic link.
    • ‘-F’ (通过subrequest来检查某文件是否可访问)
      检查TestString是否是一个合法的文件,而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。
    • ‘-U’ (通过subrequest来检查某个URL是否存在)
      检查TestString是否是一个合法的URL,而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。

  [flags]是第三个参数,多个标志之间用逗号分隔。

  1. ‘nocase|NC’ (不区分大小写)
      在扩展后的TestString和CondPattern中,比较时不区分文本的大小写。注意,这个标志对文件系统和subrequest检查没有影响.
  2. ‘ornext|OR’ (建立与下一个条件的或的关系)
      默认的情况下,二个条件之间是AND的关系,用这个标志将关系改为OR。例如:

    RewriteCond %{REMOTE_HOST} ^host1.* [OR]
    RewriteCond %{REMOTE_HOST} ^host2.* [OR]
    RewriteCond %{REMOTE_HOST} ^host3.*
    RewriteRule …

    如果没有[OR]标志,需要写三个条件/规则.

例子:根据客户端浏览器的不同,返回不同的首页面。

RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*
RewriteRule ^/$ /homepage.max.html [L]
RewriteCond %{HTTP_USER_AGENT} ^Lynx.*
RewriteRule ^/$ /homepage.min.html [L]
RewriteRule ^/$ /homepage.std.html [L]

  今天学习重写影射等内容。

RewriteMap
Syntax: RewriteMap MapName MapType:MapSource
  RewriteMap指令定义一个重写影射(Rewriting Map),在规则的substitution串中,通过影射函数(mapping-functions)来查找关键字(key),并用关键字对应的值来进行来行插入或替换操作。这个查找的对象,可以是各种各样的。
  MapName是影射的名字,将用来通过下列的某种结构来为substitution定义影射函数:
${ MapName : LookupKey }
${ MapName : LookupKey | DefaultValue }
当这些结构之一出现substitution串中时,重写引擎会到mapname影射中查找lookupkey关键字,如果找到了就用返回的值(substvalue)来替换该结构,如果找不到就用defaultvalue来替换该结构,如果没有defaultvalue,就用空串来替换。
  MapType 和mapSource组合有以下几种:

  1. 标准的普通文本(Standard Plain Text)
    MapType: txt, MapSource: Unix文件系统中合法的带有路径的regular file名
    此种情况下,MapSource文件是一个普通的ASCII文本文件,可以含有空行、注释行(以#打头),及以下结构的键值对行(每个键值对一行)。
    MatchingKey SubstValue
    例如:Mapsource文件叫/path/to/file/map.txt,其内容为
##
## map.txt — rewriting map
##
Ralf.S.Engelschall rse # Bastard Operator From Hell
Mr.Joe.Average joe # Mr. Average

在配置文件中则可以这样定义重写映射:

RewriteMap real-to-user txt:/path/to/file/map.txt

 

  • 随机的普通文本(Randomized Plain Text)
    MapType: rnd, MapSource: Unix文件系统中合法的带有路径的regular file名   这种情况与标准普通文本情况很相似,差别只是在SubstValue的格式上:此时的SubstValue由一些用”|”分隔的值组成的串,这个“|”是“或者”的意思。当根据键值找到对应的SubstValue后,mod_rewrite借助“|”将此串分解为一些候选项,然后随机选择一项作为最终的SubstValue的值返回。这听起来有点疯狂或毫无用处,其实这是设计用来在反向代理(reverse proxy)的情况下做负载均衡,用来查找服务器的名字。
    例如:MapSource文件的名字为/path/to/file/map.txt,内容如下:

    ##
    ## map.txt — rewriting map
    ##
    static www1|www2|www3|www4
    dynamic www5|www6

    在配置文件中定义的重写影射为:

    RewriteMap servers rnd:/path/to/file/map.txt
  • Hash File
    MapType: dbm, MapSource: Unix文件系统中合法的带有路径的regular file名   这儿的Mapsource文件是一个二进制的NDBM格式的文件,含有与普通文本格式文件时相同的内容,为了实现快速查找进行了优化处理后以一种特殊的格式来表达。 可以用任何NDBM工具或者用下面的Perl脚本txt2dbm.pl来创建这种格式的文件:

    #!/path/to/bin/perl
    ##
    ## txt2dbm — convert txt map to dbm format
    ## 

    use NDBM_File;
    use Fcntl;

    ($txtmap, $dbmmap) = @ARGV;

    open(TXT, “<$txtmap”) or die “Couldn’t open $txtmap!n”;
    tie (%DB, ‘NDBM_File’, $dbmmap,O_RDWR|O_TRUNC|O_CREAT, 0644) or die “Couldn’t create $dbmmap!n”;

    while () {
    next if (/^s*#/ or /^s*$/);
    $DB{$1} = $2 if (/^s*(S+)s+(S+)/);
    }

    untie %DB;
    close(TXT);

    此脚本的使用方法如下:

    $ perl txt2dbm.pl map.txt map.db
  • Apache内部函数(Internal Function)
    MapType: int, MapSource: Apache内部函数   这时,MapSource是一个Apache内置函数,目前还不能创建自己的内部函数,只能使用Apache已经定义好的:

    • toupper
      将键值转换为大写
    • tolower
      将键值转换为小写
    • escape
      将键值中的特殊字符转换为以16进制表示。
    • unescape
      将键值中16进制表示的特殊字符转换为原来的样子。
  • 外部重写程序(External Rewriting Program)
    MapType: prg, MapSource: Unix文件系统中合法的带有路径的regular file名   这儿的MapSource是一个程序而不是一个影射文件。你可以使用任何语言创建这个程序,但这个程序必须是可执行的(也就是说,要么是二进制码,要么是首行带有”#!/path/to/interpreter”格式的解释器的可执行脚本)。

      这个程序在Apache启动时被运行,然后它就用它的标准输入与标准输出的文件句柄与重写引擎通信(暗指程序能无限时等待标准输入,见下列)。对每个影射函数的查找要求,它把其标准输入中得到的字符串(newline-terminated string?)作为键值,然后通过标准输出返回查找到的值,如果找不到相应的值,则返回以四字符的字符串”NULL“。下面一个简单的例子,实现将键值作为查找结果返回:

    #!/usr/bin/perl
    $| = 1;
    while () {
    # …put here any transformations or lookups…
    print $_;
    }

    注意:

    1. “Keep it simple, stupid” (KISS),因为当规则执行时,一量这个外部程序挂起,整个Apache服务器就挂起了
    2. 避免下述常见错误:缓存了标准输出的I/O。这会导致死循环。这也是为什么上例中有“$|=1”这么一行的原因
    3. 用RewriteLock指令定义一个lockfile,以同步与外部程序的通信。默认情况下并没有这样的同步
  •   RewriteMap可以出现不只一次。每个影射函数用RewriteMap来声明它的影射文件. While you cannot declare a map in per-directory context it is of course possible to use this map in per-directory context.(?)对普通文本和DBM格式的文件,其键值被缓存在Apache内核中,直到影射文件的mtime 变化了或Apache重启动了。此时,可以将规则中的影射函数用于每个请求。
    RewriteBase
    Syntax: RewriteBase URL-path
      RewriteBase明确地指出目录范围内的重写结果的baseURL.RewriteRule指令可以用在目录范围内的配置文件里(.htaccess)。在目录范围的重写实施时,由本地路径信息构成的前缀已经被去掉,重写规则只对剩余的部分进行处理。处理结束后,被去掉的路径信息要自动加上,因为当对一个URL进行替换后,重新引擎需要将它重新插入到服务器的处理流程中去。如果服务器端的URL与文件的物理路径有直接有关系,则每当在.htacess文中定义重写规则时都需要用RewriteBase指URL-path。
    杂项Environment Variables
      mod_rewrite还维护着两个非标准的CGI/SSI环境变量,名为SCRIPT_URL和 SCRIPT_URI,存放着当前资源的逻辑web视图(logical Web-view)。标准的CGI/SSI变量SCRIPT_NAME 和 SCRIPT_FILENAME中存放着当前资源的物理系统视图(physical System-view)。请看例子:

    SCRIPT_NAME=/sw/lib/w3s/tree/global/u/rse/.www/index.html
    SCRIPT_FILENAME=/u/rse/.www/index.html
    SCRIPT_URL=/u/rse/
    SCRIPT_URI=http://en1.engelschall.com/u/rse/