深入剖析PHP输入流 php://input

记得当初在刚刚参加工作时,看到其中一个项目中在接受客户端(iphone app)数据时使用到 php://input ,觉得很好奇为什么要这么做?问了相关同事说是只有这样才能获取到客户端提交过来的数据,于是我信了,没有继续深究下去。

但是在最近我又想到这个 php://input ,打开官方手册这里写的比较准确,如下:

php://input is a read-only stream that allows you to read raw data from the request body. In the case of POST requests, it is preferable to use php://input instead of $HTTP_RAW_POST_DATA as it does not depend on special php.ini directives. Moreover, for those cases where $HTTP_RAW_POST_DATA is not populated by default, it is a potentially less memory intensive alternative to activating always_populate_raw_post_data. php://input is not available with enctype="multipart/form-data".

php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。 而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype="multipart/form-data" 的时候 php://input 是无效的。

阅读剩余部分...

29
May 2016
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

深入剖析 Http 协议

HTTP 顾名思义就是一个协议,但是要从百科上看有点让初学者摸不着头脑,太枯燥,并且也不好结合实际情形去理解。这里用一些小小的事例,争取让大家对HTTP协议有个基本的认知。

阅读剩余部分...

29
May 2016
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

正则表达式(二):Unicode诸问题(上)

关于正则表达式的文档很多,但大部分都是英文的,即便有中文的文档,也翻译或改编自英文文档。在介绍功能时,这样做没有大问题,但真要处理文本,就可能会遇到一些英文开发或应用环境中难得见到的问题。比如中文之类多字节字符的匹配,就是如此。所以,这篇文章专门谈谈正则表达式如何处理多字节字符,更准确地说,是如何处理Unicode编码的文本(为什么只提到Unicode编码,而没有提到其它编码,理由在后面详述)。

首先介绍关于编码的基础知识:

通常来说,英文编码较为统一,往往采用ascii编码或兼容ascii的编码(即编码表的前127位与ascii编码一致,常用的各种编码,包括Unicode编码都是如此)。也就是说,英文字母、阿拉伯数字和英文的各种符号,在不同编码下的表示是一样的,比如字母A,其编码总是41,常见的编码中,英文字符和半角标点符号的编码都等于ascii编码,通常只用一个字节表示。

但是中文的情况则不同,常见的中文编码有GBK(CP936)和Unicode两种,同一个中文字符在不同编码下的值并不相同,比如“发”字,GBK编码的值为b7 a2,用两个字节表示;而Unicode编码的值(也就是代码点,Code Point)为53 d1。如果用UTF-8编码保存,需要3个字节(e5 8f 91);用UTF-16编码保存,需要4个字节(53 d1)。

正因为中文字符需要多个字节来表示,常见的正则表达式的文档就有可能无法覆盖这种情况。比如常见的资料都说,点号『.』可以匹配“除换行符\n之外的任意字符”,但这可能只适用于“单字节字符”,因为点号匹配的其实只是“除换行符\n之外的任意字节”而已。不信,我们可以来试试看(以下例子中,程序均使用UTF-8编码):

Python 2.x 
>>> re.search('^.$', '发') == None # True 
PHP 4.x/5.x 
preg_match('/^.$/', '发') // 0 
Ruby 1.8 
irb(main):001:0> '发' =~ /^.$/ # nil 

之所以会出现这种情况,是因为正则表达式无法正确将多个字节识别为“单个字符”,让点号『.』能正确匹配。不过在Python 3.x、Java、.NET和Ruby 1.9中,字符串默认都是采用Unicode编码,所以不存在上面的问题。如果你使用的是Python 2.x、Ruby 1.8或PHP,也可以显式指定采用Unicode模式。

Python 2.x 
>>> re.search('^.$', u'发') == None #False 
PHP 4.x/5.x 
preg_match('/^.$/u', '发') // 1 
Ruby 1.8 
irb(main):001:0> '发' =~ /^.$/u # 0

