chaos2openResty 2.1.130619 文档

Version: 2.1.130619
[首页]

乱入 OpenResty 手册!

好吧,这是有关如何在 42分钟之内 超高速上手 OpenResty 的一个不怎么靠谱的手册;-)

章节索引

0: 为毛要入?!

先得声明,要想真正在 42分钟 内进入 `OpenResty`_ 玩起来,不是没有前提的,,,

至少:

  • 42岁 ?! 那还能有好奇心,绝对是人类精品,肯定冇问题了!
  • 42个月, Linux/Unix 系统使用~注意!是使用不是管理体验;不然各种标准的命令行操作要死人的,,
  • 42周的, 编程体验,不许什么語言,就是 JS 也成,起码要有一丝码感,,,
  • 42次的, 蕃茄工作时间 的经历,至少知道什么是” 心流 “般的专注!
  • 42天的, 相关资料通读,相关社区的加入/旁听,,,一定要摸清楚相关主要开发人员的沟通习惯 ;-)
  • 42小时, 的周边环境整备,熟悉工作系统以及配置好顺手的工具(git/apt/yum/brew ...)

然后,还是想继续这遭没谱乱入的话 ;-)

警告

  • 走起!

预备

一切开始前,先必须 相信 !

相信

...

仅仅是过一下眼,还没有领会到 OpenResty 妙处的,基本很难相信了 - 不相信,那么任何乱入行为,都会被自个儿否决 - 那真心没招了,,,

说明, OpenResty 不是你的那份儿菜,甭忙了,继续 C++/JAVA/.NET 吧,,,

警告

但是

  • 你选择了相信 OpenResty 愿意体验这种合体式的业务开发形式 ?!
  • ~~~ 那么, go!

+22分钟:初尝

整起来先! - 嗯嗯嗯,现在可以计时了: 00:00

安装

参考: 官方安装说明

走起

$ brew install pcre
$ axel http://agentzh.org/misc/nginx/ngx_openresty-1.0.8.26.tar.gz
$ tar xzvf ngx_openresty-1.0.8.26.tar.gz
$ cd ngx_openresty-1.0.8.26
$ ./configure  --with-cc-opt="-I/usr/local/Cellar/pcre/8.21/include" \
  --with-ld-opt="-L/usr/local/Cellar/pcre/8.21/lib"
$ make
$ make install
$ $ /usr/local/openresty/nginx/sbin/nginx -v
nginx: nginx version: ngx_openresty/1.0.10.48

注解

(~_~)

  • 这儿的配置,和官方不同
  • 是因为: Issue #3:
  • 另外,编译时,可以使用 -j8 参数动用所有 CPU 快速完成编译!

OpenResty 就是要反复调整内置nginx 的配置,为了方便起见

$ /opt/sbin/openresty.server
usage: /opt/sbin/openresty.server {check|start|term|stop|reload|restart|upgrade}

建议部署在 /opt/sbin 目录 然后就可以随时:

  • /opt/sbin/openresty.server check 检验配置脚本是否正确
  • /opt/sbin/openresty.server start 启动 Nginx
  • /opt/sbin/openresty.server reload 热加载新的配置文件
  • 测试是否在跑? 使用 ps aux | grep nginx

那么开始 OpenResty 的编程吧!

理解重要的目录结构:

/usr/local/openresty/
  +- luajit         Lua实时编译组件
  +- lualib         Lua预装原生库
  +- nginx          openresty 狠狠定制过的稳定版本 Nginx
    +- log          默认的日志,pid 存放目录
    +- conf         默认的配置文件目录
      +- nginx.conf

