注:此文章会持续更新
2012 来的几天关于Hash攻击的文章不断,基本语言级别的都收到影响。
看了下 PHP 相关 patch,基本就是对 POST 的 key 数量做一个限制,其他提供的 patch 差不多也是如此。
刚好可以尝试一下 nginx-lua 模块,这里简单贴一些代码,编译步骤就略去。
本文只是根据 POST 参数个数进行简单校验的测试。
这里大概有几个步骤:
加载 conf/post-limit.lua,文件内容在下一段
access_by_lua_file 'conf/post-limit.lua';
conf/post-limit.lua 文件内容:
ngx.req.read_body()
local method = ngx.var.request_method
local max_size = 2 -- 参数最多个数,这里测试用,2个
if method == 'POST' then -- 只过滤 POST 请求
local data = ngx.req.get_body_data()
if data then
local count = 0
local i = 0
while true do
if count > max_size then -- 大于2次,重定向错误页面
ngx.redirect('/post-max-count')
end
i = string.find(data, '&', i+1) -- 查找 & 字符
if i == nil then break end
count = count + 1
end
else
ngx.redirect('/post-error')
end
end
原先 ngx.req.get_post_args 函数来判断,发现也收到 hash 的影响,最后采取原始的循环字符串版本。
完整 nginx 配置片段:
location /test {
access_by_lua_file 'conf/post-limit.lua';
root html;
}
reload nginx 即可。可以这样来访问测试:
$ curl --data "a=1&a=11&b=2" http://localhost/test/1.html
返回 405,可以正常 GET 页面内容。
$ curl --data "a=1&a=11&b=2&c=1" http://localhost/test/1.html
返回 302,重定向到 /post-max-error 错误。
想起之前用 ModPerl 重写了 Apache 的 TransHandler/AccessHandler,跟这个比较类似,加一些过滤器。
感谢 @agentzh 建议,需要注意一下,在 Nginx 配置中 client_max_body_size 和 client_body_buffer_size 需要设为相同大小。
END
想了很久,很难给这篇跟技术无关的博客起一个满意的标题。刚好想起以前看过的一个视频《The Rose》,姑且凑合用吧。
其实用信乐团的《海阔天空》来当标题或许更为合适,歌词依旧如此犀利和伤感:
冷漠的人 谢谢你们曾经看轻我
让我不低头更精彩的活
…
海阔天空 狂风暴雨以后
转过头 对旧心酸一笑而过
随着时间的消逝,有些东西始终只是存在自己的回忆里,不管是美好还是伤感,始终那些都是值得。
或许,曾经有点苦涩,有点埋怨。而如今,回想起来,始终有着那么一丝丝的回味和惋惜。
清晰的记得,那是2005年的国庆前,穿越一个城市,冒着大雨,只是为了那一份为之着迷和幻想的工作。
在去南京之前,对于写程序而已,大部分时间只是在纸上模拟,代码只是流淌在自己的记忆体中。很顺利的拿到了那份工作,一呆就是四年半,接近五年的时间。
不知道五年对每个人意味着什么,得到或者失去,其实都不算什么,只是人生的一些经历而已。回忆起上上次五年,应该是自己上师范那会,十八到二十三岁,应该算是人生最年轻的阶段,得失?我不知道。而上次五年,或许算起来应该是人生中起步积累的阶段吧。
仔细想了想,印象里有很多记忆深刻的场景。比如,那个夏天,没有空调,没有风扇,几个男人,赤膊在公司加班写代码,解决线上服务器问题。那个冬天,喝完白酒,醉醺醺的去公司写了小半夜代码,早上起来检查竟然没有任何的错误。种种画面犹如电影镜头般清晰。
或许这些,都不太重要。只是我们自己在乎的太多,太把自己当回事。或许?只能是或许。
然后,一个个老员工都选择了离开。离开的原因有人会觉得是为了跳槽,是为了选择自己发展的空间,扪心自问,真是这样的吗?只是觉得那么的无奈,那么的难以言语。
聚会的时候还会经常聊起那些过去的事,过去的人。其实大家心里都清楚,为什么离开。
虽然离开这个做法,在现在看来是多么简单,多么不需要理由的事情。
但是,面对着自己陪伴成长,一步步发展起来的那份工作,谁舍得,谁愿意呢。
过去的对与错,已经不再重要。当然,我们什么都不是,真的什么都不是。
嗯,
祝福那些已经离开和即将离开的同事们,终点不是结束,只是下一个起点,一切都会好起来的。
END
经常会碰到一些返回 JSON 格式的应用,默认都是一大坨字一起显示,完全是虐待自己的眼睛。
比如以下JSON:
mac:~ smallfish$ curl http://127.0.0.1:8108/postgres
[{"datname":"postgres","datdba":"10","encoding":6,"datcollate":"zh_CN.UTF-8","datctype":"zh_CN.UTF-8",
"datistemplate":false,"datallowconn":true,"datconnlimit":-1,"datlastsysoid":"12172","datfrozenxid":"985",
"dattablespace":"1663","datacl":null},{"datname":"smallfish","datdba":"16384","encoding":6,
"datcollate":"zh_CN.UTF-8","datctype":"zh_CN.UTF-8","datistemplate":false,"datallowconn":true,
"datconnlimit":-1,"datlastsysoid":"12172","datfrozenxid":"985","dattablespace":"1663","datacl":null}]
顺手 Google 了下,发现了两种浏览方式:命令行查看和浏览器查看。
一、浏览器查看方式,需要安装 Chrome 插件:JSONView。