如果你细心就会发现,在Python 2.x中,我们指定的字符串使用Unicode编码,而文档里说了,正则表达式也可以指定Unicode模式的;相反,在PHP和Ruby中,我们指定正则表达式使用Unicode编码,而字符串并没有指定。这到底是怎么回事呢?

我们知道,正则表达式的操作可以简要概括为“用正则表达式去匹配字符串”,它涉及两个对象:正则表达式和字符串。对字符串来说,如果没有设定Unicode模式,则多字节字符很可能会拆开为多个单字节字符对待(虽然它们并不是合法的ascii字符),Python 2.x中就是如此,“发”字在没有设定Unicode编码时,变成了3个单字节字符构成的字符串,点号『.』只能匹配其中的单个“字符”。如果显式将正则表达式设定为Unicode字符串(也就是在 u'发' ),则“发”字视为单个字符,点号可以匹配。

而且,如果你在正则表达式的字符组里使用了中文字符,表示正则表达式的字符串,也应该设定为Unicode字符串,否则正则表达式会认为字符组里不是单个字符,而是3个单字节字符:

Python 2.x 
>>> re.search('^[我]$', u'我') == None # True 
>>> re.search(u'^[我]$', u'我') == None # False 

另一方面,在PHP和Ruby中并不存在“Unicode字符串”,所以我们无法修改字符串的属性。但是,设定正则表达式为Unicode模式,正则表达式也可以正确识别字符串中的Unicode字符。所以,如果你用PHP或Ruby的正则表达式处理Unicode字符串,一定不要忘记指定Unicode模式。

点号『.』对Unicode字符的匹配“我”(采用UTF-8编码)

字符串

正则表达式

语言

是否显式指定Unicode模式

可否匹配

^.$

Java

否(无须指定)

可以

^.$

JavaScript

否(无法指定)

由浏览器的实现决定

/^.$/

PHP

不可以

/^.$/u

PHP

可以

/^.$/

Ruby 1.8

不可以

/^.$/u

Ruby 1.8

可以

/^.$/

Ruby 1.9

可以

^.$

.NET

可以

^.$

Python 2.x

不可以

^.$

Python 3

可以


注:PHP和Ruby的正则表达式本身是不包含分隔符(分隔符可以有很多种,常见的是反斜线/)的,但PHP指定Unicode模式必须在后一个分隔符之后写u,所以在这里将分隔符也写出来。

不过,如果你熟悉Python语言,会发现Python也可以指定正则表达式使用Unicode模式,这又是怎么回事呢?

不妨回头仔细想想你读过的文档,正则表达式中的『\d』和『\w』,都是如何解释的?或许你的第一反应是:『\d』等价于『[0-9]』,『\w』等价于『[0-9a-zA-Z_]』。因为有些文档说明了这种等价关系,有些文档却说:『\d』匹配数字字符,『\w』匹配单词字符。然而这只是针对ascii编码的规定,在Unicode编码中,全角数字0、1、2之类,应该也可以算“数字字符”,由『\d』匹配;中文的字符,应该也可以算“单词字符”,由『\w』匹配;同样的道理,中文的全角空格,应该也可以算作“空白字符”,由『\s』匹配。所以,如果你在Python中指定了正则表达式使用,『\d』、『\w』、『\s』就能匹配全角数字、中文字符、全角空格。

Python 2.x(字符均为全角) 
>>> re.search('(?u)^\d$', u'1') == None # True 
>>> re.search('(?u)^\w$', u'发') == None # True 
>>> re.search('(?u)^\s', u' ') == None # True 

老实说,这样的规定有时候确实让人抓狂,假设你希望用正则表达式『\d{6,12}』来验证一个长度在6到12之间的数字字符串,却没留意『\d』能匹配全角数字,验证就不够严密了。

下面的表格列出了常见语言中的匹配规定

语言

『\w』『\d』『\s』的匹配规则

Java

均只能匹配ascii字符

JavaScript

均只能匹配ascii字符

