Perl 与 Nginx 应用 —— jeff
题目的由来 perl 是 OPSer 最熟悉的 script language nginx 是 OPSer 最喜欢的 web server 相信我的 perl 和 nginx 水平都不是最好的,不过感谢 laird 的乱点鸳鸯谱,把它们凑在一起给我作讲题,大概或许可能仿佛应该想来我看过一些而大家没留意的吧  ^=^
提要 nginx 的主要功能 nginx 原生 perl 模块介绍 几个简单的例子 nginx 第三方模块 (ngx_js/ngx_lua) 介绍 nginx 增强版 perl 介绍 总结
nginx 的主要功能 nginx 主要分成 http 和 mail 两个部分,一般来说,我们的操作都是 http 部分的(奇怪的是 nginx-src 的 configure 选项里只有 --without-http 而没有 --without-mail ) http 中,最常用的标准模块是静态文件发布的 core ,反向代理的 proxy ,上游集群的 upstream ,重定向的 rewrite 等 …… 其他常用官方模块有: fastcgi/flv/mp4/gzip_static/limit_conn/uwsgi 等
nginx 与动态应用 nginx 的标准作用是静态文件发布( proxy_cache/proxy_store 的结果也是静态文件发布,区别在目录结构而已) 与动态程序结合,最通用的办法就是使用 proxy 和 upstream 模块。 方便一点的,比如用 fastcgi 启动 perl/php 程序,或者支持 uwsgi 标准的 python/perl 程序等 ……
nginx 原生 perl 模块介绍 大家都知道, apache 除了 cgi 方式外,还有 mod_perl , nginx 也一样。 在编译时,启用 --with-http_perl_module 选项即可。 功能上,主要分两种,一种是简单的通过 perl_set 指令,返回一个 nginx.conf 的内部变量,完成后续响应;一种是通过 handler 替代一部分 serve 功能。 指令的使用方法,见 nginx 的 wiki 。
perl_set 举例 通过 perl_set 完成 url 大小写的改变。注意,这并不意味着 url 大小写不重要了,只是说在实际文件统一小写的情况下,不会对有大写的 url 返回 404 了 …… nginx.conf: http {   perl_set $new_uri '   sub {   my $r = shift;   return lc($r->uri);   };   ';   rewrite ^.*$ $new_uri last; };
perl 举例 在文件下载服务时,根据文件大小的不同,将请求引向不同的服务器集群。进一步可以根据 ip ,根据 dir ,根据 cookie 等等 …… nginx.conf: http {   perl_module perl/lib;   perl_require Redirect.pm;   server {   location / {   perl Redirect::handler;   };   }; }; Redirect.pm: package Redirect; use nginx; sub handler {   my $self = shift;   my $webroot = '/www/dl.gamedomain.com/'   return HTTP_NOT_ALLOWED unless $self->uri =~ m!^(/.+/)[^/]+$!;   my $file = $webroot . $1 . $self->filename;   my @filestat = stat($file) or return HTTP_NOT_FOUND;   my $filesize = $filestat[7];   if ( $filesize < 8 * 1024 * 1024 ) {   return OK;   } else {   $self->location('http://bigfile.cdndomain.com'.$self->uri);   } }; 1
模拟 ngx_perl 的 ngx_js 除了 Igor Sysoev 提供的 ngx_perl ,在第三方模块库里还有另外一个 ngx_javascript 模块,其实现效果甚至指令命名,都模仿了 ngx_perl 的形式。 感谢微博上某位我不记得的童鞋当初告诉我这个模块 …… 可惜偶不会 js ,估计会 js 又想折腾 webserver 都去玩 node 去了 = =!
突破 ngx_perl 的 ngx_lua agentzh 目前全力以赴的 OpenResty 项目主要就是以这个 ngx_lua 为中心,粘合一系列其他 ngx_*(memd/redis/mysql...) 的 asynchronous 模块完成一个全程 asynchronous 的 webservice 。 agentzh 提到过两点:一是 openresty 的早期版本是 perl 的,后来因为性能废弃了;二是 ngx_perl 模块的 blocking 完全浪费了 nginx 优秀的 nonblocking 设计所以纯粹是个玩具。
突破 ngx_perl 的 ngx_lua 在 ngx_perl 的 wiki 上,有这么两句话:一是不要试图使用 perl 来完成类似 dns 解析、 mysql 请求之类的需要长时间等待的任务,这会导致整个 worker 被阻塞;二是启用了 ngx_perl 的 server ,尽量避免使用 reload 命令,有可能会内存溢出。 在 agentzh 的文档 ( 讲座 ?) 中,也曾提到, ngx_lua 模块最好用来做运算,把 IO 请求通过 subrequest 交给专门的模块来做。
nginx 增强版 perl 介绍 由上推断,可以认为如果只是单纯的运算类的事情,使用 ngx_perl 完成是完全可以的,比如之前举例的 perl_set 的情况。 那么,如果想完成一个复杂功能,只能靠 ngx_lua 了么? 感谢 CPAN ,我发现了 Nginx::Engine 模块 —— 前不久这个模块刚刚从 cpan 搬到 github ,名为 nginx_perl ,会在 release 后再搬回 cpan~~
nginx 增强版 perl 介绍 在 Nginx::Engine 的介绍上,写的是 an asynchronous web framework based on nginx ;在 nginx_perl 的介绍上,则是 full-featured perl support for nginx 。 通过 $r->print 发送内容的方式很类似原始的 CGI.pm ,所以如果是写网页,还是用 dancer 之类的 framework 比较方便。 除了一些细节上的功能,比如 perl_init_worker/perl_eval 指令之外,最重要的几个地方:
nginx_perl 新增功能 1 、 $r->main_count_inc; 每个 async 的 handler 都要 increase 主进程的计数器以便 callback ; 2 、 ngx_timer $after, $repeat, sub {}; 一个定时器,在 $after 秒后 callback ,重复 $repeat 次; 3 、 ngx_connector $ip, $port, $timeout, sub {}; 异步链接 $ip:$port ,超时时间 $timeout ,成功的话,建立的链接将作为 $_[0] 返回;否则返回 $! 。
nginx_perl 新增功能 4 、 ngx_reader $connection, $buf, $min, $max, $timeout, sub{}; 从 ngx_connector 返回的 $connection 中异步读取数据到 $buf ,规定 $buf 的长度在 $min 到 $max 之间。如果长度不足,返回 NGX_EOF ,也会存入 $! 中。 5 、 ngx_writer $connection, $buf, $timeout, sub {}; 和 ngx_reader 相对的。
nginx_perl 新增功能 ngx_ssl_handshaker $connection, sub {}; 如果启动了 ssl 加密,必须在 connection 之后使用 handshaker ,再创建 reader/writer 。 ngx_resolver $host, $timeout, sub {}; 返回域名解析结果 ip 列表到 @_ 中;目前这个指令还是 nginx 原生 resolver 的封装,如果要使用的话,建议在本机配备 named/dnsmasq 等 dns 缓存 …… 这里是 TODO 中列的下一步重点改进。
nginx_perl 新增功能 $r->take_connection(); $r->give_connection(); 之前的 ngx_connector 指令,用来创建和其他 server 的链接,而这里的 takeover ,是用来获取 client 的链接,这样做 websocket 之类的就比较方便 …… 使用时同样要记得 inc ,更关键的是要使用 $r->finalize_request(NGX_DONE) 表示 EOF , return NGX_NOOP 表示 NGX_CLOSE 。
nginx_perl 示例 源码中默认有 helloworld,self_sufficient,redis 和 Nginx::Util 四个示例。不过我还是自己试着写一个类似 proxy_pass 的例子,从中也发现一些 README 没有说明的细节: package HelloWorld; use Nginx; use strict; # 用来在 nginx 启动的时候做的事情,这里单纯显示一下 sub init_worker { warn 'nginx_perl start [OK]'; }; # 这里是 nginx 的 http 模块中调用的 handler , alexander 有计划改成 tcp 级别的 sub handler { my $r = shift; # 增加主循环的计数器 $r->main_count_inc;
nginx_perl 示例 # 使用非阻塞的连接器连接 127.0.0.1 的 80 端口, 10 秒超时 ngx_connector '127.0.0.1', 80, 10, sub { # 如果连接出问题,会记录在 $! 中 if ($!) { $r->send_http_header('text/html'); $r->print(&quot;Connection failed, $!\n&quot;); $r->send_special(NGX_HTTP_LAST); # 不管怎么处理这次连接,最后一定要记得用 $r->finalize_request() ,会 decrease 之前 $r->main_count_inc; 里加上的计数。 $r->finalize_request(NGX_OK); return NGX_CLOSE; }; # 返回 $c 是建立的连接 my $c = shift; my $req_buf = &quot;GET /index.php HTTP/1.0\x0d\x0a&quot;. &quot;Host: chenlinux.com\x0d\x0a&quot;. &quot;Connection: close\x0d\x0a&quot;. &quot;\x0d\x0a&quot;; # 这里记住定义 buffer 的时候不要搞成 undef 了,会报段错误的,不过俄国佬回信说他修复了 my $res_buf = '';
nginx_perl 示例 # 非阻塞写入,超时 10 秒 ngx_writer $c, $req_buf, 10, sub { if ($!) { ...... }; $req_buf = ''; # 之前的 buffer 测试就是这里,如果加一个 warn ,就不会报错 …… 汗 #warn &quot;$req_buf\n$res_buf\n&quot;; # 写入完成后,开始调用读取 return NGX_READ; };
nginx_perl 示例 # 读取到 buffer ,最短 0 字节,最长 8000 字节,超时 10 秒 ngx_reader $c, $res_buf, 0, 8000, 10, sub { if ($!) { ...... } $r->send_http_header('text/html'); $r->print($res_buf); $r->send_special(NGX_HTTP_LAST); $r->finalize_request(NGX_OK); return NGX_CLOSE; }; # 这个是 connector 的语句,表示连接成功后调用写入 return NGX_WRITE; }; # 各个非阻塞调用完成后的返回, NGX_DONE 只能用在 http 的 handler 里,不能在 ngx_*r 里用,里面请用 NGX_CLOSE 。 return NGX_DONE; }; 1;
总结 今天主要是介绍了一下在 nginx.conf 里能够使用的几个 script ,以及用 perl 完成的两个 cdn 功能的实验。 关于 asynchronous 的应用,其实我还是门外围观者,感觉单纯 perl 也好 lua 也好,主要还是多多搭配各种模块的复杂运用。