二、命令行查看,需要安装 Python。
mac:~ smallfish$ curl http://127.0.0.1:8108/postgres | python -mjson.tool

是不是美观很多了?
END
上一篇《Nginx第三方模块》涉及了数据库、Memcached以及Lua的扩展,但是相对于Web开发是不是还缺点什么呢?答案是回话(Session)模块。
这里还是需要感谢一下@agentzh,已经封装好了encrypted-session模块。模块依赖ngx_devel_kit包。模块地址如下:
编译很简单,类似如下:
./configure --prefix=/opt/nginx \
--add-module=../ngx_devel_kit \
--add-module=../encrypted-session-nginx-module
重新编译Nginx二进制,Nginx需要quit再启动。而普通配置更新则reload即可:
1. kill -HUP `cat /path/nginx/logs/nginx.pid`
2. /path/nginx/sbin/nginx -s reload
在测试之前,需要配置encrypted_session_key(长度32位)和encrypted_session_iv(长度16位)。
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
encrypted_session_iv "1234567812345678";
encrypted_session_expires 5; # 默认过期时间是1d(一天)
话不到多说,直接来读写示例:
- 写入session,测试session名为name,值是smallfish。
location /session-write {
set $name 'smallfish';
set_encrypt_session $session_name $name;
set_encode_base32 $session_name;
add_header "Set-Cookie" "name=$session_name";
echo "write name: $session_name";
}
- 读取session,Nginx读取Cookie方式为:$cookie_xxx。xxx为cookie的名称。
location /session-read {
set_decode_base32 $session_name $cookie_name;
set_decrypt_session $name $session_name;
echo "read name: $name";
}
END
最近试用了几个@agentzh写的第三方Nginx模块,甚为愉悦,没想到在Nginx可以玩很多技巧和扩展,分享一下。
本文尝试的几个模块大概分为:
详细模块地址分别为:
为了省事这里一股脑把上面的module全部下载好,一起编译。PS:如果更懒惰的可以尝试下openresty项目,它帮你打包好Nginx和一堆扩展模块,得感谢@agentzh。
这里编译和drizzle和lua模块,在编译Nginx之前需要设置一下这两个库的LIB和INCLUDE文件地址:
-- lua --
export LUA_LIB=/path/to/lua/lib
export LUA_INC=/path/to/lua/include
-- drizzle --
export LIBDRIZZLE_INC=/opt/drizzle/include/libdrizzle-1.0
export LIBDRIZZLE_LIB=/opt/drizzle/lib
Nginx编译选项如下,请注意先后顺序:
./configure --prefix=/opt/nginx \
--with-pcre=../pcre \
--add-module=../ngx_devel_kit \
--add-module=../set-misc-nginx-module \
--add-module=../memc-nginx-module \
--add-module=../echo-nginx-module \
--add-module=../lua-nginx-module \
--add-module=../srcache-nginx-module \
--add-module=../drizzle-nginx-module \
--add-module=../rds-json-nginx-module
重新编译Nginx二进制,Nginx需要quit再启动。而普通配置更新则reload即可:
1. kill -HUP `cat /path/nginx/logs/nginx.pid`
2. /path/nginx/sbin/nginx -s reload
PS:详细的模块编译可以参考各自模块的文档说明。
好吧,假设到这里已经安装和编译好Nginx以及一堆第三方扩展。下面开始尝试上面那些有趣的模块~
场景1:Helloworld
程序里helloworld很简单,nginx的hello如何实现呢?
1. 最简单的helloworld
location /hello1 {
echo "hello 1111!";
}
2. 异步请求其他echo请求
location /hello2 {
echo "hello 2222!";
echo_location_async /hello1;
}
3. 输出GET请求参数,假设参数名是name,这里并对name参数进行解码
location /hello3 {
set_unescape_uri $name $arg_name;
set_if_empty $name "None";
echo "hello, $name!";
}
是不是很帅气有趣。例2中的subrequest是完全non-blocking的。
echo模块还有timer扩展,页面输出加载时间也是小case了。
场景2:Memcached HTTP 接口
想当年哥为了兼容不同语言的api,自己封装了memcached操作,真是挺苦逼的,当时还非常羡慕tokyotyrant的http接口来着,现在用memc模块效果一样,还有upstream和keepalive功能哦~
location /memcached {
set $memc_cmd $arg_cmd;
set $memc_key $arg_key;
set $memc_value $arg_val;
set $memc_exptime $arg_exptime;
memc_pass 127.0.0.1:11211;
}
URL访问类似 http://host:port/memcached?cmd=aaa&key=bbb&val=ccc
还需要自己封装么?完全的基于url编程了,不再考虑各自语言的memcached api,cluster也简化了。
场景3:数据库查询
这里用了libdrizzle模块,兼容MySQL、Drizzle、SQLite数据库。有时候为了一些查询或者接口,还需要用语言来封装一些数据库查询操作,现在直接通过drizzle模块可以很轻松的做到~~
# 添加MySQL配置
upstream mysql {
drizzle_server 127.0.0.1:3306 dbname=test user=smallfish password=123 protocol=mysql;
}
# 通过url匹配出name,并编码防止注入,最后以json格式输出结果。
location ~ '^/mysql/(.*)' {
set $name $1;
set_quote_sql_str $quote_name $name;
set $sql "SELECT * FROM users WHERE name=$quote_name";
drizzle_query $sql;
drizzle_pass mysql;
rds_json on;
}
# 查看MySQL服务状态,这个很实用哦。
location /mysql-status {
drizzle_status;
}
场景4:lua扩展
内嵌lua语言,超级牛力哦~~~想干啥都可以了,而且还可以充分发挥lua coroutine的风骚,淘宝量子统计完全是lua扩展。
location /lua1 {
default_type 'text/plain';
content_by_lua 'ngx.say("hello, lua")';
}
# 请求另外的url
location /lua2 {
content_by_lua '
local res = ngx.location.capture("/hello1")
ngx.say("data: " .. res.body)
';
}
Lua支持的选项很多,具体可参考官网WIKI文档。
嗯,这里尝试玩了几个模块,详细的例子可以参考各自的模块文档,里面都有详细的说明和选项配置。
可以看出和传统编程有些微不同之处,完全是基于URL的HTTP接口,比传统的方式更为简单,更为清晰。还有non-blocking的实现,更见轻便。
END
auto-xhprof 项目地址: https://github.com/smallfish/auto-xhprof
最近需要对一些PHP应用和底层函数进行一些排错和性能方面的分析,不由得想起xhprof这个小强的利器。
当然也可以按照官方的示例来监控应用或者页面的数据,使用起来还是有点不爽。比如想监控所有访问超过2秒的页面性能情况,或者自动打开/关闭分析,请求url、响应时间等。
随手基于xhprof写了一个扩展小工具。
主要思路如下:
通过修改php.ini中的auto_prepend_file可以预加载auto-xhprof.php,自动打开xhprof功能。
;php.ini
auto_prepend_file = '/path/prepend.php'
; <?php
; include_once '/path/auto-xhprof.php';
; ?>
主要功能点有如下:
- 参数配置是否自动开启xhprof。
- 参数配置超时的阀值,比如2秒。
- 保存分析后的数据到MySQL中,供集中统一分析。
- 数据内容含:请求的URL、相应时间。
- 支持gearman异步保存数据。
- 提供封装的xhprof_start/xhprof_end对部分程序进行手动分析。
- 自动记录错误信息
图一(列表显示):