PHP

均只能匹配ascii字符

Ruby 1.8

默认情况下只能匹配ascii字符,Unicode模式只影响『\w』的匹配

Ruby 1.9

均可以识别Unicode字符

.NET

均可以识别Unicode字符

Python 2.x

默认情况下只能匹配ascii字符,Unicode模式下均可以识别Unicode字符

Python 3

默认情况下均可以识别Unicode字符,但可以显式指定ascii


注1:一般来说,单词边界『\b』能匹配的位置是:一端是『\w』,一端不是『\w』(也可以什么都没有),其中『\w』的规定与『\w』一样,但Java中则不是这样,细节比较复杂,这里不展开,有兴趣的读者可以自己试验。

注2:在Python 3中可以在表达式之前添加『(?a)』指定ascii模式。

虽然常见的中文字符编码有GBK和Unicode两种,但如果需要使用正则表达式处理中文,我强烈推荐使用Unicode字符,不仅是因为正则表达式提供了对Unicode的现成支持,而且因为GBK编码可能会有其它问题。比如:我们要求匹配“收”字或者“发”字,很自然会想到使用字符组『[收发]』,这思路是对的,但如果采用GBK编码,正则引擎见到的很可能不是“两个字符构成的字符组”,而是“四个字节构成的字符组”。

使用GBK编码,[收发]的解释『ca d5 b7 a2』

如果我们用『[收发]』来匹配字符“罚”(它的GBK编码是b7 a3),就会产生错误——虽然“罚”字既不等于“收”也不等于“发”,但“罚”和『[收发]』却可以匹配一个字节

GBK编码的情况

罚 b7 a3

[收发] ca d5 b7 a2

Unicode编码的情况(因为Unicode编码能正确识别,无论采用UTF-8还是UTF-16,Unicode字符都会正确转化为Unicode编码点)

罚 7f5a

[收发] 6536 53d1

“罚”的Unicode编码是7f5a,无论如何也不会发生错误匹配。

如果出于某些限制,只能使用GBK编码,也有一个偏方准确保证『[收发]』的匹配,就是把字符组『[收发]』改成多选分支『(收|发)』。此时如果要匹配成功,只能是两个连续的字节ca d5或者b7a2,而“罚”字两个字节为b7 a3,无法匹配。

但这样也会有问题,因为在GBK编码下字符串被当作“字节序列”来对待。比如字符串 “账珍”对应四个字节,d5 ca d5 e4,其中正好出现了“收”字对应的两个字节ca d5,正则表达式就可能在此处匹配成功。

更重要的问题在于排除型字符组的匹配,仍然使用上面的例子,假如我们希望匹配一个“收”和“罚”之外的字符,自然的思路就是使用排除型字符组『[^收发]』。但是通过上面的讲解,我们已经知道,这样“排除”的并不是2个字符,而是4个字节:ca d5 b7 a2。但“罚”字的GBK编码为b7 a3,b7这个字节被“排除”了,所以正则表达式会显示“罚”字不能由『[^收发]』匹配,这完全违背了我们的本意。

总的来说,所以如果使用GBK编码(或者说非Unicode编码),对此类问题基本是无解的。因此,根本的办法还是使用Unicode编码。


转自:http://www.infoq.com/cn/news/2011/02/regular-expressions-unicode/

5
Aug 2015
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

js 数组长度

当 js 数组 key 有一个为数值时,这个数组的长度将是最大 key + 1。如下:

<script type="text/javascript">
var a = []; 
a[10] = 1; 
a[109] = 2; 
console.log(a.length); // 110
a["abc"] = 1; 
console.log(a.length); // 110
</script>

当 js 数组 key 都为字符串时,这个数组的长度的长度为 0。如下:

<script type="text/javascript">
var a = []; 
a["abc"] = 1; 
console.log(a.length); // 0
</script>

如果不知道这个区别,那么我们在使用 for 循环时可能会遇到麻烦,如下:

<script type="text/javascript">
var a = []; 
a[10000000] = "abc";
var start = new Date().getTime(), len = a.length, j=0;
for(var i=0; i<a.length;i++){j++;}
var end = new Date().getTime();
console.log(end - start); // 146
</script>
<script type="text/javascript">
var a = []; 
a[10000000] = "abc";
var start = new Date().getTime(), len = a.length, j=0;
for(var i in a){j++;}
var end = new Date().getTime();
console.log(end - start); // 0
</script>

上面两段分别是检测对于同一个数组,使用 for 与 for in 的执行效率。使用 for 需要 146毫秒,而使用 for in 瞬间完成。这时就体现出 for in 的优势来了。 

还有一点就是对于数值 key 加不加引号都为理解为 数值。

为什么会有这样的疑惑,那就是因为PHP 不是这样的。如下:

<?php
 $a[100] = 123;
 $a["abc"] = 333;

 echo count($a); // 2

对于 Java等强类型语言就不会有这个问题,定义时必须指定长度~~。共勉之~

16
Mar 2015
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

清除代码异味

今天,Venkat Subramaniam 就关于清除代码异味的话题给我们做了一个非常有趣的演讲。下面就是我记录的一些他的话。

  为什么我们需要有质量的代码?

敏捷开发方法是用来应付那些要求代码做大量改动的反馈信息的方法。
如果程序没有用一种好的表达方式来表现,那程序会很难读,难维护,难修改。

  什么是代码异味?

