Nginx-Lua过滤POST请求

注:此文章会持续更新

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

Published: 2012-01-04 — Comments

The Rose

想了很久,很难给这篇跟技术无关的博客起一个满意的标题。刚好想起以前看过的一个视频《The Rose》,姑且凑合用吧。

其实用信乐团的《海阔天空》来当标题或许更为合适,歌词依旧如此犀利和伤感:

    冷漠的人 谢谢你们曾经看轻我
    让我不低头更精彩的活
    …
    海阔天空 狂风暴雨以后
    转过头 对旧心酸一笑而过

随着时间的消逝,有些东西始终只是存在自己的回忆里,不管是美好还是伤感,始终那些都是值得。

或许,曾经有点苦涩,有点埋怨。而如今,回想起来,始终有着那么一丝丝的回味和惋惜。

清晰的记得,那是2005年的国庆前,穿越一个城市,冒着大雨,只是为了那一份为之着迷和幻想的工作。

在去南京之前,对于写程序而已,大部分时间只是在纸上模拟,代码只是流淌在自己的记忆体中。很顺利的拿到了那份工作,一呆就是四年半,接近五年的时间。

不知道五年对每个人意味着什么,得到或者失去,其实都不算什么,只是人生的一些经历而已。回忆起上上次五年,应该是自己上师范那会,十八到二十三岁,应该算是人生最年轻的阶段,得失?我不知道。而上次五年,或许算起来应该是人生中起步积累的阶段吧。

仔细想了想,印象里有很多记忆深刻的场景。比如,那个夏天,没有空调,没有风扇,几个男人,赤膊在公司加班写代码,解决线上服务器问题。那个冬天,喝完白酒,醉醺醺的去公司写了小半夜代码,早上起来检查竟然没有任何的错误。种种画面犹如电影镜头般清晰。

或许这些,都不太重要。只是我们自己在乎的太多,太把自己当回事。或许?只能是或许。

然后,一个个老员工都选择了离开。离开的原因有人会觉得是为了跳槽,是为了选择自己发展的空间,扪心自问,真是这样的吗?只是觉得那么的无奈,那么的难以言语。

聚会的时候还会经常聊起那些过去的事,过去的人。其实大家心里都清楚,为什么离开。

虽然离开这个做法,在现在看来是多么简单,多么不需要理由的事情。

但是,面对着自己陪伴成长,一步步发展起来的那份工作,谁舍得,谁愿意呢。

过去的对与错,已经不再重要。当然,我们什么都不是,真的什么都不是。

嗯,

祝福那些已经离开和即将离开的同事们,终点不是结束,只是下一个起点,一切都会好起来的。

END

Published: 2011-12-29 — Comments

JSON 美化输出

经常会碰到一些返回 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

Published: 2011-11-24 — Comments

Nginx Session 模块

上一篇《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

Published: 2011-11-09 — Comments

Nginx 第三方模块试用记

最近试用了几个@agentzh写的第三方Nginx模块,甚为愉悦,没想到在Nginx可以玩很多技巧和扩展,分享一下。

本文尝试的几个模块大概分为:

  • echo
  • memcached
  • nginx
  • lua

详细模块地址分别为:

为了省事这里一股脑把上面的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

Published: 2011-10-30 — Comments

auto-xhprof PHP自动性能测试工具

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库文件。

--------- 扩展安装步骤 ---------

  • 编译xhprof.so扩展
% 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
Published: 2011-09-15 — Comments

supervisor - Python进程管理工具

经常会碰到要写一些守护进程,简单做法放入后台:

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,如图:

Published: 2011-05-31 — Comments

pythonbrew - Python多版本管理利器

相信不少人在自己机器上有多个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/

Published: 2011-04-03 — Comments

互联网产品与技术的那些杂事儿

杂谈一下,我不是搞产品的也不是搞技术的,只是一个打杂的。随便扯谈一下。

作为一个产品,既然有这个名称,必定有其定位。在构思之前,就必须有明确的针对人群。比如是针对年轻人(80、90),抑或者上班族等等。模糊与笼统的定位,带来的只是无谓的“探索”。定位不是靠摸索出来的,也不是磨打出来的,是在你的脑海里,在你的产品里。即使现在模仿风气如此浓厚的时候,需要的也是定位。你的定位在哪儿,注定成就怎样的结果。

有了想法,下面就是一些调查和思考。你的这些针对人群在你的产品你会喜欢什么,会怎样的习惯?这个时候需要做的就是多和你的目标与非目标受众多去聊天,多去发觉他们的喜好与需求。或许你的改动只在某时一句话。所谓的走出去,这一步是必须的。闭门造车,最后可能出来就是一个自个儿意淫的杯具。