图二(部分函数显示):

源码文件说明:
auto-xhprof.php 全局加载文件。
auto-xhprof-config.php 全局配置文件,设置MySQL数据库和参数等。
gearman-worker.php gearman后台处理worker进程。
web/ web显示目录,xhprof列表页面和原xhprof展示部分
xhprof_lib/ xhprof库文件。
--------- 扩展安装步骤 ---------
% cd <xhprof_source_directory>/extension/
% phpize
% ./configure --with-php-config=<path to php-config>
% make
% make install
- 修改php.ini中extension,以支持xhprof扩展
[xhprof]
extension=xhprof.so
xhprof.output_dir=<directory> ; 需要有写入权限,可以写/tmp,或者不设置
- 从github上下载auto-xhprof,并配置。
1. 非web目录拷贝到系统php include目录下,比如:/usr/include。
2. web目录拷贝到可访问的应用目录下。比如:/var/www/app1/web,前端访问为:http://app1/web/
3. 修改auto-xhprof-config.php中相应的数据库配置,超时阀值等参数。
到这里,基本安装和配置结束了,访问web目录可以看到xhprof列表信息。
另外,如果支持gearman扩展的话,需要在php.ini中启动该扩展,并在auto-xhprof-config.php中配置:
define('__XHPROF_GERAMAN_SERVERS', '127.0.0.1:4730;127.0.0.1:4730'); // gearman 服务器定义
% gearmand -vvv -q libdrizzle --libdrizzle-host=127.0.0.1\
--libdrizzle-user=root --libdrizzle-password=123456 --libdrizzle-db=gearman\
--libdrizzle-table=queue --libdrizzle-mysql
% php gearman-worker.php
经常会碰到要写一些守护进程,简单做法放入后台:
shell> nohup python xxx.py &
偶尔这么做还可以接受,如果一堆这样的呢?
当然还有一个问题,就是各种服务,对应的命令或者路径都不太一致,比如Apache、MySQL或者其他自行编译的工具。
如果可以统一管理这些应用,是不是很哈皮?
按照惯例现Google一把,不失所望找到一个神奇的利器。supervisor!
supervisor地址:http://supervisord.org,官方标语就是:一个进程管理工具。
安装:
shell> sudo aptitude install supervisor # pip/easy_install
也可以通过其他包管理来安装,比如apt/yum等。
安装好以后,有两个可执行文件和一个配置文件(平台差异,可能路径不一致):
/usr/bin/supervisord -- supervisor服务守护进程
/usr/bin/supervisorctl -- supervisor服务控制程序,比如:status/start/stop/restart xx 等
/etc/supervisor/supervisord.conf -- 配置文件,定义服务名称以及接口等等
下面来一个示例,用web.py写一个hello的程序:
import web
urls = (
'/(.*)','hello'
)
app = web.application(urls, globals())
class hello:
def GET(self, name):
return 'hello: ' + name
if __name__ == '__main__':
app.run()
这个时候可以直接启动这个程序了,下面来配置supervisor,加入管理。修改supervisord.conf,加入如下片段:
[program:hello]
command=python /home/smallfish/hello.py
autorstart=true
stdout_logfile=/home/smallfish/hello.log
上面的意思应该很容易懂,program后面跟服务的名称,command是程序的执行路径,autorstart是表示自动启动,stdout_logfile是捕获标准输出。
到这里,基本搞定了,下面就是启动管理:
shell> sudo /etc/init.d/supervisor start -- 启动supervisor服务
shell> sudo supervisorctl status hello -- 获取hello服务的状态,因为是autorstart,这里已经启动了
hello RUNNING pid 1159, uptime 0:20:32
shell> sudo supervisorctl stop hello -- 停止hello服务
hello: stopped
shell> sudo supervisorctl stop hello -- 再次停止hello,会有错误信息
hello: ERROR (not running)
shell> sudo supervisorctl start hello -- 启动hello服务
hello: started
OK,基本的操作就是类似这个了,仔细看supervisord.conf文件里会发现有一段[unix_http_server]的配置,默认是9001端口,可以输入用户名和密码,主要用于Basic Auth认证用的。
填写一下,然后重启supervisor服务,打开浏览器输入:http://localhost:9001,如图:

相信不少人在自己机器上有多个Python版本,我的机器上Python有四个版本:2.5.x、2.6.x、2.7和stackless。
测试Google App Engine时候需要切换到2.5,正式调试时候回归到2.6,自己玩的时候会选择2.7或者stackless,每次都是通过bash profile来调整,或者手动加link。真麻烦那。。。
无意间看到有一个Perl版本的brew工具:http://search.cpan.org/~gugod/App-perlbrew-0.18/bin/perlbrew!
安装:
$ easy_install pythonbrew
$ pythonbrew_install
# 或手动下载
$ curl -kLO http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install
$ chmod +x pythonbrew-install
$ ./pythonbrew-install
把 source /xxx/.pythonbrew/etc/bashrc 加入到自己profile或者bashrc中去(xxx是自己的用户目录)
pythonbrew 常用命令如下:
install 安装版本:
$ pythonbrew install 2.6.6
过程可以参考安装日志:~/.pythonbrew/log/build.log
如果最后看到make error失败,应该是test过程失败。可以采取:
$ pythonbrew install --force 2.6.6
switch 选择版本:
$ pythonbrew switch 2.6.6
list 查看版本:
$ pythonbrew list # 列出目前已安装的版本
pythonbrew list -k # 列出可以下载和安装的版本
uninstall 卸载版本:
$ pythonbrew uninstall 2.6.6
参数还是很简单的。详见help或者http://pypi.python.org/pypi/pythonbrew/。
杂谈一下,我不是搞产品的也不是搞技术的,只是一个打杂的。随便扯谈一下。
作为一个产品,既然有这个名称,必定有其定位。在构思之前,就必须有明确的针对人群。比如是针对年轻人(80、90),抑或者上班族等等。模糊与笼统的定位,带来的只是无谓的“探索”。定位不是靠摸索出来的,也不是磨打出来的,是在你的脑海里,在你的产品里。即使现在模仿风气如此浓厚的时候,需要的也是定位。你的定位在哪儿,注定成就怎样的结果。
有了想法,下面就是一些调查和思考。你的这些针对人群在你的产品你会喜欢什么,会怎样的习惯?这个时候需要做的就是多和你的目标与非目标受众多去聊天,多去发觉他们的喜好与需求。或许你的改动只在某时一句话。所谓的走出去,这一步是必须的。闭门造车,最后可能出来就是一个自个儿意淫的杯具。
初期的实现与原型。很多时候喜欢在白板或者纸上写点什么,画点什么。记住!这一些都不能丢弃,或者擦掉。或许只是一些顺手涂鸦,或者解乏的简笔画。原型设计不需要一个完整的流程图,应该突出其顺序和关键点,以及部分UI的展示。这个过程中可以适当让受众面参与进来,设计出非自己主观的思路。
好吧,其实这些过程是可以修改和整合的。毕竟咱写的是互联网应用,不会给你完整的需求文档,完整的流程图,那样还叫互联网么?那是外包(无贬义,含笑)。
到现在为止应该糅合一些技术的关键字,比如哪些可以做到,哪些比较复杂。哪些技术可取,哪些可以折中。在考虑这个阶段的时候,技术人员需要的更多的是倾听,而不是纠结在某个环节,或者抵制某些习惯与思路。你可以说下自己的观点与想法,但是记住这些只是你自己的主观出发点,代表不了什么(如果正好受众面就是你,那恭喜了!)。需要做的就是记录下需求,转为脑海中虚拟的实现与流淌。一切心中有谱之后,可以适当“讨价还价”。毕竟每个人说出的都是自己的想法。
该画出流程图了。和上面原型一样,记住,别随意擦掉。用小人也可以,用箭头也可以,或者圆圈。你描述的只是过程,不是漫画和小说。当然加上这些元素会显得你的设计更加有爱。流程图的初衷在于走通整个流程,以及画出关键点。大致抽象出各个模型、结构和交互情况。适当写些文档把,这个总没有坏处的。或许这就是设计文档的初稿。
选型。有了需求,有了流程。下面当然是选择怎么去实现。说到用什么,仁者见仁,智者见智。啥好?啥不好?不是道听途说。关键在于自己。这些年来,喜欢的一句话就是:用自己熟悉的、用自己顺手的。看看外面的世界,好东西太多了,难不成有兴趣什么都来用一下?清醒吧哥们。你不是科学家,不是搞研究的。有这个时间,完全可以把你自己熟悉的搞的更透彻。这里并不是抵制新的或者好的工具,要明白一点,这些工具的作用域在哪儿,如果对自己实在可能起不到啥作用,或者只是存在你的假想中,放放把。未来再尝试也不迟。
关键点的实现与雏形。套句时髦的话,这个是不是应该算架构和设计?抽象出大致的模块,划分出结构。不必太在意其完整性和准确性,只要能走通就好。一次都给你写完了,其他人都去喝茶看报纸?雏形嘛,你懂得。当然,还得明确具体的工具,比如语言、数据库、系统等等。一个准则:用自己熟悉的,外加一条:简单就好。
是不是有了大体的架子了?可能下面就是大工作量的体力活啦。当然,分配体力活也是一个很体力的事情。没有一个好的管理者,这个产品就是大家拆东墙补西墙,互相推诿的产物。大致的职责有:传承需求和变化,分配与监察。消化和理解需求,确实是个跟天赋有关的事情,如果都是一个顺序跑下来的,那也不叫互联网了。需要很好的沟通能力,纸面或者口头的描述,随时可能千变万化,一成不变,继续去做外包把(囧)。分配这个环节就跟经验有关了,多去发觉你团队中的兴趣与擅长,用其长处,适当补短。合适的人干合适的事情,嘛效率?这就出来了。大家都在干活,进度就需要自己不断的监控与调整,适当多给自己宽裕的时间,哪怕只是那么一两小时。凡事没绝对,多想,多看,总是对的。
实现。设计数据库,写类库,写页面。详细罗列的话估计一长串。然后就是拆分逻辑,拆分页面,拆分功能点,然后就是与之对应的整合和补充。说到这边,可能就有人会说,用某某类库,或者某某框架,某某工具。这一切都建立在对其了解的基础上。不要太迷信,同样的刀在不同人手上是不一样的,用的好才是好刀,用不好连茅草都不如。折腾的滋味自己尝把。不是用了啥就牛逼,而是解决了啥,针对性的解决才是牛逼的关键。所有的一切,都只不过是实现的一种手段。条条大路,都可以。不要太迷信××,那只是一个传说。没有万能的实现,只有不断的解决与重新审视。实现细节滤过。。。
测试。不是写几个TestCase就是测试,那个只是证明你的代码看起来没啥问题,当然也不仅仅是用了啥测试框架,多专业。都只是辅助工具。除去功能性的测试,需要的是注意细节上。如果仅仅只是能用,没错没bug。这个不是产品,或许就是留言版。当然应该提供更多的可用性、流畅性以及体验。
唉,一不小心快十二点了,洗洗睡吧。未完再补充。
http://www.slideshare.net/nnfish/python-story
http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=pythonstory-100920035248-phpapp01&stripped_title=python-story&userName=nnfish