Perl在nginx里的应用

  • 1.
    Perl 与 Nginx应用 —— jeff
  • 2.
    题目的由来 perl 是OPSer 最熟悉的 script language nginx 是 OPSer 最喜欢的 web server 相信我的 perl 和 nginx 水平都不是最好的,不过感谢 laird 的乱点鸳鸯谱,把它们凑在一起给我作讲题,大概或许可能仿佛应该想来我看过一些而大家没留意的吧 ^=^
  • 3.
    提要 nginx 的主要功能nginx 原生 perl 模块介绍 几个简单的例子 nginx 第三方模块 (ngx_js/ngx_lua) 介绍 nginx 增强版 perl 介绍 总结
  • 4.
    nginx 的主要功能 nginx主要分成 http 和 mail 两个部分,一般来说,我们的操作都是 http 部分的(奇怪的是 nginx-src 的 configure 选项里只有 --without-http 而没有 --without-mail ) http 中,最常用的标准模块是静态文件发布的 core ,反向代理的 proxy ,上游集群的 upstream ,重定向的 rewrite 等 …… 其他常用官方模块有: fastcgi/flv/mp4/gzip_static/limit_conn/uwsgi 等
  • 5.
    nginx 与动态应用 nginx的标准作用是静态文件发布( proxy_cache/proxy_store 的结果也是静态文件发布,区别在目录结构而已) 与动态程序结合,最通用的办法就是使用 proxy 和 upstream 模块。 方便一点的,比如用 fastcgi 启动 perl/php 程序,或者支持 uwsgi 标准的 python/perl 程序等 ……
  • 6.
    nginx 原生 perl模块介绍 大家都知道, apache 除了 cgi 方式外,还有 mod_perl , nginx 也一样。 在编译时,启用 --with-http_perl_module 选项即可。 功能上,主要分两种,一种是简单的通过 perl_set 指令,返回一个 nginx.conf 的内部变量,完成后续响应;一种是通过 handler 替代一部分 serve 功能。 指令的使用方法,见 nginx 的 wiki 。
  • 7.
    perl_set 举例 通过perl_set 完成 url 大小写的改变。注意,这并不意味着 url 大小写不重要了,只是说在实际文件统一小写的情况下,不会对有大写的 url 返回 404 了 …… nginx.conf: http { perl_set $new_uri ' sub { my $r = shift; return lc($r->uri); }; '; rewrite ^.*$ $new_uri last; };
  • 8.
    perl 举例 在文件下载服务时,根据文件大小的不同,将请求引向不同的服务器集群。进一步可以根据ip ,根据 dir ,根据 cookie 等等 …… nginx.conf: http { perl_module perl/lib; perl_require Redirect.pm; server { location / { perl Redirect::handler; }; }; }; Redirect.pm: package Redirect; use nginx; sub handler { my $self = shift; my $webroot = '/www/dl.gamedomain.com/' return HTTP_NOT_ALLOWED unless $self->uri =~ m!^(/.+/)[^/]+$!; my $file = $webroot . $1 . $self->filename; my @filestat = stat($file) or return HTTP_NOT_FOUND; my $filesize = $filestat[7]; if ( $filesize < 8 * 1024 * 1024 ) { return OK; } else { $self->location('http://bigfile.cdndomain.com'.$self->uri); } }; 1
  • 9.
    模拟 ngx_perl 的ngx_js 除了 Igor Sysoev 提供的 ngx_perl ,在第三方模块库里还有另外一个 ngx_javascript 模块,其实现效果甚至指令命名,都模仿了 ngx_perl 的形式。 感谢微博上某位我不记得的童鞋当初告诉我这个模块 …… 可惜偶不会 js ,估计会 js 又想折腾 webserver 都去玩 node 去了 = =!
  • 10.
    突破 ngx_perl 的ngx_lua agentzh 目前全力以赴的 OpenResty 项目主要就是以这个 ngx_lua 为中心,粘合一系列其他 ngx_*(memd/redis/mysql...) 的 asynchronous 模块完成一个全程 asynchronous 的 webservice 。 agentzh 提到过两点:一是 openresty 的早期版本是 perl 的,后来因为性能废弃了;二是 ngx_perl 模块的 blocking 完全浪费了 nginx 优秀的 nonblocking 设计所以纯粹是个玩具。
  • 11.
    突破 ngx_perl 的ngx_lua 在 ngx_perl 的 wiki 上,有这么两句话:一是不要试图使用 perl 来完成类似 dns 解析、 mysql 请求之类的需要长时间等待的任务,这会导致整个 worker 被阻塞;二是启用了 ngx_perl 的 server ,尽量避免使用 reload 命令,有可能会内存溢出。 在 agentzh 的文档 ( 讲座 ?) 中,也曾提到, ngx_lua 模块最好用来做运算,把 IO 请求通过 subrequest 交给专门的模块来做。
  • 12.
    nginx 增强版 perl介绍 由上推断,可以认为如果只是单纯的运算类的事情,使用 ngx_perl 完成是完全可以的,比如之前举例的 perl_set 的情况。 那么,如果想完成一个复杂功能,只能靠 ngx_lua 了么? 感谢 CPAN ,我发现了 Nginx::Engine 模块 —— 前不久这个模块刚刚从 cpan 搬到 github ,名为 nginx_perl ,会在 release 后再搬回 cpan~~
  • 13.
    nginx 增强版 perl介绍 在 Nginx::Engine 的介绍上,写的是 an asynchronous web framework based on nginx ;在 nginx_perl 的介绍上,则是 full-featured perl support for nginx 。 通过 $r->print 发送内容的方式很类似原始的 CGI.pm ,所以如果是写网页,还是用 dancer 之类的 framework 比较方便。 除了一些细节上的功能,比如 perl_init_worker/perl_eval 指令之外,最重要的几个地方:
  • 14.
    nginx_perl 新增功能 1、 $r->main_count_inc; 每个 async 的 handler 都要 increase 主进程的计数器以便 callback ; 2 、 ngx_timer $after, $repeat, sub {}; 一个定时器,在 $after 秒后 callback ,重复 $repeat 次; 3 、 ngx_connector $ip, $port, $timeout, sub {}; 异步链接 $ip:$port ,超时时间 $timeout ,成功的话,建立的链接将作为 $_[0] 返回;否则返回 $! 。
  • 15.
    nginx_perl 新增功能 4、 ngx_reader $connection, $buf, $min, $max, $timeout, sub{}; 从 ngx_connector 返回的 $connection 中异步读取数据到 $buf ,规定 $buf 的长度在 $min 到 $max 之间。如果长度不足,返回 NGX_EOF ,也会存入 $! 中。 5 、 ngx_writer $connection, $buf, $timeout, sub {}; 和 ngx_reader 相对的。
  • 16.
    nginx_perl 新增功能 ngx_ssl_handshaker$connection, sub {}; 如果启动了 ssl 加密,必须在 connection 之后使用 handshaker ,再创建 reader/writer 。 ngx_resolver $host, $timeout, sub {}; 返回域名解析结果 ip 列表到 @_ 中;目前这个指令还是 nginx 原生 resolver 的封装,如果要使用的话,建议在本机配备 named/dnsmasq 等 dns 缓存 …… 这里是 TODO 中列的下一步重点改进。
  • 17.
    nginx_perl 新增功能 $r->take_connection();$r->give_connection(); 之前的 ngx_connector 指令,用来创建和其他 server 的链接,而这里的 takeover ,是用来获取 client 的链接,这样做 websocket 之类的就比较方便 …… 使用时同样要记得 inc ,更关键的是要使用 $r->finalize_request(NGX_DONE) 表示 EOF , return NGX_NOOP 表示 NGX_CLOSE 。
  • 18.
    nginx_perl 示例 源码中默认有helloworld,self_sufficient,redis 和 Nginx::Util 四个示例。不过我还是自己试着写一个类似 proxy_pass 的例子,从中也发现一些 README 没有说明的细节: package HelloWorld; use Nginx; use strict; # 用来在 nginx 启动的时候做的事情,这里单纯显示一下 sub init_worker { warn 'nginx_perl start [OK]'; }; # 这里是 nginx 的 http 模块中调用的 handler , alexander 有计划改成 tcp 级别的 sub handler { my $r = shift; # 增加主循环的计数器 $r->main_count_inc;
  • 19.
    nginx_perl 示例 #使用非阻塞的连接器连接 127.0.0.1 的 80 端口, 10 秒超时 ngx_connector '127.0.0.1', 80, 10, sub { # 如果连接出问题,会记录在 $! 中 if ($!) { $r->send_http_header('text/html'); $r->print(&quot;Connection failed, $!\n&quot;); $r->send_special(NGX_HTTP_LAST); # 不管怎么处理这次连接,最后一定要记得用 $r->finalize_request() ,会 decrease 之前 $r->main_count_inc; 里加上的计数。 $r->finalize_request(NGX_OK); return NGX_CLOSE; }; # 返回 $c 是建立的连接 my $c = shift; my $req_buf = &quot;GET /index.php HTTP/1.0\x0d\x0a&quot;. &quot;Host: chenlinux.com\x0d\x0a&quot;. &quot;Connection: close\x0d\x0a&quot;. &quot;\x0d\x0a&quot;; # 这里记住定义 buffer 的时候不要搞成 undef 了,会报段错误的,不过俄国佬回信说他修复了 my $res_buf = '';
  • 20.
    nginx_perl 示例 #非阻塞写入,超时 10 秒 ngx_writer $c, $req_buf, 10, sub { if ($!) { ...... }; $req_buf = ''; # 之前的 buffer 测试就是这里,如果加一个 warn ,就不会报错 …… 汗 #warn &quot;$req_buf\n$res_buf\n&quot;; # 写入完成后,开始调用读取 return NGX_READ; };
  • 21.
    nginx_perl 示例 #读取到 buffer ,最短 0 字节,最长 8000 字节,超时 10 秒 ngx_reader $c, $res_buf, 0, 8000, 10, sub { if ($!) { ...... } $r->send_http_header('text/html'); $r->print($res_buf); $r->send_special(NGX_HTTP_LAST); $r->finalize_request(NGX_OK); return NGX_CLOSE; }; # 这个是 connector 的语句,表示连接成功后调用写入 return NGX_WRITE; }; # 各个非阻塞调用完成后的返回, NGX_DONE 只能用在 http 的 handler 里,不能在 ngx_*r 里用,里面请用 NGX_CLOSE 。 return NGX_DONE; }; 1;
  • 22.
    总结 今天主要是介绍了一下在 nginx.conf里能够使用的几个 script ,以及用 perl 完成的两个 cdn 功能的实验。 关于 asynchronous 的应用,其实我还是门外围观者,感觉单纯 perl 也好 lua 也好,主要还是多多搭配各种模块的复杂运用。