初期的实现与原型。很多时候喜欢在白板或者纸上写点什么,画点什么。记住!这一些都不能丢弃,或者擦掉。或许只是一些顺手涂鸦,或者解乏的简笔画。原型设计不需要一个完整的流程图,应该突出其顺序和关键点,以及部分UI的展示。这个过程中可以适当让受众面参与进来,设计出非自己主观的思路。

好吧,其实这些过程是可以修改和整合的。毕竟咱写的是互联网应用,不会给你完整的需求文档,完整的流程图,那样还叫互联网么?那是外包(无贬义,含笑)。

到现在为止应该糅合一些技术的关键字,比如哪些可以做到,哪些比较复杂。哪些技术可取,哪些可以折中。在考虑这个阶段的时候,技术人员需要的更多的是倾听,而不是纠结在某个环节,或者抵制某些习惯与思路。你可以说下自己的观点与想法,但是记住这些只是你自己的主观出发点,代表不了什么(如果正好受众面就是你,那恭喜了!)。需要做的就是记录下需求,转为脑海中虚拟的实现与流淌。一切心中有谱之后,可以适当“讨价还价”。毕竟每个人说出的都是自己的想法。

该画出流程图了。和上面原型一样,记住,别随意擦掉。用小人也可以,用箭头也可以,或者圆圈。你描述的只是过程,不是漫画和小说。当然加上这些元素会显得你的设计更加有爱。流程图的初衷在于走通整个流程,以及画出关键点。大致抽象出各个模型、结构和交互情况。适当写些文档把,这个总没有坏处的。或许这就是设计文档的初稿。

选型。有了需求,有了流程。下面当然是选择怎么去实现。说到用什么,仁者见仁,智者见智。啥好?啥不好?不是道听途说。关键在于自己。这些年来,喜欢的一句话就是:用自己熟悉的、用自己顺手的。看看外面的世界,好东西太多了,难不成有兴趣什么都来用一下?清醒吧哥们。你不是科学家,不是搞研究的。有这个时间,完全可以把你自己熟悉的搞的更透彻。这里并不是抵制新的或者好的工具,要明白一点,这些工具的作用域在哪儿,如果对自己实在可能起不到啥作用,或者只是存在你的假想中,放放把。未来再尝试也不迟。

关键点的实现与雏形。套句时髦的话,这个是不是应该算架构和设计?抽象出大致的模块,划分出结构。不必太在意其完整性和准确性,只要能走通就好。一次都给你写完了,其他人都去喝茶看报纸?雏形嘛,你懂得。当然,还得明确具体的工具,比如语言、数据库、系统等等。一个准则:用自己熟悉的,外加一条:简单就好。

是不是有了大体的架子了?可能下面就是大工作量的体力活啦。当然,分配体力活也是一个很体力的事情。没有一个好的管理者,这个产品就是大家拆东墙补西墙,互相推诿的产物。大致的职责有:传承需求和变化,分配与监察。消化和理解需求,确实是个跟天赋有关的事情,如果都是一个顺序跑下来的,那也不叫互联网了。需要很好的沟通能力,纸面或者口头的描述,随时可能千变万化,一成不变,继续去做外包把(囧)。分配这个环节就跟经验有关了,多去发觉你团队中的兴趣与擅长,用其长处,适当补短。合适的人干合适的事情,嘛效率?这就出来了。大家都在干活,进度就需要自己不断的监控与调整,适当多给自己宽裕的时间,哪怕只是那么一两小时。凡事没绝对,多想,多看,总是对的。

实现。设计数据库,写类库,写页面。详细罗列的话估计一长串。然后就是拆分逻辑,拆分页面,拆分功能点,然后就是与之对应的整合和补充。说到这边,可能就有人会说,用某某类库,或者某某框架,某某工具。这一切都建立在对其了解的基础上。不要太迷信,同样的刀在不同人手上是不一样的,用的好才是好刀,用不好连茅草都不如。折腾的滋味自己尝把。不是用了啥就牛逼,而是解决了啥,针对性的解决才是牛逼的关键。所有的一切,都只不过是实现的一种手段。条条大路,都可以。不要太迷信××,那只是一个传说。没有万能的实现,只有不断的解决与重新审视。实现细节滤过。。。

测试。不是写几个TestCase就是测试,那个只是证明你的代码看起来没啥问题,当然也不仅仅是用了啥测试框架,多专业。都只是辅助工具。除去功能性的测试,需要的是注意细节上。如果仅仅只是能用,没错没bug。这个不是产品,或许就是留言版。当然应该提供更多的可用性、流畅性以及体验。

唉,一不小心快十二点了,洗洗睡吧。未完再补充。

 

Published: 2011-02-19 — Comments