代码异味是一种由写的很差的代码引起的一种有臭味的感觉,一种程序什么地方会有问题的感觉
异味更多的是来自一种直觉,而不是一种有据可查的标准,当你看到有味的代码时你就“感觉”到了
如果你不把异味清除,不久之后你就会习惯这种气味,不再对它有察觉
用任何语言都能写出有异味的代码:即使最简单安全的语言,你也能做出天才才能想出的蠢事:)
我们经常会意识不到自己在写很臭的代码,经常需要外人为我们指出这点
边注:如果你不想刻意去批评某人的程序,不要说“太愚蠢了”,要说“哦,这很有意思…。可有一种更好的方法你知道吗”

  重复的代码

  • 会引起程序里面多个地方相同的错误
  • 印度小伙:每两个月我们都会把这相同的错误修改一次
  • Venkat:你们去掉了重复的代码了吗?
  • 印度小伙:你说的这个方法不错!

  不必要的复杂


  • 程序员本质上讲高效去处理复杂的问题
  • 复杂最恐怖


  异常处理


  • 问:有什么比一个空的异常捕捉代码更糟糕的?
  • try{... } catch (Exception e){}
  • 答:一个带有注释的空异常捕捉代码!
  • try{... } catch (Exception e){// is this required? }
  • Java的异常检查:好还是不好?
  • 如果你不想处理一个异常,就把它传递下去
  • 如果你想捕捉两个异常,使用两个catch代码,不要只写一个而用If条件处理
  •   Switch语句& 按类型的条件判断
  • Switch语句和按类型的条件判断通常可以用多形性来代替


  长方法

  • 你不能在一屏上看到整个方法
  • 这通常意味着一个方法承担这多重任务
  • 难于调试
  • 不可测试
  • 难于重用-> 导致程序员从方法的其它地方拷贝粘贴出重复的代码
  • 复杂的条件语句-> 挑战大脑的逻辑分析能力
  • 方法长度:组织归纳水平比控制代码行数更重要

  方法组成模式

  • 方法里的所有语句都必须处在同一个归纳层次上

  无用的注释

  • 让代码自我表白
  • 标注为什么这样,而不是如何这样
  • 对方法表现进行描述等于重复表现
  • 这样的注释等于重复写一遍代码
  • i += 1 //递增
  • 长方法里用来描述这个方法有不同的功用的注释
  • 把里面的功能片段提取成小方法& 删除注释
  • IDE排泄物:IDE自动产生的注释空白占位符
  • 糟糕的注释通常产生于TDD*
  • *(TDD:Threat driven development,恐吓驱动开发)——你应该为方法的表象写注释,你应该为长方法写注释,等
  • 产品里的注释:
  • //上帝保佑,我实在不知道这是什么意思

  变量名称

  • 使用能表意的名称
  • 不要用单个字母做名称
  • 也不要使用太长的名称

  继承

  • 继承更多的是被滥用了
  • 组合通常优于继承
  • 在一对一关系中使用继承,满足Liskov替换原则
  • 不要用继承来实现方法重用
  • 重用方法时,委托是个更好的选择

  粘手的语言

  • 这种语言更容易导致犯错误

  最臭的代码

  • 冗长的类
  • 重复的代码
  • 淘汰的方法
  • 不必要的塑型(cast)
  • 过度使用设计模式

  代码除味

  • 代码复查!
  • 写出之后尽快进行
  • 要增量进行
  • 要复查测试用例
  • 可使用结对编程
  • 但要保持结对伙伴的经常变动,否则你会习惯你的气味,不再会有察觉
  • 结对伙伴一、两天调换一次

  一些设计原则

  1. 高聚合
  2. 低耦合
  3. Demeter定律 [不要告诉我,我会通知你]
  4. Liskov替换原则
  5. 先让它跑起来,再让它无误,再让它快速
  6. 开发/闭合原则
  7. 反向依赖
  8. 单一责任原则

  一些参考书籍

  1. 代码整洁之道(Clean Code)
  2. 代码大全(Code Complete) 2
  3. 程序员修炼之道(The Pragmatic Programmer)
  4. 敏捷开发修炼之道(Practices of an Agile Developer)
  5. Smalltalk Best Practice Patterns
  6. 实现模式(Implementation Patterns) (from @protoiyer)

  问和答

  1. 关于使用代码检测工具,例如PMD:这样的工具非常的有用,它能让你捕捉到很直接的问题,使你的代码复查工作专注于高层面的设计原则问题
  2. 关于IDE上附加的工具:不要自己去运行它们。让这些工具在后台自动的运行(或智能化)
  3. 动态语言里需要重构吗:动态语言里没有太多的自动重构工具,但程序员仍然应该手动的重构
  4. 关于动态语言的设计模式:每种语言都有自己的模式和特色。例如:smalltalk的execute around method模式
  5. 关于掌握多种语言
  6. 你应该知道处理一个问题的多种范式,多种风格和多种方式
  7. 一种语言中学到的特色方法应用到其它语言里
  8. 知道各种不同方式的各自风险
  9. 关于编程语言趋势:对函数性编程,移动设备编程兴趣浓厚
  10. 关于著书:长时间的思考书中的各项主题,多做这方面话题的讨论,吸取精华。当开始动手去写时,已经胸有成竹,2周内把书写成
  11. 关于思考文献:思考文献很有用,但你也要多看看批评性的思考性文章,它们是关于你如何去思考的(double loop learning?)
  12. 关于学习:在用户组里跟其它人合作,交流,讨论。你并不能学到所有的东西,但要努力缩小自己的“你不知道你不知道的东西”,让它成为“你知道你不知道的”
27
Feb 2015
AUTHOR WiFeng
CATEGORY Web,Jotting
COMMENTS No Comments

Flock,你知多少?

PHP 中的 flock 函数可以让你的站点挂了,你信吗?不可能,不就是一个锁文件嘛。

我们所使用的是 Discuz! X3.2 版本,其中这个helper_log::writelog 函数中使用了 @flock($fp, 2); 此时如果你的日志目录(data/log/)使用了NFS(网络文件系统),这将在某些情况下是有问题的。

阅读剩余部分...

2
Feb 2015
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

artTemplate模板引擎不支持全局函数的解决方案

这几天在使用artTemplate模板引擎,但是发现在定义模板中不能使用全局函数,有点不可思议。经过研读相关文档与源码发现:如果想在模板中使用全局函数,需要在 template.helper 中重新定义一次,自我感觉有些设计缺陷。 

不过找到了修复这个缺陷的解决方案:

第一:修改源代码

// ============================
} else if (helpers[name]) {
	value = "$helpers." + name;
// 全局函数,这里只处理函数,不处理变量,因为有可能全局变量与data中的变量冲突,导致结果出错
} else if (typeof window[name] == "function") {
	// value = "window." + name;
	value = '';
} else {
	value = "$data." + name;
}
// 全局函数不需要重新声明
if (value) {
	headerCode += name + "=" + value + ",";
}
uniq[name] = true;
// ============================

