apache RewriteMap MapSource自定义规则使用手记 10.1期间,一位朋友问我一个apache的 Rewrite规则中一个高级语法RewriteMap的用法问题。其想要实现的是这个功能,有个多用户的blog,用户访问的时候,是用三级域名访问的。比如http://cfc4n1.blog.cnxct.com,http://cfc4n2.blog.cnxct.com这种三级域名。在服务器上,是对三级域名做泛解析。每个三级域名都生成了一个静态的html主页文件。由于用户数量较多,linux ext硬盘格式上同一目录文件太多,检索文件的速度会有折扣,遂将文件打散到不同的目录下。打散方式是以用户名【三级域名中的cfc4n1,cfc4n2等】的MD5值的每隔两位作为一个目录。MD5的默认长度是32位,每隔两位分一次目录的话,那就有16级目录。每级目录的目录名是2个字符,每个字符的有16种可能【0-9a-f】,那么每级目录的目录数为256个目录,16级的话就有4096个目录。每个目录存1000个文件的话,可以存放4096000个文件,这样做,即可以把文件均匀打散到各个小目录中,同时,每个目录下的文件数又不是很多。当用户访问的时候,取目录里的用户名,计算MD5hash,做字符分割,重写到对应的目录下的文件中,如果文件不存在,则重写到生成这个文件的动态页面中。 到apache手册里找了下RewriteMap的用法 RewriteMap MapName MapType:MapSource 遂顺手在.htaccess里这么写了 然后访问一个三级域名试试。结果提示500 http 错误。到apache日志里看到如下 /var/www/.htaccess: RewriteMap not allowed here 搜了半天,不知道是什么错误,只好再次看手册,这时候,才发现rewritemap的作用域却是server config, virtual host,真汗了一下。自己没认真看手册。 改到virtual host里之后,重启apache,结果,还是http 500。再到错误日志里查个究竟。里面记录的确实(13)Permission denied: mod_rewrite: could not start RewriteMap program /var/www/1.php。呃,权限,权限。。赶紧chmod了一下。再次启动,却提示404。。。 /0a/c1/..../...html那种MD5字符串切割之后的文件找不到。但目录里确实是存在的啊。又到日志里查看,原来却是/0a/c1/...../...html\r 文件无法找到了。为什么地址后面多个\r呢?打开MapSource的脚本文件,才看到里面PHP操作流的结束字符里是“\r\n”了,去掉\r 才可以。 总结一下使用APACHE URL REwrite的RewriteMap方法要注意以下几点: 作用域-server config, virtual host,其他配置里无效。 自定义规则MapSource中流的结束符要跟操作系统符合,linux的要用“\n”,同时,切记在win平台编辑脚本传到linux上的时候,文件换行符要用linux格式的,不然,同样会出现问题。 要给apache赋予对脚本的读权限。 apache会在启动的时候,将自定义规则的脚本读取到内存中,之后,再次修改脚本时,不会立刻生效,需要重启 apache apache 的error.log中会记录[warn] mod_rewrite: Running external rewrite maps without defining a RewriteLock is DANGEROUS!这样的错误日志,在apache2.conf【我的系统是ubuntu,其他linux在httpd.conf中】中添加RewriteLock /etc/apache2/script/cfc.lock来指定RewriteLock的文件位置。记得给apache对script目录下有读写权限。 自定义脚本的代码格式如下: 2010-10-14 0 0
PHP中正则表达式对UNICODE字符码的匹配 酷暑难耐,又在家宅一整天。天气热或许是借口,尽管不热,我也喜欢宅在家。晚上看新三国83集(插一句,最近世界杯很火,可是我不看世界杯。来鄙视我吧),一直看到10点半,突然觉得肚子痛,赶紧直奔厕所。如厕之后,觉得精神抖擞,容光焕发,年轻了二十多岁,打开QQ,看到N多条消息,其中有条是生命如蓝同学留的。打开一看,是PPC的链接,突然觉得我好久没登陆PPC了,想当年........算了,看链接吧。 打开链接,看到标题是“请教PHP 一个正则匹配的问题”,又是正则表达式,好吧,看下,谁让俺比较喜欢鼓捣正则呢。下面开始正题。 网友ainiaa的问题是 PHP代码如下 为什么打印的结果会是: otherStr: ! #$% & {}| '"你好啊我们 麻烦问下其中正则表达式 /[chr(128)-chr(256)]+/is 代表什么意思? 如果/[chr(128)-chr(256)]+/is 指的是ascii码在128到256的字符,为什么a-zA-Z这样的字符也被替换掉了,他们的ascii码是小于127的。 最令人郁闷的是为什么ascii码同在0-127区间"#","$","%","&", "!"," {","}","|"," '","确没有被替换掉???? 更令人感觉神奇的是 如果把正则表达式修改为"/[chr(128)-chr(256)]+/s"的话,输出的结果就变成了: otherStr: defg ijklmnopq stuvwxyz ! #$% & {}| '"你好啊我们 只是把正则表达式中的符号‘i’给去掉,结果缺失这样的。 完全的令我理解不了。 不知各位 有何见解???? 另附ascii 码 对照表 (这个ASCII码表的图我就不贴了) 回帖中,有个网友说没解析chr(128)这些,并给出了新的解决方法。首先说下此网友回答的是正确的,先不评论他是否“知其然,且知其所以然”,这位网友没有给出错误的原因。 CFC4N来回答一下这位网友: PHP的正则的preg_match函数用的是PCRE正则引擎,这位网友的代码中,PCRE引擎处理的正则表达式为【/[chr(128)-chr(256)]+/is】,后面的is是什么呢? 在PHP的正则里,边界字符后面的叫模式修饰符。它会告诉引擎如何解析,处理正则。其中i修饰符表示不区分大小写。s表示“点号通配模式”,用来让正则里的元字符点号【.】可以匹配换行符,这个修饰符仅对点号【.】起作用。在这位网友的问题中,修饰符s并不起作用的。 查找原因: 我们在来分析一下这个网友写的正则表达式【[chr(128)-chr(256)]+】,正则表达式的PCRE引擎是如何解释这个正则的呢?首先,我们要知道,在正则表达式中,中括号【[]】表示字符组,字符组中除了连接符【-】只外,都不是元字符,也就是说,都是普通字符,当然,如果连字符出现在第一个,或者不是标识两个字符之间范围的,都是普通的字符横杠“-”罢了。这里的chr(128)只是标识ASCII码为128(确切的说,ASCII码只是0-127个,128到其他的,应该不叫ASCII码了。),但是在正则里,他仍然代表【c、h、r、(、1、2、8、)】(顿号不是,只是区分易读的)这八个字符罢了。这个正则里的连接字符,是哪些范围呢?很明显,这里的连接字符的范围是【)-c】,“)”ASCII码为0x29,也就是十进制的41;“c”的ASCII码为0x63,也就是十进制的99,那么,他这个连接字符的范围就是ASCII 41(chr(41))到ASCII 99(chr(99))之间的字符。也就是说,这位网友的正则的范围是【[hr)-c(]】,就是chr(41)到chr(99)外加hr这两个字母和前面的“(”。 网友第一次测试的时候,有修饰符i,意思就是说,不区分大小写,那么在chr(41)到chr(99)之间的字符,以及这些字符如果有大小写,则包括他们的大小写都符合匹配。都会被替换成空。其第二次测试的时候,去掉了修饰符i,进行了不区分大小写的匹配,由于其范围只到c,但突然,再除了小写字母的“h”、“r”,所以,测试结果会多出“defgijklmnopqstuvwxyz”。所以,他的结果出现了这些差别。 网友的表达式等同于如下图所示 解决办法: 错误的原因找出来了,那么,解决的办法呢? 我们先来看看这位网友的需求,他的需求是将unicode(ASCII只是0-127位的,128之后的,应该叫UNICODE码)的chr(128)到chr(255)之间的字符匹配,替换为空罢了。正则表达式里,对十六进制的字符匹配的表示方式有两种,【\u】和【\x{}】,前者只能表示【\u】后面4位的十六进制数值,而后者【\x{}】则可以表示任意多的十六进制位数(写在大括号中)。 那么,这个正则表达式该如何写???? 网友的目的是chr(128)到chr(255),那么就是【[\u0080-\u00FF]】或者【[\x{0080}-\x{00FF}]】。 其目的是匹配下图中的红框内字符 提醒一下,PHP里正则匹配unicode字符时,需要使用u修饰符。 根据网友需求,更改正则之后的PHP代码如下: 其运行结果是仍然输出那段字符串,为什么呢?因为哪些字符串都不在chr(128)到chr(255)的范围之内。 (测试时,注意文件编码为UTF-8) 以上为鄙人愚见,欢迎批评指正。 2010-07-04 +2 6 0
PHP正则表达式的效率:回溯与固化分组 上文中,我们聊到了一点关于PHP中(NFA PCRE)正则表达式匹配优先量词,忽略优先量词的匹配原理了。那么上文留下的问题,您的答案是什么呢? 先来看下问题。 字符串 正则表达式为 这三个正则,分别会造成几次回溯呢?? 答案: 对于第一种贪婪匹配的匹配规则,回溯的9次是正则【】对字符串“”匹配时,构成的回溯,回溯的次数,恰好是字符串的长度。 第二种非贪婪匹配规则,回溯5次,是正则【.+?】对字符串“123456”匹配时构成的回溯。回溯的次数,为字符串长度减去最小次数。也就是6-1=5次。如果正则表达式为【.*?】那么,回溯次数就是6次了。 第三种正则是零宽断言,或者叫环视。(暂且不说。) 在NFA正则引擎中,回溯是他的灵魂,所以,不管是贪婪,非贪婪,环视等写法中肯定会有回溯的出现的,这个我们无法避免(用词不太准确),但是,我们可以减少回溯的次数,或者保护其中一部分匹配的规则不进行回溯。 对于上篇BLOG上提到的鸟哥谈到一个非贪婪引起的大量回溯问题,大家可以知道,回溯,确实是浪费资源的罪魁祸首,那么,我们能否不让其回溯呢? 答案是肯定的,NFA引擎中,有个概念,叫固化分组。引用一下书上的概念 具体来说,使用「(?>…)」的匹配与正常的匹配并无差别,但是如果匹配进行到此结构之后(也就是,进行到闭括号之后),那么此结构体中的所有备用状态都会被放弃。也就是说,在固化分组匹配结束时,它已经匹配的文本已经固化为一个单元,只能作为整体而保留或放弃。括号内的子表达式中未尝试过的备用状态都不复存在了,所以回溯永远也不能选择其中的状态(至少是,当此结构匹配完成时,“锁定(locked in)”在其中的状态)。 那么,固化分组到底有什么用处呢?我们来举个例子。(找不到合适的例子,俺只好借用一下书上的例子了) 比如要处理一批数据,原来格式为123.456,后来因为浮点数显示问题,部分数据格式变为123.456000000789这种,,要求做到只保留小数点后面2-3位,但是,最后一位不能为0,这个正则如何写呢?(下面直接考虑小数点后面的数字),写出正则之后,我们还要用这个正则去匹配数据,把原来的数据替换成匹配的结果。 首先,我们可以立刻写出这样的正则【\.\d\d[1-9]?\d*】,PHP代码为 很明显,这种写法,对于部分数据格式为123.456的这种格式,白白的处理了一遍,为了提高效率,我们还要对这个正则进行处理。从123.456这个字符串跟其他的比较一下,我们发现,是疑问123.456这个数据后面没数字了,所以,白白处理一遍。那好办,我们对这个正则改造一下,把后面的量词*改成+,这样对于123.45 小数点后面1,2位数字的,不会去白白处理,而且,对三位以上数字的,处理正常。其PHP代码为 好了,这个正则真的没问题吗??确定吗?上篇博文,我们了解了匹配原理,那么,我们也分析一下这个正则的匹配过程吧。 字符串"123.456",正则表达式为【\.(\d\d[1-9]?)\d+】,我们来看下 首先(小数点前123不说了),【\.】匹配".",匹配成功,把控制权给下一个【\d】,【\d】匹配“4”成功,把控制权给第二个【\d】,这个【\d】匹配“5”成功,然后,把控制权给了【[1-9]?】,由于量词是【?】,正则表达式遵循“量词优先匹配”,而且,此处是【?】,还会留下一个回溯点。然后匹配"6"成功,然后把控制权给【\d+】,【\d+】发现后面没字符了,最遵循“后进先出”规则,回到上一个回溯点,进行匹配,这时,【[1-9]?】会交还出其匹配的字符“6”,【[1-9]?】匹配“6”成功。匹配完成了。大家发现【(\d\d[1-9]?)】匹配的结果确是"45",并不是我们想要的“456”,“6”被【\d+】匹配去了。那么,我们该如何办呢? 能否让【[1-9]?】匹配一旦成功,不进行回溯呢?这就用到了我们上面说的"固化分组", PHP(preg_replace函数)中使用的正则引擎支持固化分组,我们根据固化分组的写法,可以把代码改成如下方式 改成这样的话,那字符串“123.456“是不符合要求,不会被匹配的。那我们就可以实现我们的要求了。 从上面的例子中,知道了固化分组的作用,那么对于鸟哥BLOG上写的那个非贪婪的回溯问题,我们能否也对其改造,使得其不回溯呢? 先看下鸟哥给的答案 鸟哥写的很精悍。排除“ 2010-06-21 +10 14 0