那么,要作是只是:

  • 修订 主配置文件,加入我的专用配置文件包含:

    http {
        include my_openresty.conf;
        ...
    
  • 创建 my_openresty.conf 并写入:

    server {
      listen       9090;
      server_name  localhost;
      error_log   logs/error.my_openresty.log info;
      location / {
          content_by_lua "ngx.say('Hello,world!')";
      }
    }
    
  • 然后享受成果

    $ /opt/sbin/openresty.server reload
    $ curl localhost:9090
    Hello,world!
    

每次都要修改和各种 web 服务配置杂在一起的 nginx 配置,而且再重启 nginx 才能见到成果?!

当然有好招 - 引入独立 Lua 脚本,到指定 url 路由 - 增补 my_openresty.conf :

server {
  listen       9090;
  server_name  localhost;
  error_log   logs/error.my_openresty.log info;
  location / {
      content_by_lua "ngx.say('Hello,world!')";
  }
  location /readme {
      lua_code_cache off; # <<< 关键技巧!
      content_by_lua_file conf/lua/readme.lua;
  }
}
  • 创建业务脚本: /usr/local/openresty/nginx/conf/lua/readme.lua
-- readme for my openresty app.
ngx.say("Hallo World!")
  • 再重启 nginx, curl localhost:9090/readme 获得同样的 “Hallo World!”
    • 要是看到:
nginx: [warn] lua_code_cache is off; this will hurt performance in /usr/local/openresty/nginx/conf/my_openresty.conf:23
  • 别怕,正常的提醒,說没有 Lua 脚本的缓存,整个性能会下载,没关系,测试开发阶段,俺们不论运行性能,先关注开发效率!
  • 然后,增补 readme.lua
-- readme for my openresty app.
VERTION="URIsAok4openresty v12.03.6"

ngx.say(VERTION
    ,"\n\tusage:"
    ,"$crul -d 'uri=http://sina.com' locahost:9090/=/chk"
    )
  • 不用重启, 再次 curl 就发现返回已经变化了!
$ curl localhost:9090/readme
URIsAok4openresty v12.03.6
  usage:$crul -d 'uri=http://sina.com' 127.0.0.1:9090/=/chk
小结

不出意外的话, 22:00 用在这个阶段,太足够了!

应该已经体验到 OpenResty 的核心爽直了?!

具体的:

  • 不用跟 c 死磕,想給 Nginx 扩展功能,不用以往麻烦的调试过程循环

    改c代码
    ^  `->编译
    |   `->替换老.so
    |     `->重启nginx (加载模块,有时,还必须整个重新编译 nginx)
    |       `-> curl 请求测试
    |               |
    +---------------/
    
  • 而是,非常直接的:

    改lua代码
     ^ `-> curl 请求测试
     |        |
     +--------/
    

而且, 性能几乎没有下降!

那么

要干点儿实事儿了!

仅仅以使用 OpenResty 包装 金山网址云安全开放API 为实例

那么,要解决的子问题有:
  • 怎么获得 POST 来的数据?
  • 怎么对提交的数据进行分组解析?
  • 怎么对字串进行 base64 ?
  • 怎么对字串进行 md5 ?
  • 怎么对字串进行合并 ?
  • 怎么在请求期间抓取外部网站数据 ?
  • 怎么对请求方式进行判定?
  • ...

1: 进入初阶

好吧,想 C/C++/PHP/node.js/Python ... 切换到 Lua ?!

  • 其实,真心简单,毕竟, Lua 实在是非常简单的 脚本语言
  • 其实,任何开发语言的乱入,完全可以不管什么设计思想,开发模式之类的高级东西

警告

  • 走起!

整备

只要明确一些 Lua 的`基本形式` 80% 的实际编程就可以混进去了 !-) :

  • 基本语法
-- 单行注释
--[[
    多行
    注释
]]
a="hollo"
b= a .. 1   -- 字串连接,连接数字会自动转换类型
b,a = a,b   -- 巨爽直的变量交换

c=0
if 1 ~= c   -- 不等于?
    print "Yes"
else
    print "No"
end


function d(e,f)
    return e,f,e*f
end

A1,A2,result = d(2,3)
print(A1,A2,result)
  • 基本数据

    • 数字,字串,布尔 基本和其它脚本语言类同
    • nil ~ 空值
    • 特殊的是 function 也是基本数据类型!
    • 关系表,嗯嗯嗯,就是Python 里的字典吼
T1 ={ 10,  -- 相当于 [1] = 10
    ,[100] = 40,
    ,John={  -- 如果你原意,你还可以写成:["John"] =
        Age=27   -- 如果你原意,你还可以写成:["Age"] =27
        ,Gender=Male   -- 如果你原意,你还可以写成:["Gender"] =Male
        }
    ,20  -- 相当于 [2] = 20
}

+15分钟:突入

google 真心好朋友,通过搜索可以获得很多已经用上 OpenResty 的先驱们的各种代码片段

只要快速使用 写lua->curl测试 流程,结合以往的开发经验,驗证猜想,突进就好!

  • 嗯嗯嗯,现在可以计时了: 22:01
阵地

增补 my_openresty.conf :

server {
...
    location ~ ^/=/(\w+) {
        content_by_lua_file conf/lua/$1.lua;
        lua_code_cache off;
    }
...
}

解释一下

  • ~ ^/=/(w+) 就是説,处理所有对类似 localhost:9090/=/abc 形式的请求

    • /=/ 只是笔者看来的合法 url 形式,以便和一般的服务请求区分
    • (w+) 是基础的 正则表达式 意思是长度至少为一,全部由e文字母组成的字串
  • conf/lua/$1.lua 也是 正则表达式 ,意思是匹配到 conf/lua 目录中所有和 url 请求同名的 .lua 脚本

    • 比如: localhost:9090/=/abc
    • 将试图执行: conf/lua/abc.lua

创建 chk.lua :

-- try openresty easy creat RESTful API srv.
ngx.req.read_body()
local method = ngx.var.request_method
ngx.say("request_method:\t",method)

if method ~= 'POST' then
    ngx.say('pls. /=/chk only work as POST ;-)')
else
    ngx.say('realy working,,,')
end

表忘记:

  • $ /opt/sbin/openresty.server restart 重启 OpenResty 中的 Nginx
工作环境
  • 根据 ngx.var.request_method 的状态值, 已经完成一个业务流程:

    • 如果是普通的 GET 请求,直接吼回提示,不处置
    • 如果是数据的 POST 请求,开始 真正的干活

注解

(~_~)

  • 其实,使用 print 或是 ngx.log() 都是可以直接将调试值输出到错误日志里的
  • 但是,会间杂在大量其它正当日志中,很不容易观察, 例如: 插图 1-1 日志中不同的调试方式输出 所示
  • 就是在 readme.lua 中增补两种调试打印代码引发的
VERTION="URIsAok4openresty v12.03.6"
print("VERTION: ",VERTION)
ngx.log(ngx.INFO,"VERTION: ",VERTION)
  • 使用 tail -f /path/2/error.log 持续观察日志时的情景
_images/chaos2-1-1.png

插图 1-1 日志中不同的调试方式输出

进一步的,其实,比较舒服的开发环境,应该是:

  • 可以同时观察到代码,错误日志,以及测试请求过程的!

  • 俺推荐使用 tmux 作为多窗格终端

  • 具体情景如 插图 1-2 使用tumx 同时进行观察的情景 所示

    • 上方是 tail -f /path/2/error.log 监察
    • 下左是脚本编辑
    • 下右是 curl 请求测试区
_images/chaos2-1-2.png

插图 1-2 使用tumx 同时进行观察的情景

搞掂

好的,什么都整顿舒服了,就可以快速一条条试错的方式,堆出所有业务逻辑了!

local method = ngx.var.request_method
--各种事先申請好的 金山网址云 接口授权信息
APPKEY = "k-60666"
SECRET = "99fc9fdbc6761f7d898ad25762407373"
ASKHOST = "http://open.pc120.com"
ASKTYPE = "/phish/?"
--内部辅助函式,组成 金山网址云 需要的查询請求链接
function checkForValidUrl(uri)
    crtURI = ngx.encode_base64(uri)
    timestamp = ngx.now()
    ngx.say('timestamp:\t',timestamp)
    signbase = ASKTYPE .. "appkey=" .. APPKEY .. "&q=" .. crtURI .. "&timestamp=" .. timestamp
    sign = ngx.md5(signbase .. SECRET)
    return ASKHOST .. signbase .. "&sign=" .. sign
end

if method ~= 'POST' then
    ngx.say('pls. only POST chk me;-)')
else
    local data = ngx.req.get_body_data()
    ngx.say("get_body_data:\t",data)
    local args = ngx.req.get_post_args()
    local uri = args.uri
    ngx.say("args.uri:\t",uri)
    local chkURI = checkForValidUrl(uri)
    ngx.say(chkURI)
end

测试输出如:

$ curl -d "uri=http://sina.com" localhost:9090/=/chk
get_body_data:  uri=http://sina.com
args.uri:       http://sina.com
timestamp:      1331304634.315
http://open.pc120.com/phish/?appkey=k-60666&q=aHR0cDovL3NpbmEuY29t\
&timestamp=1331304408.315&sign=d7e80af8f0f83438e315e320eba4efe6

HttpLuaModule 真心 碉堡 了! 所有最常见的操作都内置了!

  • 字串的 base64 编码: ngx.encode_base64()
  • 字串的 md5 编码: ngx.md5()
  • 当前时间戮: ngx.now()
  • POST 上来的数据体 ngx.req.get_body_data()
  • 数据体的解析: ngx.req.get_post_args()
  • 实际数据的引用: args.uri

但是,怎么 向外部url发出请求并接收数据?!

luarocks

摇滚吧! Lua....

和所有成熟的开发语言一样, Lua 也有自个儿的第3方扩展包管理平台:

luarocks

真心有爱的方便工具!

  • 安装?! 下载,解开,./configure,make,make install
  • 使用? 先搜索
$ luarocks search url

Search results:
===============

Rockspecs and source rocks:
---------------------------

luacurl
   1.2.1-1 (src) - http://luarocks.org/repositories/rocks
   1.2.1-1 (rockspec) - http://luarocks.org/repositories/rocks
   1.1-3 (src) - http://luarocks.org/repositories/rocks
   1.1-3 (rockspec) - http://luarocks.org/repositories/rocks
   1.1-2 (src) - http://luarocks.org/repositories/rocks
   1.1-2 (rockspec) - http://luarocks.org/repositories/rocks
  • 安装扩展
$ luarocks install luacurl
Installing http://luarocks.org/repositories/rocks/luacurl-1.2.1-1.src.rock...
Using http://luarocks.org/repositories/rocks/luacurl-1.2.1-1.src.rock... switching to 'build' mode
Archive:  /tmp/luarocks_luarocks-rock-luacurl-1.2.1-1-8288/luacurl-1.2.1-1.src.rock
  inflating: luacurl-1.2.1-1.rockspec
 extracting: luacurl-1.2.1.zip
Archive:  luacurl-1.2.1.zip
   creating: luacurl-1.2.1/
  inflating: luacurl-1.2.1/CMakeLists.txt
  inflating: luacurl-1.2.1/luacurl.c
export MACOSX_DEPLOYMENT_TARGET=10.3; gcc -O2 -fPIC -I/usr/local/include -c luacurl.c -o luacurl.o -I/usr/include
export MACOSX_DEPLOYMENT_TARGET=10.3; gcc -bundle -undefined dynamic_lookup -all_load -o luacurl.so -L/usr/local/lib luacurl.o -L/usr/lib -lcurl
Updating manifest for /usr/local/lib/luarocks/rocks

luacurl 1.2.1-1 is now built and installed in /usr/local/ (license: MIT/X11)

抄个小函式就好:

curl = require "luacurl"
function _fetch_uri(url, c)
    local result = { }
    if c == nil then
        c = curl.new()
    end
    c:setopt(curl.OPT_URL, url)
    c:setopt(curl.OPT_WRITEDATA, result)
    c:setopt(curl.OPT_WRITEFUNCTION, function(tab, buffer)
        table.insert(tab, buffer)
        return #buffer
    end)
    local ok = c:perform()
    return ok, table.concat(result)
end
--前略,,,
local chkURI = checkForValidUrl(url)
--ngx.say(chkURI)
ok, html = _fetch_uri(chkURI)
ngx.say("_fetch_uri:\t", ok, "\t", html)
  • 测试,确认效果:

    ...
    http://open.pc120.com/phish/?appkey=k-60666&q=aHR0cDovL3NpbmEuY29t&timestamp=1331306157.315&sign=4190815f3920d9bbd0d1410525343a0e
    _fetch_uri:     true    {"success":1,"phish":0}
    

基本功能,达成, 收功!

小结

37:00 ~ 这一堆,一刻鈡,整出来不难吧?

想来: - 其实,关键功能性行为代码,就8行

  • 其中7 行全部可以在一篇文档中查到: HttpLuaModule
  • 仅仅有一行,是需要学习新的工具,安装新的组件,学习新的文档,抄进来新的函式
  • 即: ok, html = _fetch_uri(chkURI)
  • 其余,都是力气活儿

    • 只要别抄錯
    • 都是赋值,赋值,赋值,赋值,,,,
  • 只要注意每一步,都使用 ngx.say() 吼回来,测试确认无误,就可以继续前进了,,,

这就是脚本语言的直觉式开发调试体验!

11:21 p.m., Wednesday Jan. 15 吉米:

luarocks with luajit 的方式

./configure –with-lua=”/usr/local/openresty/luajit/bin” –prefix=”/usr/local” –lua-suffix=”jit-2.1.0-alpha” –with-lua-include=”/usr/local/openresty/luajit/include/luajit-2.1” –force-config

解释一下:

--lua-suffix="jit-2.1.0-alpha" :
    /usr/local/openresty/luajit/bin/luajit-2.1.0-alpha

这是专家建议的编译出专用版本的技巧.

那么

接下来出于 程序员的直觉 应该折腾什么?!

  • 模块化代码
  • 友好输出
  • 提升效率

...

警告

  • 走着...

2: 怎么深入?

好了,已经完成了预期的 :

但是 :

  • 代码很集中,业务逻辑离头部很远,很难看
  • 当没有进行 POST 请求时,以及各种其它意外请求时,并没智能的捕获,并提示
  • 变量名设计的都很挫,需要重构

...

所以,继续折腾...

调研

核心是先理解 Lua 的包管理机制:

OpenResty 邮件列表中的讨论过程,核心作者的 明确提示 :

agentzh agentzh@gmail.com
发件人当地时间:     发送时间 11:00 (GMT+08:00)。发送地当前时间:上午12:07。 ✆
回复:  openresty@googlegroups.com
主题:  Re: [openresty:99] 纯lua 问题: attempt to ... (a nil value)

...
一般地,由于全局变量是每请求的生命期,因此以此种方式定义的函数的生命期也是每请求的。
为了避免每请求创建和销毁 Lua closure 的开销,建议将函数的定义都放置在自己的 Lua module 中,
例如:

    -- my_module.lua
    module("my_module", package.seeall)
    function foo() ... end

然后,再在 content_by_lua_file 指向的 .lua 文件中调用它:

    local my_module = require "my_module"
    my_module:foo()

因为 Lua module 只会在第一次请求时加载一次
(除非显式禁用了 lua_code_cache 配置指令),后续请求便可直接复用。

Regards,
-agentzh

警告

  • 来吧!

+5分钟:重构

设计

/usr/local/openresty/nginx/conf
    +- my_openresty.conf    业务路由配置
      +- lua/               业务脚本入口
        +- readme.lua       /readme 响应
        +- chk.lua          /=/chk 响应
        +- ksc.lua          可复用的功能包
模块化

所以,兴冲冲,将代码分离出来:

-- KCS API support
module("ksc", package.seeall)
--curl as lua
curl = require "luacurl"
function _fetch_uri(url, c)
    local result = { }
    if c == nil then
        c = curl.new()
    end
    c:setopt(curl.OPT_URL, url)
    c:setopt(curl.OPT_WRITEDATA, result)
    c:setopt(curl.OPT_WRITEFUNCTION, function(tab, buffer)
        table.insert(tab, buffer)
        return #buffer
    end)
    local ok = c:perform()
    return ok, table.concat(result)
end
-- global var
--ngx.say(PHISHTYPE["2"])
APPKEY = "k-60666"
SECRET = "99fc9fdbc6761f7d898ad25762407373"
ASKHOST = "http://open.pc120.com"
ASKTYPE = "/phish/?"
function checkForValidUrl(uri)
    crtURI = ngx.encode_base64(uri)
    timestamp = ngx.now()
    signbase = ASKTYPE .. "appkey=" .. APPKEY .. "&q=" .. crtURI .. "&timestamp=" .. timestamp
    sign = ngx.md5(signbase .. SECRET)
    return ASKHOST .. signbase .. "&sign=" .. sign
end

精简原 chk.lua

ngx.req.read_body()
local method = ngx.var.request_method
local KSC = require "ksc"

if method ~= 'POST' then
    ngx.say('pls. only POST chk me;-)')
else
    local args = ngx.req.get_post_args()
    local uri = args.uri
    local chkURI = KSC.checkForValidUrl(uri)
    ok, html = KSC._fetch_uri(chkURI)
    ngx.say("_fetch_uri:\t", ok, "\t", html)
end

哗! 业务逻辑首屏得见!

撞!

但是! 立即...

*1 lua handler aborted: runtime error:
/usr/local/openresty/nginx/conf/lua/chk.lua:6: module 'ksc' not found:
no field package.preload['ksc']
no file '/usr/local/openresty/lualib/ksc.lua'
no file './ksc.lua'
no file '/usr/local/openresty/luajit/share/luajit-2.0.0-beta9/ksc.lua'
no file '/usr/local/share/lua/5.1/ksc.lua'
no file '/usr/local/share/lua/5.1/ksc/init.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/ksc.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/ksc/init.lua'
no file '/usr/local/openresty/lualib/ksc.so'
no file './ksc.so'
no file '/usr/local/lib/lua/5.1/ksc.so'
no file '/usr/local/openresty/luajit/lib/lua/5.1/ksc.so'
no file '/usr/local/lib/lua/5.1/loadall.so', client: 127.0.0.1, server: localhost, request: "POST /=/chk HTTP/1.1", host: "127.0.0.1:9090"

...

明显是没有找到 ksc.lua 包, 为毛?! - 一通乱 搜索 - 据某处的示例修订:

-- ksc.lua
module(..., package.seeall)
,,,
-- chk.lua
local KSC = require "lua.ksc"
  • 并增补 my_openresty.conf
server {
    lua_package_path '/usr/local/openresty/nginx/conf/lua/?.lua;;';
    ...
  • /opt/sbin/openresty.server reload 热加载新的配置文件

就可以了!

12:57 a.m., Thursday Jan. 16 吉米 :
lua_package_path '/usr/local/openresty/nginx/conf/lua/?.lua;;';

放到 server 外面去

lua_package_path '/usr/local/openresty/nginx/conf/lua/?.lua;;';
server {
...
}

善! 这个可以有 ;-)

仔细...

可是,立即遭到了 agentzh 的劝戒,,, - 再尝试,才知道

  • lua_package_path 不允许用在 server 或是 location 域中,只能在 http 中!
  • lua_package_path 增补后, Nginx 仅仅 reload 可能不加载这一行为,最好 restart !
  • 不相信! 尝试一下,才真正见到日志中的 [emerg]
Checking configuration for correct syntax and
then trying to open files referenced in configuration...
nginx: [emerg] "lua_package_path" directive
is not allowed here in
/usr/local/openresty/nginx/conf/my_openresty.conf:10
nginx: configuration file
/usr/local/openresty/nginx/conf/nginx.conf test failed
  • 其实,这本身,在 HttpLuaModule 的维基手册示例第一行就 暗示 过!
_images/chaos2-2-1.png

插图 2-1 维基中手册文档的提示

警告

(~.~)

  • 这得要多少 心灵相通 才意识的到吼?!

快速修订回来:

-- ksc.lua
module("ksc", package.seeall)
,,,

-- chk.lua
local KSC = require "ksc"

一切如愿鸟 !-)

12:57 a.m., Thursday Jan. 16 吉米 :
-- chk.lua
local KSC = require "lua.ksc"

不要修改..

这个得研究一下,不过,形式上的确不进行消减要直觉的多, 谢谢!

复用?

对于各种意外,其实,很想直接使用 /readme 中的输出就好的,

  • 根据以往幻灯中的提示,使用 ngx.location.capture()
if method ~= 'POST' then
    ngx.say('pls. only POST chk me;-)')
    local readme = ngx.location.capture("/readme")
    if readme.status == 200 then
        ngx.say(readme.body)
    end

效果

$ curl  localhost:9090/=/chk
pls. only POST chk me;-)
URIsAok4openresty v12.03.6
        usage:$crul -d 'uri=http://sina.com' 127.0.0.1:9090/=/chk

BINGO!

小结

42:01 ~ 这两个重构,纯粹是复制/粘贴,5分鈡,整出来不难吧?

  • 但是,过程中的心理冲突,绝对不轻

  • 比如,为毛 ngx.redirect()重定向 ?!

    • 俺是首先尝试使用 重定向 来合并输出的
    • 结果,只是使用标准的 http 302 状态码返回而已
  • 等等,都需要补课,老实查阅文档,认真领悟,大胆尝试,建立靠谱的思路和反应,,,

整个儿的

最终,完成所有功能的配置和代码:

nginx.conf
http {
    ...
    # 设置纯Lua扩展库PATH(';;' is the default path):
    lua_package_path '/usr/local/openresty/nginx/conf/lua/?.lua;;';
    include my_openresty.conf;
    ...
my_openresty.conf
server {
    listen       9090;
    server_name  localhost;
    error_log   logs/error.my_openresty.log info;
    default_type 'text/plain';

    location / {
        content_by_lua_file conf/lua/readme.lua;
    }
    location /readme {
        content_by_lua_file conf/lua/readme.lua;
    }

    location ~ ^/=/(\w+) {
        content_by_lua_file conf/lua/$1.lua;

        lua_code_cache off;
    }
}
readme.lua
-- readme for /=/ export base help!
ngx.req.read_body()
VERTION="URIsAok4openresty v12.03.6"
ngx.say(VERTION
    ,"\n\tusage:"
    ,"$crul -d 'uri=http://sina.com' 127.0.0.1:9090/=/chk"
    )
ksc.lua
-- KCS API support
module("ksc", package.seeall)
curl = require "luacurl"
function _fetch_uri(url, c)
    local result = { }
    if c == nil then
        c = curl.new()
    end
    c:setopt(curl.OPT_URL, url)
    c:setopt(curl.OPT_WRITEDATA, result)
    c:setopt(curl.OPT_WRITEFUNCTION, function(tab, buffer)
        table.insert(tab, buffer)
        return #buffer
    end)
    local ok = c:perform()
    return ok, table.concat(result)
end
-- global var
PHISHTYPE = {["-1"]='UNKNOW'
    ,["0"]='GOOD'
    ,["1"]='PHISH'
    ,["2"]='MAYBE PHISH'
    }
--ngx.say(PHISHTYPE["2"])
APPKEY = "k-60666"
SECRET = "99fc9fdbc6761f7d898ad25762407373"
ASKHOST = "http://open.pc120.com"
ASKTYPE = "/phish/?"
function checkForValidUrl(uri)
    ngx.say("uri:\t",uri)
    crtURI = ngx.encode_base64(uri)
    timestamp = ngx.now()
    signbase = ASKTYPE .. "appkey=" .. APPKEY .. "&q=" .. crtURI .. "&timestamp=" .. timestamp
    sign = ngx.md5(signbase .. SECRET)
    return ASKHOST .. signbase .. "&sign=" .. sign
end
chk.lua
-- try openresty easy creat RESTful API srv.
ngx.req.read_body()
local method = ngx.var.request_method
local KSC = require "ksc"

if method ~= 'POST' then
    ngx.say('pls. only POST chk me;-)')
    local readme = ngx.location.capture("/readme")
    if readme.status == 200 then
        ngx.say(readme.body)
    end
else
    local data = ngx.req.get_body_data()
    local args = ngx.req.get_post_args()
    local uri = args.uri
    local url = uri --fields[3]
    local chkURI = KSC.checkForValidUrl(url)
    ok, html = KSC._fetch_uri(chkURI)
    if ok then
        local cjson = require "cjson"
        json = cjson.decode(html)
        if 1 == json.success then
            ngx.log(ngx.INFO,"\n\tKCS say:  ",html,"\n\t")
            ngx.say("KCS /phish?:\t", KSC.PHISHTYPE[tostring(json.phish)])
        else
            ngx.say("KCS say:\t",html)
        end
    end
end

∞分钟 折腾到永远

好了,已经完成了预期的 :

但是 :

  • 代码很集中,业务逻辑离头部很远,很难看
  • 当没有进行 POST 请求时,以及各种其它意外请求时,并没智能的捕获,并提示
  • 变量名设计的都很挫,需要重构

...

所以,继续折腾...

本章节,撰写ing,,,先散乱的从 OpenResty 列表中收集了一些自个儿感觉很基础的技巧

  • 但是工作原因,没有时间继续深入了
  • 好在是开源图书,有心者可以共同完善下去...

ngx_lua 管理的 cosocket 和 nginx_http_uptream 的平行关系 http://agentzh.org/misc/slides/libdrizzle-lua-nginx/#57

再出发

用非常与注的 42 分钟,算是将将入得 OpenResty 的门来, 然后?!

  • 真正理解 `OpenRsty`_ 之下的 Nginx 了嘛?
  • 真正能嵌入复杂的业务跑生产系统了嘛?
  • 真正的真正,,,

所以,,,

警告

  • 益rz ...

一切的效率发源:

Kernel Asynchronous I/O (AIO) Support for Linux http://lse.sourceforge.net/io/aio.html

替换 luacurl 回归非阻塞

问题
分析
解决

agentzh agentzh@gmail.com 发送至: openresty@googlegroups.com 日期: 2012年3月16日 下午6:33 主题: Re: ngx_lua 中访问远方 HTTP 服务的两种推荐的做法 (Was Re: [openresty:287] 42分钟,乱入 OpenResty 手册!-)) 明显是因为你漏了我的示例代码中的下面这一行: >> >> resolver 114.114.114.114; >> >> 咱能直接复制粘贴么?上面的 Nginx 出错信息也指示你未配置 resolver 指令: >> >> http://wiki.nginx.org/HttpCoreModule#resolver >> > > - FT! 俺想 DNS 当前可用就没有配置吼,, > - 不明白的代码就没抄,,, > 看来都有深意,先用起来,再理解,,, > - 是为了防止各种内部网络的乱解析? >

因为 Nginx 不会自动读取当前系统中的 DNS resolver 配置。

非阻塞访问远方 http 服务有两种做法。下面以从 ngx_lua 中访问百度搜索为例,演示一下这两种做法:

  1. 使用 nginx 子请求 + ngx_proxy 模块:

    resolver 114.114.114.114;

    location = /baidu {

    internal; proxy_pass http://www.baidu.com/s?wd=$arg_query;

    }

    location = /test {
    content_by_lua ‘
    local res =

    ngx.location.capture(“/baidu”, { args = { query = “openresty” }})

    if res.status ~= 200 then

    ngx.say(“failed to query baidu: ”, res.status, ”: ”, res.body) return

    end ngx.say(res.body)

    ‘;

    }

然后请求这里的 /test 接口便可以得到 baidu 里搜索 openresty 查询词时的 HTML 结果页(不过要仔细字符编码哦)。

  1. 使用 cosocket API 访问之:

    resolver 114.114.114.114;

    location = /test {
    content_by_lua ‘

    local sock = ngx.socket.tcp() sock:settimeout(1000) local ok, err = sock:connect(“www.baidu.com”, 80) if not ok then

    ngx.say(“failed to connect to baidu: ”, err) return

    end local req = “GET /s?wd=openresty HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n” sock:send(req) local read_headers = sock:receiveuntil(“\r\n\r\n”) headers, err = read_headers() if not headers then

    ngx.say(“failed to read response headers: ”, err) return

    end local body, err = sock:receive(“*a”) if not body then

    ngx.say(“failed to read response body: ”, err) return

    end ngx.say(body)

    ‘;

    }

这里直接用 TCP cosocket API 现写了一个简单的 HTTP 1.0 客户端访问 baidu.com,得到了 openresty 查询词的结果页。当然,未来等有人仿照上面的做法,专门基于 cosocket 实现了完整的 HTTP 客户端库 lua-resty-http 之后,便会更有方便,就像那些已经基于 cosocket 实现的 lua-resty-memcached, lua-resty-redis 和 lua-resty-mysql 库一样(见 https://github.com/agentzh/lua-resty-memcached ,还有 https://github.com/agentzh/lua-resty-redis 以及 https://github.com/agentzh/lua-resty-mysql

以且仅以上面两种方式访问远方服务在 ngx_lua 中才是非阻塞的。

Regards, -agentzh

怎么可以观察到 proxy_pass 真实进行请求的 url ?

> - 以便明确lua 的处理是否正确?

有两种方法:

一是使用 nc 工具。比如在本地用下面的命令启动 nc 以监听 1978 端口:

nc -l 1978

(有的系统会需要使用命令 nc -l -p 1978),然后临时修改你的 nginx 配置以便让 proxy_pass 指向它:

重新加载 nginx 配置后如从前那般请求你的接口,然后在启动 nc 的终端检查 ngx_proxy 发送的原始的 HTTP 请求。

  1. 重新编译 nginx 以启用 Nginx 调试日志,然后在 nginx 配置文件中使用 debug 日志级别。然后正常请求你的接口,在 Nginx 的 error.log 文件中应当会看到类似下面这样的 dump:

[debug] 20844#0: *1 http proxy header: “GET /foo?blah HTTP/1.0^M Host: 127.0.0.1:1987^M Connection: close^M ^M “

当然,使用 tcpdump 之类的抓包工具来搞也是可以的。

是也乎

这里的一个建议是,尽量不要对 ngx.var.request_uri 进行正则匹配,因为 Nginx 的标准变量 $request_uri 是未经过 URI 解码的原始形式,比如 “/a b” 和 “/a%20b” 是彼此等价的 URI,但作为字符串它们又是不相等的。建议改成分别对 ngx.var.uri 和 ngx.req.get_args()[“test”] 进行匹配。

可以看一看下面这个例子:

location /test {

default_type “text/plain”; content_by_lua ‘

ngx.say(“request uri: ”, ngx.var.request_uri) ngx.say(“uri: ”, ngx.var.uri) ngx.say(“arg test: ”, ngx.var.arg_test) ngx.say(“arg test 2: ”, ngx.req.get_uri_args()[“test”])

‘;

}

假设当前 nginx 监听的是本机的 1984 端口,则当在 Firefox 地址栏里输入下面这个 URL 时,

会得到这样的页面输出:

request uri: /test%20it?test=a%20b uri: /test it arg test: a%20b arg test 2: a b

我们看到,Firefox 作为编写良好的 HTTP 客户端,根据 RFC 的要求,自动把原始 URL 中的空格字符编码为了 %20 序列。

附录A: 脱水版 agentzh之Nginx教程

千呼万唤之下,终于:agentzh 的 Nginx 教程(版本 2012.03.11) - http://agentzh.org/misc/nginx/agentzh-nginx-tutorials-zhcn.html - 算是成了系列

但是! - agentzh 真心看原文书多了 - 行文没有了中国教材的权威感觉 - 一步步,反复演示/引导,分享了对于 Nginx 的各种血淋淋的细节技术经验

可是~ - 我们就想要快速知道怎么玩吼 - 认同你 agentzh 的人品, OpenResty 的品质了 - 不甭什么内核细节了,告诉俺怎么用就对才好!

所以,俺根据自个儿的理解,脱水压缩系列 blog 成为条目,以便快速掌握,,,

警告

  • 完全 Zoom.Quiet 私人理解
  • 不代表 OpenResty 官方学识
  • 大家自个儿看着办,俺尽量负责了,,, Orz ...

Nginx 变量使用守则

问题
分析
解决

Nginx 配置指令执行顺序 守则

问题
分析
解决

关于作者

  • Zoom.Quiet 是也乎;-)

    • Python 中文社区创始人 / 管理员之一,热心于各种开源技术社区的公众事业,大家熟知的社区”大妈”;OBP及蟒营工程设计者

修订记要

All tests successful. Files=7, Tests=28, 2 wallclock secs ( 0.04 usr 0.02 sys + 1.12 cusr 0.11 csys = 1.29 CPU) Result: PASS

AGENT/Test-Nginx-0.18.tar.gz /usr/bin/make test – OK

Running make install Installing /Library/Perl/5.12/Test/Nginx.pm Installing /Library/Perl/5.12/Test/Nginx/LWP.pm Installing /Library/Perl/5.12/Test/Nginx/Socket.pm Installing /Library/Perl/5.12/Test/Nginx/Util.pm Installing /usr/local/share/man/man3/Test::Nginx.3pm Installing /usr/local/share/man/man3/Test::Nginx::LWP.3pm Installing /usr/local/share/man/man3/Test::Nginx::Socket.3pm Appending installation info to /Library/Perl/Updates/5.12.3/darwin-thread-multi-2level/perllocal.pod

AGENT/Test-Nginx-0.18.tar.gz /usr/bin/make install – OK

zoomq @ Zq111216MBP in ~/Works/lua/org.openresty $ sudo cpan Test::Nginx

基于结构化文本~ rsticon 的图书工程

Shinx 说明

参考:

本书行文体例

本书使用不同的体例来区分不同的情景.

精巧地址

本书包含很多外部网站的URL地址,但是图书必竟不是网页,读者无法点击进入相关网站;所以,笔者尝试使用URL精简工具来帮助读者可以快速输入自动跳转到原有网站来访问;

  • 比如说: 本书的维基入口 http://wiki.woodpecker.org.cn/moin/ObpLovelyPython
  • 精巧地址: http://bit.ly/2QA425
  • 输入的字符量少了三倍! 这是借助 http://bit.ly 提供的网址精简服务达到的效果;
  • 提醒:毕竟这是借用外国的免费服务进行的精简,如果读者输入后不能自动跳转的话,可能是网络问题也可能是服务问题,那就只能麻烦读者重新使用原有的URL进入了;
程序体例

使用有语法颜色的代码引用

def foo():
    print "Love Python, Love FreeDome"
    print "E文标点,.0123456789,中文标点,. "

可以 用 .. code-block:: 追加各种语法高亮声明:

.. code-block:: python
    :linenos:

    def foo():
        print "Love Python, Love FreeDome"
        print "E文标点,.0123456789,中文标点,. "

效果:

1
2
3
def foo():
    print "Love Python, Love FreeDome"
    print "E文标点,.0123456789,中文标点,. "

外部包含:

.. literalinclude:: example.py
    :language: python

效果:

@route('%s/'%ini.urlprefix)
def index():
    __urlog("INFO","idx++")
    return template('index.tpl',urlprefix=ini.urlprefix)

文本体例

引用,题词:

No matter where you go, there you are.

—Buckaroo Banzai

技巧警示:

注解

(~_~)

  • This icon signifies a tip, suggestion, or general note.

警告

(#_#)

  • 警告得注意的...

参见

(^.^)

  • 指向参考的...

附加说明:

进一步的

包含题外的信息,笔者心路,等等和正文有关,但是不直接的信息

知识引用:

  • 使用边注
  • 追随正文
  • 活动説明
  • 效果如右

rST排版技巧

跨章节指引
  • 行文中,经常要对其它章节进行指引,在 html 中对应的就是 锚点链接

  • rST 中提供了非常优雅的解决:
    • 使用通用元素定义
    • 比如説:
各个章节的首页一般是 index.rst
头一行,习惯性加个聲明:
.. _chapter2index:

那么,在其它任意文本中,随时可以使用:
:ref:`基本电子学 <chapter2index>`
来生成一个指向第二章 首页的链接!
插图/表格指代
  • 行文中,经常对指定插图/表格 进行指代

  • rST 中提供了非常优雅的解决:
    • 进行通用元素定义
    • 比如说
.. _fig_2_4:
.. figure:: _static/figs/tmux-curl-test.png

   插图 2-4 命令行测试情景

然后,就可以在任意地方使用 插图 2-4 命令行测试情景 来指代, 实际输出的就是 “插图 2-4 命令行测试情景”

_static/figs/tmux-curl-test.png

插图 2-4 命令行测试情景

上下标号

有时要进行数学/化学的表示,在 html 中就需要上/下标( <sub> , <sup>) 的表达, rST 中当然也有:

H\ :sub:`2`\ O
E = mc\ :sup:`2`

效果:

H2O

E = mc2

注解

注意:

这里的 只是为了制造语法空间,输出时,是没有空格的了,,,

线性表格

中文的非等宽性导致 rST 这种字符艺术式的图表很难作!

=====  =====
 A    not A
=====  =====
False  True
True   False
=====  =====

所以,使用列表也可以方便的生成表格:

.. list-table:: 实例
   :widths: 15 10 30
   :header-rows: 1

   * - Treat
     - Quantity
     - Description
   * - Albatross
     - 2.99
     - On a stick!
   * - Crunchy Frog
     - 1.49
     - If we took the bones out, it wouldn't be
       crunchy, now would it?
   * - Gannet Ripple
     - 1.99
     - On a stick!

效果

实例
Treat Quantity Description
Albatross 2.99 On a stick!
Crunchy Frog 1.49 If we took the bones out, it wouldn’t be crunchy, now would it?
Gannet Ripple 1.99 On a stick!
段落层次约定

使用 reSTsections

共分4级
=======================
大标题
=======================


-----------------------
小标题
-----------------------


^^^^^^^^^^^^^^^^^^^^^^^
二级标题
^^^^^^^^^^^^^^^^^^^^^^^


"""""""""""""""""""""""
三级标题
"""""""""""""""""""""""

再小,就使用列表!:

  • 列表项目1
  • 列表项目2
  • ...

效果:

大标题

小标题

二级标题
三级标题