第二:在data参数中定义要使用的函数,这个方法与上面提到的template.helper中重新定义是同一种思路

// ============================
var data = {},html;
function myfun(isAdmin,show){
	return (isAdmin ? "ok" : "no") + show;
}
data.myfun = window.myfun;
html = template('test', data);
document.getElementById('content').innerHTML = html;
// ============================

参考来自:
https://github.com/aui/artTemplate/
https://github.com/aui/artTemplate/wiki/syntax:native

同时也希望作者把这个问题得到更加完善的优化~~

12
Oct 2014
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

简析Javascript变量作用域链

Javascript语言与服务端编程语言有一些不同,比如事件驱动、函数内定义新函数、函数即类、作用域链等等,这些特性促使这门语言相当奇葩。这一小节就来看看这个作用域链。

彻底搞明白作用域链原理,可以让你的程序少一些bug,也会节省一些开发调试时间。下面开始一段简单的代码:

<html>
<head></head>

<body>
<input id="test" type="text" />
</body>

<script>
function $(id) {
	return document.getElementById(id);
}

function fun1() {
	var a= 1;
	function inner() {
	    a++;
	    alert(a);
	}
	$("test").onclick = inner;
}
fun1(); 
</script>

</html>

这是一个非常容易读懂的事件绑定代码,先推测一下每次点击 input 会提示什么内容,然后拿这代码亲自执行一遍,看看你的推测对不对。如果是对,非常恭喜你,如果不对,那就接着往下看吧。

函数 inner 中的 a 没有使用 var 定义,所以执行时会使用当前函数外层的作用域,逐层向外查找,此时发现有定义 var a= 1,就停止查找,此时变量 a  的作用域就是fun1函数内部。此时变量 a 虽然在 函数 fun1 中,但是不会被垃圾回收机制回收掉,这是为什么,请关注 Javascript 变量生命周期。

相关概念:
函数运行在其定义的作用域,而不是执行的作用域。这个与作用域链是一个意思,只是表达方式不同。

24
Mar 2014
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

【转载】MySQL 数据库性能优化之缓存参数优化

在平时被问及最多的问题就是关于 MySQL 数据库性能优化方面的问题,所以最近打算写一个MySQL数据库性能优化方面的系列文章,希望对初中级 MySQL DBA 以及其他对 MySQL 性能优化感兴趣的朋友们有所帮助。

数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作。而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级。所以,要优化数据库,首先第一步需要优化的就是 IO,尽可能将磁盘IO转化为内存IO。本文先从 MySQL 数据库IO相关参数(缓存参数)的角度来看看可以通过哪些参数进行IO优化:

阅读剩余部分...

19
Jan 2014
AUTHOR WiFeng
CATEGORY Web,DataX
COMMENTS No Comments

如何使用C++开发PHP扩展(下)

上文中介绍了使用C++编写简单PHP扩展的方法,但是更多的情况是业务中已经有独立的 api 库,形式为 libxxx.a / libxxx.so,PHP程序中需要调用这些 api,所以这时就要编写PHP扩展来实现。这时是使用静态库 libxxx.a ,还是使用 libxxx.so 呢 ?常见的做法是使用静态库 libxxx.a ,下面一步一步介绍:

阅读剩余部分...

18
Jan 2014
AUTHOR WiFeng
CATEGORY C/C++,Web
COMMENTS 5 Comments