diff --git a/.gitignore b/.gitignore index 05d67e1..21388f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,16 @@ *.o *.swp *_temp/ -*logs/ +*.log *.log *.pid *.out *.rdb t/servroot/ *temp/ -*.sh +*.pyc -*stdout +*stdout* +stdout +*.log diff --git a/LICENSE b/LICENSE index d5802f8..2720490 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 渣浪研发中心技术保障部移动端保障团队 +Copyright (c) 2016 CNSRE 技术保障团队 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index af934da..c416fa0 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,25 @@ -基于动态策略的灰度发布系统 -======================== -ABTestingGateway 是一个可以动态设置分流策略的灰度发布系统,工作在7层,基于[nginx](http://nginx.org/)和[ngx-lua](https://github.com/openresty/lua-nginx-module)开发,使用 redis 作为分流策略数据库,可以实现动态调度功能。 - -nginx是目前使用较多的7层服务器,可以实现高性能的转发和响应;ABTestingGateway 是在 nginx 转发的框架内,在转向 upstream 前,根据 用户请求特征 和 系统的分流策略 ,查找出目标upstream,进而实现分流。 - -在以往的基于 nginx 实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和 rewrite 指令等实现,优点是`性能较高`,缺点是`功能受限`、`容易出错`,以及`转发规则固定,只能静态分流`。针对这些缺点,我们设计实现了ABTestingGateway,采用 ngx-lua 实现系统功能,通过启用[lua-shared-dict](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)和[lua-resty-lock](https://github.com/openresty/lua-resty-lock)作为系统缓存和缓存锁,系统获得了较为接近原生nginx转发的性能。 -

ABTestingGateway 的架构简图

+基于动态策略的灰度发布系统 +========================= -ABTestingGateway 项目是新浪研发中心技术保障部移动端保障团队出品,在[@平凡的香草](http://weibo.com/chunshengster)的指导下完成。 +* ABTestingGateway 是一个可以动态设置分流策略的灰度发布系统,工作在7层,基于nginx和ngx-lua开发,使用 redis 作为分流策略数据库,可以实现动态调度功能。 -在此特别感谢: +* nginx是目前使用较多的7层服务器,可以实现高性能的转发和响应;ABTestingGateway 是在 nginx 转发的框架内,在转向 upstream 前,根据 用户请求特征 和 系统的分流策略 ,查找出目标upstream,进而实现分流。 -- 王春生 微博:[@平凡的香草](http://weibo.com/chunshengster) -- 王发康 微博:[@王发康博客](http://weibo.com/fakangwang) -- 冯磊 微博:[@冯磊424](http://weibo.com/myfenglei) -- 金普 微博:[@jinpulym](http://weibo.com/jinpulym) -- 何凤存 微博:[@哈喽易](http://weibo.com/p/1005055196369143) -- 黄振栋 微博:[@BG2BKK](http://weibo.com/bg2bkk) +* ABTestingGateway 是新浪微博内部的动态路由系统 dygateway 的一部分,因此本文档中的 dygateway 主要是指其子功能 ABTestingGateway。动态路由系统dygateway目前应用于手机微博7层、微博头条等产品线。 -等同学的支持。 +在以往的基于 nginx 实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和 rewrite 指令等实现,优点是`性能较高`,缺点是`功能受限`、`容易出错`,以及`转发规则固定,只能静态分流`。针对这些缺点,我们设计实现了ABTestingGateway,采用 ngx-lua 实现系统功能,通过启用[lua-shared-dict](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)和[lua-resty-lock](https://github.com/openresty/lua-resty-lock)作为系统缓存和缓存锁,系统获得了较为接近原生nginx转发的性能。 -感谢大家对 ABTestingGateway 的帮助。 +

ABTestingGateway 的架构简图

如果在使用过程中有任何问题,欢迎大家来吐槽,一起完善、一起提高、一起使用! email: bg2bkk@gmail.com open.hfc@gmail.com +压测数据:[压测报告](https://github.com/WEIBOMSRE/ABTestingGateway/blob/master/doc/%E7%81%B0%E5%BA%A6%E5%8F%91%E5%B8%83%E7%B3%BB%E7%BB%9F%E5%8E%8B%E6%B5%8B%E6%8A%A5%E5%91%8A.pdf) + +项目演讲:[演讲文档](https://github.com/WEIBOMSRE/ABTestingGateway/blob/master/doc/%E5%9F%BA%E4%BA%8E%E5%8A%A8%E6%80%81%E7%AD%96%E7%95%A5%E7%9A%84%E7%81%B0%E5%BA%A6%E5%8F%91%E5%B8%83%E7%B3%BB%E7%BB%9F.pdf) + Features: ---------- @@ -37,164 +30,135 @@ Features: - 灰度系统配置写在nginx配置文件中,方便管理员配置 - 适用于多种场景:灰度发布、AB测试和负载均衡等 -系统实现 ------------- -###分流功能: -转发分流是灰度系统的主要功能,目前 ABTestingGateway 支持 `ip段分流(iprange)`、`uid用户段分流(uidrange)`、`uid尾数分流(uidsuffix)` 和 `指定特殊uid分流(uidappoint)` 四种方式。 +- new feature: ***支持多级分流*** -ABTestingGateway 依据系统中配置的 `运行时信息runtimeInfo` 进行分流工作;通过将 runtimeInfo 设置为不同的分流策略,实现运行时分流策略的动态更新,达到动态调度的目的。 +灰度发布系统功能简介 +------------------- -1. 系统运行时信息设置 +对于ab管理功能而言,步骤是以下三步: -
-如图所示 +1. 向系统添加策略,将策略写入策略数据库中 +1. 为具体的server设置运行时信息,将某个分流策略设置为运行时策略 +1. 之后可以进行分流操作 - - 系统管理员通过系统管理接口将`分流策略policy`设置为`运行时策略`,并指定该策略对应的 `分流模块名divModulename` 和 `用户信息提取模块名userInfoModulename` 后,系统可以进行分流工作。 - - 系统对用户请求进行分流时,首先获得系统 `运行时信息runtimeInfo` 中的信息,然后提取 `用户特征userInfo`,最后 `分流模块divModule` 根据 `分流策略dviDataKey` 和 `用户特征userInfo` 查找出应该转发到的upstream。如果没有对应的upstream,则将该请求转向默认upstream。 +详细解释参见: [ab分流功能须知](doc/ab功能须知.md) -2. 以iprange分流为例 - - 以某个iprange分流策略为例: - { - "divtype":"iprange", - "divdata":[ - {"range":{"start":1111, "end":2222}, "upstream":"beta1"}, - {"range":{"start":3333, "end":4444}, "upstream":"beta2"}, - {"range":{"start":7777, "end":8888}, "upstream":"beta3"} - ] - } -其中divdata中的每个 range:upstream 对中,range 为 ip 段,upstream 为 ip 段对应的后端;range 中的 start 和 end 分别为 ip 段的起始和终止, ip以整型表示。 -当灰度系统启用iprange分流方式时,会根据用户请求的ip进行分流转发。 -假如用户请求中的ip信息转为整型后是4000,将被转发至beta2 upstream。 +对于ab分流功能而言,分流流程图如图所示 -目前灰度系统支持的其他分流方式的策略样例在**utils/conf/policy_sample** +

分流过程流程图

-3. 分流过程流程图 -

分流过程流程图

- -###管理功能: -

管理功能架构图

- - 1. 管理员登入后,得到系统信息视图,运行时信息视图,可以进行策略管理和运行时信息管理 - 2. 业务接口层向管理员提供 增/删/查/改 接口 - 3. 适配层将承担业务接口与分流模块的沟通工作 - 4. 适配层提出统一接口,开发人员可以通过实现接口来添加新的分流方式 - -####管理接口: -
-[策略管理接口]	
-    #分流策略检查,参数为一个分流策略数据的json串
-    1. /admin/policy/check
-    #分流策略添加,参数与check接口一致
-    2. /admin/policy/set
-    #分流策略读取,参数为要读取策略的policyid
-    3. /admin/policy/get
-    #分流策略删除,参数为要删除策略的policyid
-    4. /admin/policy/del
-
-[运行时信息管理接口]
-    #设置分流策略为运行时策略,参数为policyid
-    1. /admin/runtime/set
-    #获取系统当前运行时信息,无参数
-    2. /admin/runtime/get
-    #删除系统运行时信息,关闭分流接口,无参数
-    3. /admin/runtime/del
-
- -快速部署 ----------- +系统部署 +========================= + +软件依赖 +------------------ -###软件依赖 -- tengine-2.1.0 -- LuaJIT-2.1-20141128 -- ngx_lua-0.9.13 -- lua-cjson-2.1.0.2 -- redis-2.8.19 +* openresty +* ngx_lua +* LuaJIT +* lua-cjson +* redis +> > 注意:建议选用openresty最新版,但是从openresty-1.9.15.1开始,lua-resty-core有些api变更,因此建议先使用openresty-1.9.7.5,原因是:[必读](https://github.com/CNSRE/ABTestingGateway/issues/27#issuecomment-236149255) -###系统部署 +> > 注意:tengine用户仍然可以使用本项目,只需要从openresty软件包中获取最新的ngx_lua、LuaJIT以及lua-cjson等,并注意:[必读](https://github.com/CNSRE/ABTestingGateway/issues/27#issuecomment-236149255) + +how to start +----------------------- repo中的`utils/conf`文件夹中有灰度系统部署所需的最小示例 -
+> 目前repo的master分支是支持多级分流的版本,如果只想体验单级分流,可以fork single_diversion_release分支,具体文档都在相关分支的readme中。
+
+```bash
 1. git clone https://github.com/SinaMSRE/ABTestingGateway
-2. cd /path/to/ABTestingGateway/utils
+2. cd /path/to/ABTestingGateway/utils && mkdir logs
 
-#启动redis数据库
+# 启动redis数据库
 3. redis-server conf/redis.conf 
 
-#启动upstream server,其中stable为默认upstream
+# 启动upstream server,其中stable为默认upstream
 4. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/stable.conf
 5. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta1.conf
 6. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta2.conf
 7. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta3.conf
 8. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta4.conf
 
-#启动灰度系统,proxy server,灰度系统的配置也写在conf/nginx.conf中
+# 启动灰度系统,proxy server,灰度系统的配置也写在conf/nginx.conf中
 9. /usr/local/nginx/sbin/nginx -p `pwd` -c conf/nginx.conf
-
- -###灰度系统使用demo --------------- -1. 管理功能 - - 1. 部署并启动系统 - - 2. 查询系统运行时信息,得到null - 0> curl 127.0.0.1:8030/admin/runtime/get - {"errcode":200,"errinfo":"success ","data":{"divModulename":null,"divDataKey":null,"userInfoModulename":null}} - - 3. 查询id为9的策略,得到null - 0> curl 127.0.0.1:8030/admin/policy/get?policyid=9 - {"errcode":200,"errinfo":"success ","data":{"divdata":null,"divtype":null}} - - 4. 向系统添加策略,返回成功,并返回新添加策略的policyid - 以uidsuffix尾数分流方式为例,示例分流策略为: - { - "divtype":"uidsuffix", - "divdata":[ - {"suffix":"1", "upstream":"beta1"}, - {"suffix":"3", "upstream":"beta2"}, - {"suffix":"5", "upstream":"beta1"}, - {"suffix":"0", "upstream":"beta3"} - ] - } - 添加分流策略接口 /admin/policy/set 接受json化的policy数据 - 0> curl 127.0.0.1:8030/admin/policy/set -d '{"divtype":"uidsuffix","divdata":[{"suffix":"1","upstream":"beta1"},{"suffix":"3","upstream":"beta2"},{"suffix":"5","upstream":"beta1"},{"suffix":"0","upstream":"beta3"}]}' - {"errcode":200,"errinfo":"success the id of new policy is 0"} - - 5. 查看添加结果 - 0> curl 127.0.0.1:8030/admin/policy/get?policyid=0 - {"errcode":200,"errinfo":"success ","data":{"divdata":["1","beta1","3","beta2","5","beta1","0","beta3"],"divtype":"uidsuffix"}} - - 6. 设置系统运行时策略为 0号策略 - 0> curl 127.0.0.1:8030/admin/runtime/set?policyid=0 - {"errcode":200,"errinfo":"success "} - - 7. 查看系统运行时信息,得到结果 - 0> curl 127.0.0.1:8030/admin/runtime/get - {"errcode":200,"errinfo":"success ","data":{"divModulename":"abtesting.diversion.uidsuffix","divDataKey":"ab:test:policies:0:divdata","userInfoModulename":"abtesting.userinfo.uidParser"}} - - 8. 当访问接口不正确返回时,将返回相应的 错误码 和 错误描述信息 - 0> curl 127.0.0.1:8030/admin/policy/get?policyid=abc - {"errcode":50104,"errinfo":"parameter type error for policyID should be a positive Integer"} - - -2. 分流功能 - - 在验证管理功能通过,并设置系统运行时策略后,开始验证分流功能 - - 1. 分流,不带用户uid,转发至默认upstream - 0> curl 127.0.0.1:8030/ - this is stable server - - 2. 分流,带uid为30,根据策略,转发至beta3 - 0> curl 127.0.0.1:8030/ -H 'X-Uid:30' - this is beta3 server - - 3. 分流,带uid为33,根据策略,转发至beta2 - 0> curl 127.0.0.1:8030/ -H 'X-Uid:33' - this is beta2 server + +# 简单验证:添加分流策略组 +$ curl 127.0.0.1:8080/ab_admin?action=policygroup_set -d '{"1":{"divtype":"uidsuffix","divdata":[{"suffix":"1","upstream":"beta1"},{"suffix":"3","upstream":"beta2"},{"suffix":"5","upstream":"beta1"},{"suffix":"0","upstream":"beta3"}]},"2":{"divtype":"arg_city","divdata":[{"city":"BJ","upstream":"beta1"},{"city":"SH","upstream":"beta2"},{"city":"XA","upstream":"beta1"},{"city":"HZ","upstream":"beta3"}]},"3":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":2130706433},"upstream":"beta2"}]}}' + +{"desc":"success ","code":200,"data":{"groupid":0,"group":[0,1,2]}} + +# 简单验证:设置运行时策略 + +$ curl "127.0.0.1:8080/ab_admin?action=runtime_set&hostname=api.weibo.cn&policygroupid=0" + +# 分流 +$ curl 127.0.0.1:8030 -H 'X-Uid:39' -H 'X-Real-IP:192.168.1.1' +this is stable server + +$ curl 127.0.0.1:8030 -H 'X-Uid:30' -H 'X-Real-IP:192.168.1.1' +this is beta3 server + +$ curl 127.0.0.1:8030/?city=BJ -H 'X-Uid:39' -H 'X-Real-IP:192.168.1.1' +this is beta1 server + +``` + +配置过程 +------------------------ + +* 由于内部部署时以dygateway项目名部署,因此下文中的所有配置,都应将ABTestingGateway文件夹重命名为dygateway + +[nginx.conf配置过程](doc/nginx_conf_配置过程.md) + +[redis.conf配置过程](doc/redis_conf_配置过程.md) + + +系统使用 +========================= + +ab分流策略 +----------------- + +* ab功能目前支持的分流策略有 + * iprange: ip段分流 + * uidrange: 用户uid段分流 + * uidsuffix: uid尾数分流 + * uidappoint: uid白名单分流 + +* 可以灵活添加新的分流方式 + +[ab分流策略格式和样例](doc/ab分流策略.md) + +ab功能接口 +------------------ + +* [ab功能接口说明文档](doc/ab功能接口使用介绍.md) + +```bash + * 策略管理:增删改查 + /ab_admin?action=policy_check + /ab_admin?action=policy_set + /ab_admin?action=policy_get + /ab_admin?action=policy_del + + * 策略组管理(用于多级分流) + /ab_admin?action=policygroup_check + /ab_admin?action=policygroup_set + /ab_admin?action=policygroup_get + /ab_admin?action=policygroup_del + + * 运行时信息管理 + * 其中runtime_set接受policyid和policygroupid参数,分别用于单级分流和多级分流 + + /ab_admin?action=runtime_get + /ab_admin?action=runtime_set + /ab_admin?action=runtime_del +``` 压测结果: ----------- @@ -213,8 +177,60 @@ proxy server的硬件配置: - Mem:24GB - Nic:千兆网卡,多队列,理论流量峰值为125MB/s +* 注:压测结果是单级分流模式的压力测试结果,多级压测与单级压测的数据像差不多,因为ngx_lua的执行时间仅占ab功能的小部分,瓶颈不在于此 + + 线上部署简图: -----------
+FAQ +------------------------------- + +* 启动时报错,[提示ngx_http_lua_ffi_semaphore_new未定义](https://github.com/CNSRE/ABTestingGateway/issues/27) + * 这个是ngx_lua比较新的版本中带有的 resty.semaphore模块,请确定ngx_lua版本包含该模块 + * LuaJIT也请采用openresty软件包中提供的版本 + * 其实直接采用openresty最好,使用最新版本的openresty不会用软件依赖问题,只是我所在的项目组有tengine的需求,所以用tengine +* 为什么都root用户了还会提示Permission denied, [solution](https://github.com/CNSRE/ABTestingGateway/issues/28) + +TODO LIST +---------------------------- + +* 开发nginx shm storage模块,扩展 ngx-shared-dict 的功能 + * 目前 ngx-shared-dict 提供高效快速的 kv 式简单存储 + * 简单高效的存储,不足之处最直接的体现在不支持缓存 lua table。[lua-resty-lrucache提供LuaVM级别的缓存,不能跨worker共享] + * 目前团队同学基于红黑树实现了类似于ngx-shared-dict的存储功能,可以存储任意类型,查找方式由用户自定义。项目地址:[ngx-shared-rbtree](https://github.com/helloyi/ngx-lua-shrbtree-module) + * ngx-shared-rbtree的不足之处在于“模块只能存储一颗红黑树”,不能实现复杂用法,因此需要被进一步扩展。类似于如下所示的使用方法: + +```bash + +local dict = ngx.shared.ngxdb +----------------------------------- + +local db_tab = "db_tab" +local k_tab = {["a"]=1, ["b"]=2} +local v_tab = {1, 2, 3} + +dict:set(db_tab, k_tab, v_tab) + +local v_tab = dict:get(db_tab, k_tab) +----------------------------------- + +local db_kv = "db_kv" +local k = "foo" +local v = "bar" + +dict:set(db_kv, k, v) + +local getv = dict:get(db_kv, v) +----------------------------------- +``` +* 扩展ABTestingGateway的功能 + * 由于该项目最初是为手机微博7层开发的,所以只关注了分流功能。 + * 而项目在公司内部使用过程中发现,不同业务对于ab项目这层转发工作的期许不太一样 + * 有的业务需要将用户分流到不同的服务器上 + * 有的业务需要在这步处理中根据策略增减用户请求的uri参数或者header头 + * 所以扩展ab项目的这层转发很有必要,目前随着在公司内部的推广,也在不停的收集需求,探索新的玩法 + * 其实整个AB项目并没有太高的技术含量,大家关于ab和分流的玩法都大同小异,所不同的是,我们在这种范式下,能发出多少有趣的花样,期待大家多多交流 +* 逐步开源dygateway的所有功能 diff --git a/admin/ab_action.lua b/admin/ab_action.lua new file mode 100644 index 0000000..7c85248 --- /dev/null +++ b/admin/ab_action.lua @@ -0,0 +1,92 @@ +local policyModule = require('abtesting.adapter.policy') +local redisModule = require('abtesting.utils.redis') +local systemConf = require('abtesting.utils.init') +local handler = require('abtesting.error.handler').handler +local utils = require('abtesting.utils.utils') +local log = require('abtesting.utils.log') +local ERRORINFO = require('abtesting.error.errcode').info +local policy = require("admin.policy") +local runtime = require('admin.runtime') +local policygroup = require("admin.policygroup") + +local cjson = require('cjson.safe') +local doresp = utils.doresp +local dolog = utils.dolog + +local redisConf = systemConf.redisConf +local divtypes = systemConf.divtypes +local prefixConf = systemConf.prefixConf +local policyLib = prefixConf.policyLibPrefix +local runtimeLib = prefixConf.runtimeInfoPrefix +local domain_name = prefixConf.domainname + +local ab_action = {} + +ab_action.policy_check = policy.check +ab_action.policy_set = policy.set +ab_action.policy_get = policy.get +ab_action.policy_del = policy.del + +ab_action.runtime_set = runtime.set +ab_action.runtime_del = runtime.del +ab_action.runtime_get = runtime.get + + +ab_action.policygroup_check = policygroup.check +ab_action.policygroup_set = policygroup.set +ab_action.policygroup_get = policygroup.get +ab_action.policygroup_del = policygroup.del + + +local get_uriargs_error = function() + local info = ERRORINFO.ACTION_BLANK_ERROR + local response = doresp(info, 'user req') + log:errlog(dolog(info, desc)) + ngx.say(response) + return +end + +local get_action_error = function() + local info = ERRORINFO.ACTION_BLANK_ERROR + local response = doresp(info, 'user req') + log:errlog(dolog(info, desc)) + ngx.say(response) + return +end + +local do_action_error = function(action) + local info = ERRORINFO.DOACTION_ERROR + local desc = action + local response = doresp(info, desc) + local errlog = dolog(info, desc) + log:errlog(errlog) + ngx.say(response) + return +end + +local red = redisModule:new(redisConf) +local ok, err = red:connectdb() +if not ok then + local info = ERRORINFO.REDIS_CONNECT_ERROR + local response = doresp(info, err) + log:errlog(dolog(info, desc)) + ngx.say(response) + return +end + +local args = ngx.req.get_uri_args() +if args then + local action = args.action + local do_action = ab_action[action] + if do_action then + do_action({['db']=red}) +-- local ok, info = do_action(policy, {['db']=red}) +-- if not ok then +-- do_action_error() +-- end + else + do_action_error(action) + end +else + get_uriargs_error() +end diff --git a/admin/action.lua b/admin/action.lua new file mode 100644 index 0000000..9a6d1aa --- /dev/null +++ b/admin/action.lua @@ -0,0 +1,83 @@ +local policyModule = require('abtesting.adapter.policy') +local redisModule = require('abtesting.utils.redis') +local systemConf = require('abtesting.utils.init') +local handler = require('abtesting.error.handler').handler +local utils = require('abtesting.utils.utils') +local ERRORINFO = require('abtesting.error.errcode').info + +local cjson = require('cjson.safe') +local doresp = utils.doresp +local dolog = utils.dolog + +local redisConf = systemConf.redisConf +local divtypes = systemConf.divtypes +local prefixConf = systemConf.prefixConf +local policyLib = prefixConf.policyLibPrefix +local runtimeLib = prefixConf.runtimeInfoPrefix +local domain_name = prefixConf.domainname + + +local policy = require("admin/policy") +local runtime = require('admin/runtime') +local ab_action = {} + +ab_action.policy_check = policy.check +ab_action.policy_set = policy.set +ab_action.policy_get = policy.get +ab_action.policy_del = policy.del + +ab_action.runtime_set = runtime.set +ab_action.runtime_del = runtime.del +ab_action.runtime_get = runtime.get + +local get_uriargs_error = function() + local info = ERRORINFO.ACTION_BLANK_ERROR + local response = doresp(info, 'user req') + dolog(info, desc) + ngx.say(response) + return +end + +local get_action_error = function() + local info = ERRORINFO.ACTION_BLANK_ERROR + local response = doresp(info, 'user req') + dolog(info, desc) + ngx.say(response) + return +end + +local do_action_error = function() + local info = ERRORINFO.DOACTION_ERROR + local desc = action + local response = doresp(info, desc) + dolog(info, desc) + ngx.say(response) + return +end + +local red = redisModule:new(redisConf) +local ok, err = red:connectdb() +if not ok then + local info = ERRORINFO.REDIS_CONNECT_ERROR + local response = doresp(info, err) + dolog(info, desc) + ngx.say(response) + return +end + +local args = ngx.req.get_uri_args() +if args then + local action = args.action + local do_action = ab_action[action] + if do_action then + do_action({['db']=red}) +-- local ok, info = do_action(policy, {['db']=red}) +-- if not ok then +-- do_action_error() +-- end + else + doaction_error() + end +else + get_uriargs_error() +end diff --git a/admin/policy.lua b/admin/policy.lua new file mode 100644 index 0000000..d7da279 --- /dev/null +++ b/admin/policy.lua @@ -0,0 +1,236 @@ +--- +-- @classmod abtesting.adapter.policy +-- @release 0.0.1 +local modulename = "abtestingAdminPolicy" + +local _M = { _VERSION = "0.0.1" } +local mt = { __index = _M } + +local ERRORINFO = require('abtesting.error.errcode').info + +local runtimeModule = require('abtesting.adapter.runtime') +local policyModule = require('abtesting.adapter.policy') +local redisModule = require('abtesting.utils.redis') +local systemConf = require('abtesting.utils.init') +local handler = require('abtesting.error.handler').handler +local utils = require('abtesting.utils.utils') +local log = require('abtesting.utils.log') +local ERRORINFO = require('abtesting.error.errcode').info + +local cjson = require('cjson.safe') +local doresp = utils.doresp +local dolog = utils.dolog +local doerror = utils.doerror + +local redisConf = systemConf.redisConf +local divtypes = systemConf.divtypes +local prefixConf = systemConf.prefixConf +local policyLib = prefixConf.policyLibPrefix +local runtimeLib = prefixConf.runtimeInfoPrefix +local domain_name = prefixConf.domainname + +local getPolicyId = function() + local policyID = tonumber(ngx.var.arg_policyid) + + if not policyID or policyID < 0 then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "policyID invalid" + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + return policyID +end + +local getPolicy = function() + + local request_body = ngx.var.request_body + local postData = cjson.decode(request_body) + + if not request_body then + -- ERRORCODE.PARAMETER_NONE + local errinfo = ERRORINFO.PARAMETER_NONE + local desc = 'request_body or post data' + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + if not postData then + -- ERRORCODE.PARAMETER_ERROR + local errinfo = ERRORINFO.PARAMETER_ERROR + local desc = 'postData is not a json string' + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + local divtype = postData.divtype + local divdata = postData.divdata + + if not divtype or not divdata then + -- ERRORCODE.PARAMETER_NONE + local errinfo = ERRORINFO.PARAMETER_NONE + local desc = "policy divtype or policy divdata" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + if not divtypes[divtype] then + -- ERRORCODE.PARAMETER_TYPE_ERROR + local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "unsupported divtype" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + return postData + +end + +_M.check = function(option) + local db = option.db + + local policy = getPolicy() + if not policy then + return false + end + + local pfunc = function() + local policyMod = policyModule:new(db.redis, policyLib) + return policyMod:check(policy) + end + + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local chkout = info + local valid = chkout[1] + local err = chkout[2] + local desc = chkout[3] + + local response + if not valid then + log:errlog(dolog(err, desc)) + response = doresp(err, desc) + else + response = doresp(ERRORINFO.SUCCESS) + end + ngx.say(response) + return true + +end + +_M.set = function(option) + local db = option.db + + local policy = getPolicy() + if not policy then + return false + end + + local pfunc = function() + policyMod = policyModule:new(db.redis, policyLib) + return policyMod:check(policy) + end + + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local chkout = info + local valid = chkout[1] + local err = chkout[2] + local desc = chkout[3] + + if not valid then + log:errlog(dolog(err, desc)) + local response = doresp(err, desc) + ngx.say(response) + return false + end + + local pfunc = function() return policyMod:set(policy) end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + local data + if info then + data = ' the id of new policy is '..info + end + + local response = doresp(ERRORINFO.SUCCESS, data) + ngx.say(response) + return true + +end + +_M.del = function(option) + local db = option.db + + local policyId = getPolicyId() + if not policyId then + return false + end + + local pfunc = function() + local policyMod = policyModule:new(db.redis, policyLib) + return policyMod:del(policyId) + end + + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true +end + +_M.get = function(option) + local db = option.db + + local policyId = getPolicyId() + if not policyId then + return false + end + + local pfunc = function() + local policyIO = policyModule:new(db.redis, policyLib) + return policyIO:get(policyId) + end + + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + else + local response = doresp(ERRORINFO.SUCCESS, nil, info) + log:errlog(dolog(ERRORINFO.SUCCESS, nil)) + ngx.say(response) + return true + end + +end + +return _M diff --git a/admin/policy/check.lua b/admin/policy/check.lua deleted file mode 100644 index 176e4f5..0000000 --- a/admin/policy/check.lua +++ /dev/null @@ -1,103 +0,0 @@ -local policyModule = require('abtesting.adapter.policy') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info - -local cjson = require('cjson.safe') -local doresp = utils.doresp -local dolog = utils.dolog - -local redisConf = systemConf.redisConf -local divtypes = systemConf.divtypes -local prefixConf = systemConf.prefixConf -local policyLib = prefixConf.policyLibPrefix - -local request_body = ngx.var.request_body -local postData = cjson.decode(request_body) - -if not request_body then - -- ERRORCODE.PARAMETER_NONE - local errinfo = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data' - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -if not postData then - -- ERRORCODE.PARAMETER_ERROR - local errinfo = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -local divtype = postData.divtype -local divdata = postData.divdata - -if not divtype or not divdata then - -- ERRORCODE.PARAMETER_NONE - local errinfo = ERRORINFO.PARAMETER_NONE - local desc = "policy divtype or policy divdata" - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -if not divtypes[divtype] then - -- ERRORCODE.PARAMETER_TYPE_ERROR - local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "unsupported divtype" - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(errinfo, err) - dolog(errinfo, err) - ngx.say(response) - return -end - -local pfunc = function() - local policyMod = policyModule:new(red.redis, policyLib) - local policy = postData - return policyMod:check(policy) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local chkout = info -local valid = chkout[1] -local err = chkout[2] -local desc = chkout[3] - -local response -if not valid then - dolog(err, desc) - response = doresp(err, desc) -else - response = doresp(ERRORINFO.SUCCESS) -end -ngx.say(response) - diff --git a/admin/policy/del.lua b/admin/policy/del.lua deleted file mode 100644 index 83ca471..0000000 --- a/admin/policy/del.lua +++ /dev/null @@ -1,143 +0,0 @@ -local runtimeModule = require('abtesting.adapter.runtime') -local policyModule = require('abtesting.adapter.policy') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info - -local cjson = require('cjson.safe') -local doresp = utils.doresp -local dolog = utils.dolog - -local redisConf = systemConf.redisConf -local divtypes = systemConf.divtypes -local prefixConf = systemConf.prefixConf -local policyLib = prefixConf.policyLibPrefix -local runtimeLib = prefixConf.runtimeInfoPrefix -local domain_name = prefixConf.domainname - -local policyID = ngx.var.arg_policyid - -if policyID then - policyID = tonumber(ngx.var.arg_policyid) - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - -if not policyID then - local request_body = ngx.var.request_body - local postData = cjson.decode(request_body) - - if not request_body then - -- ERRORCODE.PARAMETER_NONE - local info = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data to get policyID' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not postData then - -- ERRORCODE.PARAMETER_ERROR - local info = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = postData.policyid - - if not policyID then - local info = ERRORINFO.PARAMETER_ERROR - local desc = "policyID is needed" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = tonumber(postData.policyid) - - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local info = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(info, err) - dolog(info, desc) - ngx.say(response) - return -end - - -local pfunc = function() - local runtimeMod = runtimeModule:new(red.redis, runtimeLib) - return runtimeMod:get(domain_name) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local divModname = info[1] -local divPolicy = info[2] -local userInfoModname = info[3] - -if divPolicy and divPolicy ~= ngx.null then - local s, e = string.find(divPolicy, policyLib) - if s and e then - local sub = string.sub(divPolicy, e+1) - local _, _, num = string.find(sub, "(%d+)") - num = tonumber(num) - if num and num == policyID then - local response = doresp(ERRORINFO.POLICY_BUSY_ERROR, policyID) - ngx.say(response) - return - end - end -end - -local pfunc = function() - local policyMod = policyModule:new(red.redis, policyLib) - return policyMod:del(policyID) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local response = doresp(ERRORINFO.SUCCESS) -ngx.say(response) diff --git a/admin/policy/get.lua b/admin/policy/get.lua deleted file mode 100644 index d75036c..0000000 --- a/admin/policy/get.lua +++ /dev/null @@ -1,107 +0,0 @@ -local policyModule = require('abtesting.adapter.policy') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info -local cjson = require('cjson.safe') - -local redisConf = systemConf.redisConf -local divtypes = systemConf.divtypes -local prefixConf = systemConf.prefixConf -local policyLib = prefixConf.policyLibPrefix - -local doresp = utils.doresp -local dolog = utils.dolog - -local policyID = ngx.var.arg_policyid - -if policyID then - policyID = tonumber(ngx.var.arg_policyid) - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - -if not policyID then - local request_body = ngx.var.request_body - local postData = cjson.decode(request_body) - - if not request_body then - -- ERRORCODE.PARAMETER_NONE - local info = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data to get policyID' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not postData then - -- ERRORCODE.PARAMETER_ERROR - local info = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = postData.policyid - - if not policyID then - local info = ERRORINFO.PARAMETER_ERROR - local desc = "policyID is needed" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = tonumber(postData.policyid) - - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local info = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(info, err) - dolog(info, desc) - ngx.say(response) - return -end - -local pfunc = function() - local policyIO = policyModule:new(red.redis, policyLib) - return policyIO:get(policyID) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -else - local response = doresp(ERRORINFO.SUCCESS, nil, info) - dolog(ERRORINFO.SUCCESS, nil) - ngx.say(response) -end - diff --git a/admin/policy/set.lua b/admin/policy/set.lua deleted file mode 100644 index a606ccd..0000000 --- a/admin/policy/set.lua +++ /dev/null @@ -1,124 +0,0 @@ -local policyModule = require('abtesting.adapter.policy') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info - -local cjson = require('cjson.safe') -local doresp = utils.doresp -local dolog = utils.dolog - -local redisConf = systemConf.redisConf -local divtypes = systemConf.divtypes -local prefixConf = systemConf.prefixConf -local policyLib = prefixConf.policyLibPrefix - -local request_body = ngx.var.request_body -local postData = cjson.decode(request_body) - -if not request_body then - -- ERRORCODE.PARAMETER_NONE - local errinfo = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data' - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -if not postData then - -- ERRORCODE.PARAMETER_ERROR - local errinfo = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -local divtype = postData.divtype -local divdata = postData.divdata - -if not divtype or not divdata then - -- ERRORCODE.PARAMETER_NONE - local errinfo = ERRORINFO.PARAMETER_NONE - local desc = "policy divtype or policy divdata" - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -if not divtypes[divtype] then - -- ERRORCODE.PARAMETER_TYPE_ERROR - local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "unsupported divtype" - local response = doresp(errinfo, desc) - dolog(errinfo, desc) - ngx.say(response) - return -end - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - -- ERRORCODE.REDIS_CONNECT_ERROR - -- connect to redis error - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(errinfo, err) - dolog(errinfo, err) - ngx.say(response) - return -end - -local policyMod -local policy = postData - -local pfunc = function() - policyMod = policyModule:new(red.redis, policyLib) - return policyMod:check(policy) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local chkout = info -local valid = chkout[1] -local err = chkout[2] -local desc = chkout[3] - -if not valid then - dolog(err, desc) - local response = doresp(err, desc) - ngx.say(response) - return -end - -local pfunc = function() return policyMod:set(policy) end -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local data -if info then - data = ' the id of new policy is '..info -end - -local response = doresp(ERRORINFO.SUCCESS, data) -ngx.say(response) diff --git a/admin/policygroup.lua b/admin/policygroup.lua new file mode 100644 index 0000000..5fbe109 --- /dev/null +++ b/admin/policygroup.lua @@ -0,0 +1,269 @@ +--- +-- @classmod abtesting.adapter.policy +-- @release 0.0.1 +local modulename = "abtestingAdminPolicyGroup" + +local _M = { _VERSION = "0.0.1" } +local mt = { __index = _M } + +local ERRORINFO = require('abtesting.error.errcode').info + +local runtimeModule = require('abtesting.adapter.runtime') +local policyModule = require('abtesting.adapter.policy') +local redisModule = require('abtesting.utils.redis') +local systemConf = require('abtesting.utils.init') +local handler = require('abtesting.error.handler').handler +local utils = require('abtesting.utils.utils') +local log = require('abtesting.utils.log') +local ERRORINFO = require('abtesting.error.errcode').info + +local cjson = require('cjson.safe') +local doresp = utils.doresp +local dolog = utils.dolog +local doerror = utils.doerror + +local redisConf = systemConf.redisConf +local divtypes = systemConf.divtypes +local prefixConf = systemConf.prefixConf +local policyLib = prefixConf.policyLibPrefix +local runtimeLib = prefixConf.runtimeInfoPrefix +local domain_name = prefixConf.domainname + +local policyGroupModule = require('abtesting.adapter.policygroup') +local policyGroupLib = prefixConf.policyGroupPrefix + +local getPolicyGroupId = function() + local policyGroupId = tonumber(ngx.var.arg_policygroupid) + if not policyGroupId or policyGroupId < 0 then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "policyGroupId invalid" + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + return policyGroupId +end + +local getPolicyGroup = function() + + local request_body = ngx.var.request_body + local postData = cjson.decode(request_body) + + if not request_body then + -- ERRORCODE.PARAMETER_NONE + local errinfo = ERRORINFO.PARAMETER_NONE + local desc = 'request_body or post data' + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + if not postData then + -- ERRORCODE.PARAMETER_ERROR + local errinfo = ERRORINFO.PARAMETER_ERROR + local desc = 'postData is not a json string' + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + local policy_cnt = 0 + local policyGroup = {} + for k, v in pairs(postData) do + policy_cnt = policy_cnt + 1 + + local idx = tonumber(k) + if not idx or type(v) ~= 'table' then + local errinfo = ERRORINFO.PARAMETER_ERROR + local desc = 'policyGroup error' + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + local policy = v + local divtype = policy.divtype + local divdata = policy.divdata + + if not divtype or not divdata then + -- ERRORCODE.PARAMETER_NONE + local errinfo = ERRORINFO.PARAMETER_NONE + local desc = "policy divtype or policy divdata" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + if not divtypes[divtype] then + -- ERRORCODE.PARAMETER_TYPE_ERROR + local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "unsupported divtype" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + if policyGroup[idx] then + --不能混淆,优先级不能重复 + local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "policy in policy group should not overlap" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + policyGroup[idx] = policy + end + + if policy_cnt ~= #policyGroup then + local errinfo = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "index of policy in policy_group should be one by one" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return nil + end + + return policyGroup +end + +_M.checkPolicy = function(option) + local db = option.db + + local policyGroup = getPolicyGroup() + if not policyGroup then + return false + end + + local steps = #policyGroup + if steps < 1 then + local errinfo = ERRORINFO.PARAMETER_NONE + local desc = "blank policy group" + local response = doresp(errinfo, desc) + log:errlog(dolog(errinfo, desc)) + ngx.say(response) + return false + end + + local pfunc = function() + local policyGroupMod = policyGroupModule:new(db.redis, + policyGroupLib, policyLib) + return policyGroupMod:check(policyGroup) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local chkout = info + local valid = chkout[1] + local err = chkout[2] + local desc = chkout[3] + + if not valid then + local response = doresp(err, desc) + ngx.say(response) + log:errlog(dolog(err, desc)) + return false + end + + return true +end + +_M.check = function(option) + local status = _M.checkPolicy(option) + if not status then return end + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true +end + +_M.set = function(option) + + local status = _M.checkPolicy(option) + if not status then return end + + local db = option.db + + local policyGroup = getPolicyGroup() + if not policyGroup then + return false + end + + local pfunc = function() + local policyGroupMod = policyGroupModule:new(db.redis, + policyGroupLib, policyLib) + return policyGroupMod:set(policyGroup) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local data = info + local response = doresp(ERRORINFO.SUCCESS, _, data) + ngx.say(response) + return true +end + +_M.get = function(option) + local db = option.db + + local policyGroupId = getPolicyGroupId() + if not policyGroupId then + return false + end + + local pfunc = function() + local policyGroupMod = policyGroupModule:new(db.redis, + policyGroupLib, policyLib) + return policyGroupMod:get(policyGroupId) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + local data = info + local response = doresp(ERRORINFO.SUCCESS, _, data) + ngx.say(response) + return true +end + +_M.del = function(option) + local db = option.db + + local policyGroupId = getPolicyGroupId() + if not policyGroupId then + return false + end + + local pfunc = function() + local policyGroupMod = policyGroupModule:new(db.redis, + policyGroupLib, policyLib) + return policyGroupMod:del(policyGroupId) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true +end + +return _M diff --git a/admin/runtime.lua b/admin/runtime.lua new file mode 100644 index 0000000..28d9547 --- /dev/null +++ b/admin/runtime.lua @@ -0,0 +1,239 @@ +--- +-- @classmod abtesting.adapter.policy +-- @release 0.0.1 +local modulename = "abtestingAdminRuntime" + +local _M = { _VERSION = "0.0.1" } +local mt = { __index = _M } + +local ERRORINFO = require('abtesting.error.errcode').info + +local runtimeModule = require('abtesting.adapter.runtime') +local policyModule = require('abtesting.adapter.policy') +local redisModule = require('abtesting.utils.redis') +local systemConf = require('abtesting.utils.init') +local handler = require('abtesting.error.handler').handler +local utils = require('abtesting.utils.utils') +local log = require('abtesting.utils.log') +local ERRORINFO = require('abtesting.error.errcode').info +local cjson = require('cjson.safe') + +local redisConf = systemConf.redisConf +local prefixConf = systemConf.prefixConf +local runtimeLib = prefixConf.runtimeInfoPrefix +local policyLib = prefixConf.policyLibPrefix +local domain_name = prefixConf.domainname +local divtypes = systemConf.divtypes +local fields = systemConf.fields + +local runtimeGroupModule = require('abtesting.adapter.runtimegroup') +local policyGroupModule = require('abtesting.adapter.policygroup') +local policyGroupLib = prefixConf.policyGroupPrefix + + +local doresp = utils.doresp +local dolog = utils.dolog +local doerror = utils.doerror + +local getPolicyId = function() + local policyID = tonumber(ngx.var.arg_policyid) + return policyID +end + +local getPolicyGroupId = function() + local policyGroupId = tonumber(ngx.var.arg_policygroupid) + return policyGroupId +end + +local getHostName = function() + local hostname = ngx.var.arg_hostname + return hostname +end + +local getDivSteps = function() + local divsteps = tonumber(ngx.var.arg_divsteps) + return divsteps +end + +_M.get = function(option) + local db = option.db + local database = db.redis + + local hostname = getHostName() + if not hostname or string.len(hostname) < 1 or hostname == ngx.null then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = 'arg hostname invalid: ' + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + + local pfunc = function() + local runtimeGroupMod = runtimeGroupModule:new(database, runtimeLib) + return runtimeGroupMod:get(hostname) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local response = doresp(ERRORINFO.SUCCESS, nil, info) + ngx.say(response) + return true +end + +_M.del = function(option) + local db = option.db + local database = db.redis + + local hostname = getHostName() + if not hostname or string.len(hostname) < 1 or hostname == ngx.null then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = 'arg hostname invalid: ' + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + + local pfunc = function() + local runtimeGroupMod = runtimeGroupModule:new(database, runtimeLib) + return runtimeGroupMod:del(hostname) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true +end + +_M.set = function(option) + local policyId = getPolicyId() + local policyGroupId = getPolicyGroupId() + + if policyId and policyId >= 0 then + _M.runtimeset(option, policyId) + elseif policyGroupId and policyGroupId >= 0 then + _M.groupset(option, policyGroupId) + else + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = "policyId or policyGroupid invalid" + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end +end + +_M.groupset = function(option, policyGroupId) + local db = option.db + local database = db.redis + + local hostname = getHostName() + local divsteps = getDivSteps() + + if not hostname or string.len(hostname) < 1 or hostname == ngx.null then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = 'arg hostname invalid: ' + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + + local pfunc = function() + local runtimeGroupMod = runtimeGroupModule:new(database, runtimeLib) + runtimeGroupMod:del(hostname) + return runtimeGroupMod:set(hostname, policyGroupId, divsteps) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true + +end + +_M.runtimeset = function(option, policyId) + local db = option.db + local database = db.redis + + local hostname = getHostName() + local divsteps = 1 + + if not hostname or string.len(hostname) < 1 or hostname == ngx.null then + local info = ERRORINFO.PARAMETER_TYPE_ERROR + local desc = 'arg hostname invalid: ' + local response = doresp(info, desc) + log:errlog(dolog(info, desc)) + ngx.say(response) + return nil + end + + local pfunc = function() + local runtimeGroupMod = runtimeGroupModule:new(database, runtimeLib) + return runtimeGroupMod:del(hostname) + end + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + local pfunc = function() + local policyMod = policyModule:new(database, policyLib) + local policy = policyMod:get(policyId) + + local divtype = policy.divtype + local divdata = policy.divdata + + if divtype == ngx.null or divdata == ngx.null then + error{ERRORINFO.POLICY_BLANK_ERROR, 'policy NO '..policyId} + end + + if not divtypes[divtype] then + + end + + local prefix = hostname .. ':first' + local divModulename = table.concat({'abtesting', 'diversion', divtype}, '.') + local divDataKey = table.concat({policyLib, policyId, fields.divdata}, ':') + local userInfoModulename = table.concat({'abtesting', 'userinfo', divtypes[divtype]}, '.') + local runtimeMod = runtimeModule:new(database, runtimeLib) + runtimeMod:set(prefix, divModulename, divDataKey, userInfoModulename) + + local divSteps = runtimeLib .. ':' .. hostname .. ':' .. fields.divsteps + local ok, err = database:set(divSteps, 1) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + end + + local status, info = xpcall(pfunc, handler) + if not status then + local response = doerror(info) + ngx.say(response) + return false + end + + + local response = doresp(ERRORINFO.SUCCESS) + ngx.say(response) + return true +end + + + +return _M diff --git a/admin/runtime/del.lua b/admin/runtime/del.lua deleted file mode 100644 index 6d989e0..0000000 --- a/admin/runtime/del.lua +++ /dev/null @@ -1,85 +0,0 @@ -local runtimeModule = require('abtesting.adapter.runtime') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info -local cjson = require('cjson.safe') - -local redisConf = systemConf.redisConf -local prefixConf = systemConf.prefixConf -local runtimeLib = prefixConf.runtimeInfoPrefix -local policyLib = prefixConf.policyLibPrefix -local domain_name = prefixConf.domainname -local divtypes = systemConf.divtypes - -local doresp = utils.doresp -local dolog = utils.dolog - -local domainName = domain_name or ngx.var.arg_domainname -if not domainName then - local request_body = ngx.var.request_body - local postData = cjson.decode(request_body) - - if not request_body then - -- ERRORCODE.PARAMETER_NONE - local info = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not postData then - -- ERRORCODE.PARAMETER_ERROR - local info = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not domainName then - domainName = postData.domainname - if not domainName then - local info = ERRORINFO.PARAMETER_NONE - local desc = "domainName" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - end -end - - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(errinfo, err) - dolog(errinfo, err) - ngx.say(response) - return -end - -local pfunc = function() - local runtimeMod = runtimeModule:new(red.redis, runtimeLib) - runtimeMod:del(domainName) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local response = doresp(ERRORINFO.SUCCESS) -ngx.say(response) diff --git a/admin/runtime/get.lua b/admin/runtime/get.lua deleted file mode 100644 index b2e7bc4..0000000 --- a/admin/runtime/get.lua +++ /dev/null @@ -1,100 +0,0 @@ -local runtimeModule = require('abtesting.adapter.runtime') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info -local cjson = require('cjson.safe') - -local redisConf = systemConf.redisConf -local prefixConf = systemConf.prefixConf -local runtimeLib = prefixConf.runtimeInfoPrefix -local policyLib = prefixConf.policyLibPrefix -local domain_name = prefixConf.domainname -local divtypes = systemConf.divtypes - -local fields = {} -fields.divModulename = 'divModulename' -fields.divDataKey = 'divDataKey' -fields.userInfoModulename = 'userInfoModulename' - -local doresp = utils.doresp -local dolog = utils.dolog - -local domainName = domain_name or ngx.var.arg_domainname - -if not domainName then - local request_body = ngx.var.request_body - local postData = cjson.decode(request_body) - - if not request_body then - -- ERRORCODE.PARAMETER_NONE - local info = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not postData then - -- ERRORCODE.PARAMETER_ERROR - local info = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not domainName then - domainName = postData.domainname - if not domainName then - local info = ERRORINFO.PARAMETER_NONE - local desc = "domainName" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - end -end - - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(errinfo, err) - dolog(errinfo, err) - ngx.say(response) - return -end - -local pfunc = function() - local runtimeMod = runtimeModule:new(red.redis, runtimeLib) - return runtimeMod:get(domainName) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -else - divModulename = fields.divModulename - divDataKey = fields.divDataKey - userInfoModulename = fields.userInfoModulename - local runtimeInfo = {} - runtimeInfo[divModulename] = info[1] - runtimeInfo[divDataKey] = info[2] - runtimeInfo[userInfoModulename] = info[3] - - local response = doresp(ERRORINFO.SUCCESS, nil, runtimeInfo) - ngx.say(response) -end - diff --git a/admin/runtime/set.lua b/admin/runtime/set.lua deleted file mode 100644 index 0b80587..0000000 --- a/admin/runtime/set.lua +++ /dev/null @@ -1,162 +0,0 @@ -local runtimeModule = require('abtesting.adapter.runtime') -local policyModule = require('abtesting.adapter.policy') -local redisModule = require('abtesting.utils.redis') -local systemConf = require('abtesting.utils.init') -local handler = require('abtesting.error.handler').handler -local utils = require('abtesting.utils.utils') -local ERRORINFO = require('abtesting.error.errcode').info -local cjson = require('cjson.safe') - -local redisConf = systemConf.redisConf -local prefixConf = systemConf.prefixConf -local runtimeLib = prefixConf.runtimeInfoPrefix -local policyLib = prefixConf.policyLibPrefix -local domain_name = prefixConf.domainname -local divtypes = systemConf.divtypes - -local separator = ':' -local fields = {} -fields.divtype = 'divtype' -fields.divdata = 'divdata' -fields.idCount = 'idCount' - -local doresp = utils.doresp -local dolog = utils.dolog - -local domainName = domain_name -if not domainName or domainName == ngx.null - or string.len(domainName) < 1 then - local info = ERRORINFO.PARAMETER_NONE - local desc = "domainName is blank and please set it in nginx.conf" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return -end - -local policyID = ngx.var.arg_policyid - -if policyID then - policyID = tonumber(ngx.var.arg_policyid) - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - -if not policyID then - local request_body = ngx.var.request_body - local postData = cjson.decode(request_body) - - if not request_body then - -- ERRORCODE.PARAMETER_NONE - local info = ERRORINFO.PARAMETER_NONE - local desc = 'request_body or post data to get policyID' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - if not postData then - -- ERRORCODE.PARAMETER_ERROR - local info = ERRORINFO.PARAMETER_ERROR - local desc = 'postData is not a json string' - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = postData.policyid - - if not policyID then - local info = ERRORINFO.PARAMETER_ERROR - local desc = "policyID is needed" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end - - policyID = tonumber(postData.policyid) - - if not policyID or policyID < 0 then - local info = ERRORINFO.PARAMETER_TYPE_ERROR - local desc = "policyID should be a positive Integer" - local response = doresp(info, desc) - dolog(info, desc) - ngx.say(response) - return - end -end - - -local red = redisModule:new(redisConf) -local ok, err = red:connectdb() -if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - local response = doresp(errinfo, err) - dolog(errinfo, err) - ngx.say(response) - return -end - -local pfunc = function() - local policyMod = policyModule:new(red.redis, policyLib) - return policyMod:get(policyID) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local divtype = info.divtype -local divdata = info.divdata -if divtype == ngx.null or - divdata == ngx.null then - local err = ERRORINFO.POLICY_BLANK_ERROR - local desc = 'policy NO.'..policyID - local response = doresp(err, desc) - dolog(err, desc) - ngx.say(response) - return -end - -if not divtypes[divtype] then - -- unsupported divtype -end - -local pfunc = function() - local divModulename = table.concat({'abtesting', 'diversion', divtype}, '.') - local divDataKey = table.concat({policyLib, policyID, fields.divdata}, ':') - local userInfoModulename= table.concat({'abtesting', 'userinfo', divtypes[divtype]}, '.') - - local runtimeMod = runtimeModule:new(red.redis, runtimeLib) - return runtimeMod:set(domainName, divModulename, divDataKey, userInfoModulename) -end - -local status, info = xpcall(pfunc, handler) -if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - local response = doresp(err, desc) - dolog(err, desc, nil, errstack) - ngx.say(response) - return -end - -local response = doresp(ERRORINFO.SUCCESS) -ngx.say(response) diff --git a/diversion/diversion.lua b/diversion/diversion.lua index 4ead4db..f1ac6fe 100644 --- a/diversion/diversion.lua +++ b/diversion/diversion.lua @@ -1,255 +1,341 @@ -local runtimeModule = require('abtesting.adapter.runtime') +local runtimeModule = require('abtesting.adapter.runtimegroup') local redisModule = require('abtesting.utils.redis') local systemConf = require('abtesting.utils.init') +local utils = require('abtesting.utils.utils') +local logmod = require("abtesting.utils.log") +local cache = require('abtesting.utils.cache') local handler = require('abtesting.error.handler').handler local ERRORINFO = require('abtesting.error.errcode').info local cjson = require('cjson.safe') -local utils = require('abtesting.utils.utils') local resty_lock = require("resty.lock") +local semaphore = require("abtesting.utils.sema") + +local dolog = utils.dolog +local doerror = utils.doerror local redisConf = systemConf.redisConf local prefixConf = systemConf.prefixConf local divConf = systemConf.divConf -local cacheConf = systemConf.cacheConf - -local runtimeInfoLib = prefixConf.runtimeInfoPrefix -local domainname = prefixConf.domainname - -local shdict_expire = divConf.shdict_expire or 60 -local default_backend = divConf.default_backend - -local cache_expire = cacheConf.timeout or 0.001 -local rt_cache_lock = cacheConf.runtimeInfoLock +local indices = systemConf.indices +local fields = systemConf.fields +local runtimeLib = prefixConf.runtimeInfoPrefix +local redirectInfo = 'proxypass to upstream http://' -local dolog = utils.dolog +local sema = semaphore.sema +local upsSema = semaphore.upsSema -local sysConfig = ngx.shared.sysConfig -local kv_upstream = ngx.shared.kv_upstream +local upstream = nil local getRewriteInfo = function() - return 'redirect to upstream http://'..ngx.var.backend + return redirectInfo..ngx.var.backend end -local doredirect = function() +local doredirect = function(info) local ok = ERRORINFO.SUCCESS - local err = 'redirect to upstream http://'..ngx.var.backend - dolog(ok, err) + local err = redirectInfo..ngx.var.backend + return dolog(ok, err, info) end -local isNULL = function(v) - return not v or v == ngx.null +local setKeepalive = function(red) + local ok, err = red:keepalivedb() + if not ok then + local errinfo = ERRORINFO.REDIS_KEEPALIVE_ERROR + local errdesc = err + dolog(errinfo, errdesc) + return + end end -local areNULL = function(...) - local t = {...} - if not next(t) then - return true - end - for k, v in pairs(t) do - if isNULL(v) then - return true - end +local getHost = function() + local host = ngx.req.get_headers()['Host'] + if not host then return nil end + local hostkey = ngx.var.hostkey + if hostkey then + return hostkey + else + --location 中不配置hostkey时 + return host end - return false end -local isSwitchOff = function(...) - local t = {...} - if not next(t) then - return true - end - for k, v in pairs(t) do - if v == -1 then - return true - end - end - return false +local getRuntime = function(database, hostname) + local runtimeMod = runtimeModule:new(database, runtimeLib) + return runtimeMod:get(hostname) end -local red -local setKeepalive = function(red) - local ok, err = red:keepalivedb() +local getUserInfo = function(runtime) + local userInfoModname = runtime[fields.userInfoModulename] + local userInfoMod = require(userInfoModname) + local userInfo = userInfoMod:get() + return userInfo +end + +local getUpstream = function(runtime, database, userInfo) + local divModname = runtime[fields.divModulename] + local policy = runtime[fields.divDataKey] + local divMod = require(divModname) + local divModule = divMod:new(database, policy) + local upstream = divModule:getUpstream(userInfo) + + return upstream +end + +local connectdb = function(red, redisConf) + if not red then + red = redisModule:new(redisConf) + end + local ok, err = red:connectdb() if not ok then - local errinfo = ERRORINFO.REDIS_KEEPALIVE_ERROR - local errdesc = err - dolog(errinfo, errdesc) - return + local info = ERRORINFO.REDIS_CONNECT_ERROR + dolog(info, err) + return false, err end + + return ok, red +end + +local hostname = getHost() +if not hostname then + local info = ERRORINFO.ARG_BLANK_ERROR + local desc = 'cannot get [Host] from req headers' + dolog(info, desc, getRewriteInfo()) + return nil end ---==================================================== ---获取当前domain的运行时 --- 分流模块名 divModulename --- 分流策略库名 divDataKey --- 用户特征提取模块名 userInfoModulename - -local k_divModname = runtimeInfoLib .. ':' .. domainname .. 'divModulename' -local k_divData = runtimeInfoLib .. ':' .. domainname .. 'divDataKey' -local k_userinfoModname = runtimeInfoLib .. ':' .. domainname .. 'userInfoModulename' -local divModule, divPolicy, userInfoModname - -divModname = sysConfig:get(k_divModname) -divPolicy = sysConfig:get(k_divData) -userInfoModname = sysConfig:get(k_userinfoModname) - ---step 1: read from cache -if areNULL(divModname, divPolicy, userInfoModname) then --- setp 2: acquire the lock - local opts = {["timeout"] = tonumber(cache_expire)} - local lock = resty_lock:new(rt_cache_lock, opts) - local elapsed, err = lock:lock(userInfo) - if not elapsed then +local log = logmod:new(hostname) + +local red = redisModule:new(redisConf) + +-- getRuntimeInfo from cache or db +local pfunc = function() + local runtimeCache = cache:new(ngx.var.sysConfig) + + --step 1: read frome cache, but error + local divsteps = runtimeCache:getSteps(hostname) + if not divsteps then + -- continue, then fetch from db + elseif divsteps < 1 then + -- divsteps = 0, div switch off, goto default upstream + return false, 'divsteps < 1, div switchoff' + else + -- divsteps fetched from cache, then get Runtime From Cache + local ok, runtimegroup = runtimeCache:getRuntime(hostname, divsteps) + if ok then + return true, divsteps, runtimegroup + -- else fetch from db + end + end + + --step 2: acquire the lock + local sem, err = sema:wait(0.01) + if not sem then -- lock failed acquired - -- but go on. This action just set a fence for all but this request + -- but go on. This action just sets a fence end - + -- setp 3: read from cache again - divModname = sysConfig:get(k_divModname) - divPolicy = sysConfig:get(k_divData) - userInfoModname = sysConfig:get(k_userinfoModname) - - if areNULL(divModname, divPolicy, userInfoModname) then - -- step 4: fetch from redis - if not red then - red = redisModule:new(redisConf) - local ok, err = red:connectdb() - if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - dolog(errinfo, err, getRewriteInfo()) - local ok, err = lock:unlock() - return - end + local divsteps = runtimeCache:getSteps(hostname) + if not divsteps then + -- continue, then fetch from db + elseif divsteps < 1 then + -- divsteps = 0, div switch off, goto default upstream + if sem then sema:post(1) end + return false, 'divsteps < 1, div switchoff' + else + -- divsteps fetched from cache, then get Runtime From Cache + local ok, runtimegroup = runtimeCache:getRuntime(hostname, divsteps) + if ok then + if sem then sema:post(1) end + return true, divsteps, runtimegroup + -- else fetch from db end - - local pfunc = function() - local runtimeMod = runtimeModule:new(red.redis, runtimeInfoLib) - local runtimeInfo = runtimeMod:get(domainname) - return runtimeInfo - end - local status, info = xpcall(pfunc, handler) - if not status then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - dolog(err, desc, getRewriteInfo(), errstack) - local ok, err = lock:unlock() - return - end - - divModname = info[1] - divPolicy = info[2] - userInfoModname = info[3] - - if areNULL(divModname, divPolicy, userInfoModname) then - local errinfo = ERRORINFO.RUNTIME_BLANK_ERROR - local errdesc = 'runtimeInfo blank' - - sysConfig:set(k_divModname, -1, shdict_expire) - sysConfig:set(k_divData, -1, shdict_expire) - sysConfig:set(k_userinfoModname, -1, shdict_expire) - local ok, err = lock:unlock() - - if red then setKeepalive(red) end - dolog(errinfo, errdesc, getRewriteInfo()) - return - else - sysConfig:set(k_divModname, divModname, shdict_expire) - sysConfig:set(k_divData, divPolicy, shdict_expire) - sysConfig:set(k_userinfoModname, userInfoModname, shdict_expire) - local ok, err = lock:unlock() - end end -elseif isSwitchOff(divModname, divPolicy, userInfoModname) then - -- switchoff, so goto default upstream - doredirect() - return -else - -- maybe userful -end + -- step 4: fetch from redis + local ok, db = connectdb(red, redisConf) + if not ok then + if sem then sema:post(1) end + return ok, db + end ---==================================================== ---准备工作: --- 分流模块 divModule --- 用户特征提取模块 userInfoModule ---获取用户信息: --- 用户信息 userInfo -local userInfoMod -local diversionMod -local userInfo + local database = db.redis + local runtimeInfo = getRuntime(database, hostname) -local pfunc = function() - userInfoMod = require(userInfoModname) - diversionMod = require(divModname) - userInfo = userInfoMod:get() -end -local ok, info = xpcall(pfunc, handler) -if not ok then - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - dolog(err, desc, getRewriteInfo(), errstack) + local divsteps = runtimeInfo.divsteps + local runtimegroup = runtimeInfo.runtimegroup + + runtimeCache:setRuntime(hostname, divsteps, runtimegroup) if red then setKeepalive(red) end - return + + if sem then sema:post(1) end + return true, divsteps, runtimegroup end -if not userInfo then - local errinfo = ERRORINFO.USERINFO_BLANK_ERROR - local errdesc = userInfoModulename - dolog(errinfo, errdesc, getRewriteInfo()) - if red then setKeepalive(red) end - return +local ok, status, steps, runtimeInfo = xpcall(pfunc, handler) +if not ok then + -- execute error, the type of status is table now + log:errlog("getruntime\t", "error\t") + return doerror(status, getRewriteInfo()) +else + local info = 'getRuntimeInfo error: ' + if not status or not steps or steps < 1 then + if not status then + local reason = steps + if reason then + info = info .. reason + end + elseif not steps then + info = info .. 'no divsteps, div switch OFF' + elseif steps < 1 then + info = info .. 'divsteps < 1, div switch OFF' + end + return log:info(doredirect(info)) + else + log:debug('divstep = ', steps, + '\truntimeinfo = ', cjson.encode(runtimeInfo)) + end end -------------------分流准备工作结束-------------------- ---==================================================== -local upstream, err = kv_upstream:get(userInfo) -if not upstream then +local divsteps = steps +local runtimegroup = runtimeInfo - if not red then - red = redisModule:new(redisConf) - local ok, err = red:connectdb() - if not ok then - local errinfo = ERRORINFO.REDIS_CONNECT_ERROR - dolog(errinfo, err, getRewriteInfo()) - return +local pfunc = function() + + local upstreamCache = cache:new(ngx.var.kv_upstream) + + local usertable = {} + for i = 1, divsteps do + local idx = indices[i] + local runtime = runtimegroup[idx] + local info = getUserInfo(runtime) + + if info and info ~= '' then + usertable[idx] = info end end - - local pfunc = function() - local divModule = diversionMod:new(red.redis, divPolicy) - local upstream = divModule:getUpstream(userInfo) - return upstream + + log:debug('userinfo\t', cjson.encode(usertable)) + + +-- usertable is empty, it seems that will never happen +-- if not next(usertable) then +-- return nil +-- end + + --step 1: read frome cache, but error + local upstable = upstreamCache:getUpstream(divsteps, usertable) + log:debug('first fetch: upstable in cache\t', cjson.encode(upstable)) + for i = 1, divsteps do + local idx = indices[i] + local ups = upstable[idx] + if ups == -1 then + if i == divsteps then + local info = "usertable has no upstream in cache 1, \ + proxypass to default upstream" + log:info(info) + return nil, info + end + -- continue + elseif ups == nil then + -- why to break + -- the reason is that maybe some userinfo is empty + -- 举例子,用户请求 + -- location/div -H 'X-Log-Uid:39' -H 'X-Real-IP:192.168.1.1' + -- 分流后缓存中 39->-1, 192.168.1.1-> beta2 + -- 下一请求: + -- location/div?city=BJ -H 'X-Log-Uid:39' -H 'X-Real-IP:192.168.1.1' + -- 该请求应该是 39-> -1, BJ->beta1, 192.168.1.1->beta2, + -- 然而cache中是 39->-1, 192.168.1.1->beta2, + -- 如果此分支不break的话,将会分流到beta2上,这是错误的。 + + break + else + local info = "get upstream ["..ups.."] according to [" + ..idx.."] userinfo ["..usertable[idx].."] in cache 1" + log:info(info) + return ups, info + end end - local status, backend = xpcall(pfunc, handler) - if not status then - local info = backend - local errinfo = info[1] - local errstack = info[2] - local err, desc = errinfo[1], errinfo[2] - dolog(err, desc, getRewriteInfo(), errstack) - return + + --step 2: acquire the lock + local sem, err = upsSema:wait(0.01) + if not sem then + -- lock failed acquired + -- but go on. This action just set a fence for all but this request + end + + -- setp 3: read from cache again + local upstable = upstreamCache:getUpstream(divsteps, usertable) + log:debug('second fetch: upstable in cache\t', cjson.encode(upstable)) + for i = 1, divsteps do + local idx = indices[i] + local ups = upstable[idx] + if ups == -1 then + -- continue + if i == divsteps then + local info = "usertable has no upstream in cache 2, \ + proxypass to default upstream" + return nil, info + end + + elseif ups == nil then + -- do not break, may be the next one will be okay + break + else + if sem then upsSema:post(1) end + local info = "get upstream ["..ups.."] according to [" + ..idx.."] userinfo ["..usertable[idx].."] in cache 2" + return ups, info + end + end + + -- step 4: fetch from redis + local ok, db = connectdb(red, redisConf) + if not ok then + if sem then upsSema:post(1) end + return nil, db end - - upstream = backend + local database = db.redis + + for i = 1, divsteps do + local idx = indices[i] + local runtime = runtimegroup[idx] + local info = usertable[idx] + + if info then + local upstream = getUpstream(runtime, database, info) + if not upstream then + upstreamCache:setUpstream(info, -1) + log:debug('fetch userinfo [', info, '] from db, get [nil]') + else + if sem then upsSema:post(1) end + if red then setKeepalive(red) end + + upstreamCache:setUpstream(info, upstream) + log:debug('fetch userinfo [', info, '] from db, get [', upstream, ']') + + local info = "get upstream ["..upstream.."] according to [" + ..idx.."] userinfo ["..usertable[idx].."] in db" + return upstream, info + end + end + end + + if sem then upsSema:post(1) end + if red then setKeepalive(red) end + return nil, 'the req has no target upstream' end -if upstream then - ngx.var.backend = upstream +local status, info, desc = xpcall(pfunc, handler) +if not status then + doerror(info) else - upstream = default_backend + upstream = info end -kv_upstream:set(userInfo, upstream, shdict_expire) -doredirect() ---------------------分流结果结束---------------------- ---==================================================== - ---==================================================== ----------------------后续处理------------------------- ----如果本次请求使用过redis,设置redis对象的keepalive--- ------------------------------------------------------- -if red then - setKeepalive(red) +if upstream then + ngx.var.backend = upstream end +local info = doredirect(desc) +log:errlog(info) diff --git "a/doc/ab\345\210\206\346\265\201\347\255\226\347\225\245.md" "b/doc/ab\345\210\206\346\265\201\347\255\226\347\225\245.md" new file mode 100644 index 0000000..a2479b1 --- /dev/null +++ "b/doc/ab\345\210\206\346\265\201\347\255\226\347\225\245.md" @@ -0,0 +1,128 @@ +ab分流策略格式 +====================== + +* 分流策略 policy +* 分流策略组 policygroup + +分流策略policy +-------------------------- + +```bash +{ + "divtype":"分流类型", + "divdata":[ + {规则一}, + {规则二}, + {规则三}, + ... + ] +} +``` + +* ab 灰度系统目前支持的策略有ip段分流、用户uid段分流、uid尾数分流、uid白名单分流 +* 可以灵活添加新的分流方式 + +```bash +#iprange分流,其中start和end为ip的整型表示。 +{ + "divtype":"iprange", + "divdata":[ + {"range":{"start":1111, "end":2222}, "upstream":"beta1"}, + {"range":{"start":3333, "end":4444}, "upstream":"beta2"}, + {"range":{"start":7777, "end":8888}, "upstream":"beta3"} + ] +} +``` + +```bash +#uid段分流 +{ + "divtype": "uidrange", + "divdata": [ + {"range":{"start":1111, "end":2222}, "upstream":"beta1"}, + {"range":{"start":3333, "end":4444}, "upstream":"beta2"} + ] +} +``` + +```bash +#uid尾数分流 +{ + "divtype": "uidsuffix", + "divdata": [ + {"suffix":1, "upstream":"beta1"}, + {"suffix":3, "upstream":"beta2"}, + {"suffix":5, "upstream":"beta1"}, + {"suffix":0, "upstream":"beta3"} + ] +} +``` + +```bash +#uid白名单分流 +{ + "divtype": "uidappoint", + "divdata": [ + {"uidset":[1234,5124,653], "upstream":"beta1"}, + {"uidset":[3214,652,145], "upstream":"beta2"} + ] +} +``` +* 当向系统添加分流策略时,需要将策略数据转为json类型字符串,以POST方式访问添加策略接口 +* http://www.bejson.com/jsoneditoronline/ 可以将上述策略转换为字符串,用以通过post方式向系统添加策略 +* 以上四条策略的字符串格式分别为: + * {"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]} + * {"divtype":"uidrange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"}]} + * {"divtype":"uidsuffix","divdata":[{"suffix":1,"upstream":"beta1"},{"suffix":3,"upstream":"beta2"},{"suffix":5,"upstream":"beta1"},{"suffix":0,"upstream":"beta3"}]} + * {"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]} + + +分流策略组policygroup +-------------------------- + +* 分流策略组中会有多个策略 +* 优先级由数字表示,从1开始,级别为1的策略优先级最高 +* 分流策略组格式为: + +```bash +{ + "1":{ + "divtype":"分流类型", + "divdata":[ + {规则一}, + {规则二}, + ... + ] + }, + "2":{ + + }, + ... +} +``` +* 以下是一个包含两级分流策略,第一级为uid白名单分流策略,第二级为ip段分流 + +```bash +{ + + "1":{ + "divtype": "uidappoint", + "divdata": [ + {"uidset":[1234,5124,653], "upstream":"beta1"}, + {"uidset":[3214,652,145], "upstream":"beta2"} + ] + }, + "2":{ + "divtype":"iprange", + "divdata":[ + {"range":{"start":1111, "end":2222}, "upstream":"beta1"}, + {"range":{"start":3333, "end":4444}, "upstream":"beta2"}, + {"range":{"start":7777, "end":8888}, "upstream":"beta3"} + ] + } +} +``` +* 该分流策略组的字符串形式为: + * {"1":{"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]},"2":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]}} + + diff --git "a/doc/ab\345\212\237\350\203\275\346\216\245\345\217\243\344\275\277\347\224\250\344\273\213\347\273\215.md" "b/doc/ab\345\212\237\350\203\275\346\216\245\345\217\243\344\275\277\347\224\250\344\273\213\347\273\215.md" new file mode 100644 index 0000000..a9a99e4 --- /dev/null +++ "b/doc/ab\345\212\237\350\203\275\346\216\245\345\217\243\344\275\277\347\224\250\344\273\213\347\273\215.md" @@ -0,0 +1,181 @@ +ab功能接口介绍 +=================== + +ab管理接口 +------------------ + +```bash +#策略管理 +* /ab_admin?action=policy_check +* /ab_admin?action=policy_set +* /ab_admin?action=policy_get +* /ab_admin?action=policy_del + +#策略组管理(用于多级分流) +* /ab_admin?action=policygroup_check +* /ab_admin?action=policygroup_set +* /ab_admin?action=policygroup_get +* /ab_admin?action=policygroup_del + +#运行时信息设置(其中runtime_set接受policyid和policygroupid参数,分别用于单级分流和多级分流) +* /ab_admin?action=runtime_get +* /ab_admin?action=runtime_set +* /ab_admin?action=runtime_del +``` +* 1.检查策略是否合法 + * http://localhost:port/ab_admin?action=policy_check -d '{"divtype":"uidsuffix","divdata":[{"suffix":"1","upstream":"beta1"},{"suffix":"3","upstream":"beta2"},{"suffix":"5","upstream":"beta1"},{"suffix":"0","upstream":"beta3"}]}' + * 接口说明: + * action: 代表要执行的操作,检查策略接口为policy_check + * 仅接受POST方法,POST数据为待检查策略的json字符串 + * 返回值:{"code":200,"desc":"success "},系统中如果返回非200的code码,就认为是发生错误,desc为错误信息。 + * 错误返回值:{"code":50102,"desc":"parameter error for postData is not a json string"} 如果出错,返回错误码与对应错误信息,反馈给用户,其他接口同样。 + + +* 2.向系统添加策略 + * http://localhost:port/ab_admin?action=policy_set -d '{"divtype":"uidsuffix","divdata":[{"suffix":"1","upstream":"beta1"},{"suffix":"3","upstream":"beta2"},{"suffix":"5","upstream":"beta1"},{"suffix":"0","upstream":"beta3"}]}'` + + * 接口说明: + * action: 代表要执行的操作 + * 仅接受POST方法,POST数据为待检查策略的json字符串 + * 返回值:{"code":200,"desc":"success the id of new policy is 20"},策略添加成功,返回策略号policyid,样例中policyid为20 + +* 3.从系统读取策略 + * http://localhost:port/ab_admin?action=policy_get&policyid=20 + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policyid: 获取第policyid号策略 + * 返回值:{"desc":"success ","code":200,"data":{"divdata":["1","beta1","3","beta2","5","beta1","0","beta3"],"divtype":"uidsuffix"}} 返回值中data部分是读取的策略数据,json格式。 + +* 4.从系统删除策略 + * http://localhost:port/ab_admin?action=policy_del&policyid=20 + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policyid: 删除第policyid号策略 + * 返回值:{"code":200,"desc":"success "} + +* 5.检查策略组是否合法 + * http://localhost:port/ab_admin?action=policygroup_check -d '{"1":{"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]},"2":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]}} + + * 接口说明: + * action: 代表要执行的操作,检查策略接口为policygroup_check + * 仅接受POST方法,POST数据为待检查策略的json字符串 + * 返回值:{"code":200,"desc":"success "},系统中如果返回非200的code码,就认为是发生错误,desc为错误信息。 + * 错误返回值:{"code":50102,"desc":"parameter error for postData is not a json string"} 如果出错,返回错误码与对应错误信息,反馈给用户,其他接口同样。 + + +* 6.向系统添加策略组 + * http://localhost:port/ab_admin?action=policygroup_set -d '{"1":{"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]},"2":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]}} + + * 接口说明: + * action: 代表要执行的操作 + * 仅接受POST方法,POST数据为待检查策略的json字符串 + * 返回值:{"desc":"success ","code":200,"data":{"groupid":2,"group":[11,12]}},策略组添加成功,返回策略组号groupid是2,组中包括两个策略,策略id分别是11和12. + +* 7.从系统读取策略组 + * http://localhost:port/ab_admin?action=policygroup_get&policygroupid=2 + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policyid: 获取第policygroupid号策略组 + * 返回值:{"desc":"success ","code":200,"data":{"groupid":2,"group":["11","12"]}} 返回值以json格式返回该组策略中包括哪些策略。 + +* 8.从系统删除策略组 + * http://localhost:port/ab_admin?action=policygroup_del&policygroupid=2 + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policyid: 删除第policygroupid号策略组 + * 返回值:{"code":200,"desc":"success "} + +* 9.设置***策略***为系统的运行时策略,进行单级分流 + * http://localhost:port/ab_admin?action=runtime_set&policyid=22&hostname=api.weibo.cn + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policyid: 设置第policyid号策略为运行时策略 + * 参数:hostname:非常重要,向server api.weibo.cn绑定运行时信息,或向location /abc @server api.weibo.cn绑定运行时信息 + * 返回值:{"code":200,"desc":"success "} + * 注意:设置运行时信息的动作会导致原来数据库中的运行时信息删除,不论本次设置是否成功 + +* 10.设置***策略组***为系统的运行时策略,进行多级分流 + * http://localhost:port/ab_admin?action=runtime_set&policygroupid=4&hostname=api.weibo.cn + + * 接口说明: + * 参数:action: 代表要执行的操作 + * 参数:policygroupid: 设置第policygroupid号策略组 为 运行时策略 + * 参数:hostname: 为host api.weibo.cn设置运行时策略 + * 返回值:{"code":200,"desc":"success "} + * 返回值:若发生错误,则有相关提示,比如某策略不存在。 + + * 请注意:将 策略 或者 策略组 设置为运行时策略的接口是一样的,区别的方式在于参数是policyid还是policygroupid,所以要注意不要写错。 + * 请注意:设置运行时信息的动作会导致原来数据库中的运行时信息删除,不论本次设置是否成功 + + +* 11.获取系统运行时信息 + * http://localhost:port/ab_admin?action=runtime_get&hostname=api.weibo.cn + + * 接口说明: + * 参数:action: 代表要执行的操作,获取系统运行时信息runtime_get + * 参数:hostname: 获取hostname主机的运行时信息 + * 系统未设置运行时信息时,返回值{"desc":"success ","code":200,"data":{"divsteps":0,"runtimegroup":{}}} + * 系统设置运行时信息后,举例为: + +```bash + { + "desc": "success ", + "code": 200, + "data": { + "divsteps": 2, + "runtimegroup": { + "second": { + "divModulename": "abtesting.diversion.iprange", + "divDataKey": "ab:test:policies:16:divdata", + "userInfoModulename": "abtesting.userinfo.ipParser" + }, + "first": { + "divModulename": "abtesting.diversion.uidappoint", + "divDataKey": "ab:test:policies:15:divdata", + "userInfoModulename": "abtesting.userinfo.uidParser" + } + } + } + } + # divsteps表示几级分流 + # runtimegroup是分流信息,以first、second等作为下标,最多十级分流 + # divModulename为运行时的分流模块名 + # userInfoModulename为运行时的用户信息提取模块名 + # divDataKey为运行时的分流策略名 +``` + +* 12.删除系统运行时信息 + * http://localhost:port/ab_admin?action=runtime_del&hostname=api.weibo.cn + + * 接口说明: + * 参数:action: 代表要执行的操作,删除系统运行时信息runtime_del + * 返回值:{"code":200,"desc":"success "} + +ab分流接口 +------------------ + +* ab分流接口目前只能配置为 location / +* 以***ab管理接口***小节中的第11条获取运行时信息为例,第一级是uidappoint白名单分流,第二级是iprange ip段分流方式 + +* curl http://localhost:port/ -H 'Host:api.weibo.cn' -H 'X-Uid:30' + * HOST字段是每个合法用户请求都有的,从HTTP 请求头中获取 + * 在匹配到virtual host和location后,分流功能通过location中设置的***hostkey***字段找到运行时信息,然后进行下一步的分流。 + * 因此location中的***$hostkey***字段是分流的基础,这里与ab管理功能中的设置运行时信息中的hostname参数一样 + +response格式 +------------------ + +系统response采用json方式返回,resp包括返回码**code**、调用信息**desc**和调用结果**data**: + +* 操作成功 +{***"code"***:200, ***"desc"***:"success", ***"data"***:["stable","beta1","beta2","beta3","beta4","beta5"]} + +* 操作错误 +{***"code"***:500, ***"desc"***:"Invalid operation: get_upstream"} + + diff --git "a/doc/ab\345\212\237\350\203\275\351\241\273\347\237\245.md" "b/doc/ab\345\212\237\350\203\275\351\241\273\347\237\245.md" new file mode 100644 index 0000000..919890a --- /dev/null +++ "b/doc/ab\345\212\237\350\203\275\351\241\273\347\237\245.md" @@ -0,0 +1,71 @@ +ab分流功能须知 +------------------- + +转发分流是灰度系统的主要功能,目前 ABTestingGateway 支持 `ip段分流(iprange)`、`uid用户段分流(uidrange)`、`uid尾数分流(uidsuffix)` 和 `指定特殊uid分流(uidappoint)` 四种方式。 + +ABTestingGateway 依据系统中配置的 `运行时信息runtimeInfo` 进行分流工作;通过将 runtimeInfo 设置为不同的分流策略,实现运行时分流策略的动态更新,达到动态调度的目的。 + +对于ab功能而言,步骤是以下三步: + +1. 向系统添加策略,将策略写入策略数据库中 +1. 为具体的server设置运行时信息,将某个分流策略设置为运行时策略 +1. 之后可以进行分流操作 + +### 1. 向系统添加策略 ### +向系统中添加分流策略的过程,本质上是将分流策略写入到系统的redis中,这个过程与具体哪个server没有关系,只是将分流策略以id为key存入redis中。本系统采用***ab:test:policies***为策略前缀,比如某个策略写入redis成功后,返回policyId=1, 那么在redis中,key为***ab:test:policyies:1:divtype***和***ab:test:policyies:1:divdata***将用来存储策略1的类型type和规则。 +同样的,向系统添加分流策略组,首先将策略组中的单个策略写入系统中,然后返回策略组的policyGroupId。 + +### 2. 为具体的server设置运行时信息 ### +将某个策略设置成server的运行时策略,最终将这个运行时信息写入到redis中。运行时信息包括三个元素,分流模块名divModuleName,分流策略名divDataKey和用户信息提取模块名userInfoModuleName;运行时信息以***ab:test:runtimeInfo***为前缀;server name为redis中key的关键部分,用于甄别不同server的运行时信息。最终redis中存储的运行时信息是 + +```bash + ab:test:runtimeInfo:xxx.weibo.cn:first:divModulename + ab:test:runtimeInfo:xxx.weibo.cn:first:divDataKey + ab:test:runtimeInfo:xxx.weibo.cn:first:userInfoModulename + + ab:test:runtimeInfo:xxx.weibo.cn:second:divModulename + ab:test:runtimeInfo:xxx.weibo.cn:second:divDataKey + ab:test:runtimeInfo:xxx.weibo.cn:second:userInfoModulename + + # ab:test:runtimeInfo 为前缀 + # xxx.weibo.cn 是server name + # first和second分别表示第一级分流和第二级分流 + # divModulename等是运行时信息的元素 +``` +因此设置运行时信息时,server name是关键。 +运行时信息设置的接口是 + + /ab_admin?action=runtime_set&policyid=0&hostname=xxx.weibo.cn + +向hostname=xxx.weibo.cn设置运行时信息,完全是在访问runtime_set接口时的hostname参数中指定的。 + +因此,如果引入location级别的运行时信息,我们只需要在调用runtime_set接口时指定hostname就可以。这个hostname同时在location中指定,比如对location /abc设置分流信息时,hostname指定为xxx.weibo.cn.abc,这样在对访问/abc接口的请求进行分流时,以xxx.weibo.cn.abc为key的分流信息生效(例如 ab:test:runtiemInfo:xxx.weibo.cn.abc:divModulename)。具体配置方法见下文 + +### 3. 对用户请求进行分流 ### +对用户请求进行分流的过程: + +1. 提取用户请求的HOST字段,拿到以***ab:test:runtimeInfo***和***HOST***为前缀的运行时信息的三要素 +2. 用户信息提取模块userInfoModule提取用户信息userInfo +3. 分流模块divmodule根据分流策略divDataKey和userInfo计算得到对应的upstream,转发;如果没有对应的upstream,则转向默认upstream。 + +* 分流三要素的用途 +
+ +* 分流过程流程图 +

分流过程流程图

+ +在引入location级别的分流设置后,需要对访问同一个servername的不同location进行甄别,这里将第一步中从用户请求中获取HOST字段,改为在location配置块中预先设置HOST字段,其效果是一样的。 + +```bash + curl http://ip:port/abc?city=BJ -H 'X-Uid:30' -H 'Host:whatever' + + 该请求访问/abc接口,在location接口中已经指定了server name是xxx.weibo.cn.abc,那么分流工作就按步骤进行。配置如下所示: + location ~* /abc/(i|f)/ { + set $hostkey $server_name.abc; + + rewrite_by_lua_file '/usr/local/dygateway/diversion/diversion.lua'; + proxy_pass http://$backend; + } +``` + + diff --git a/doc/dygateway_nginx_conf.markdown b/doc/dygateway_nginx_conf.markdown new file mode 100644 index 0000000..37a9139 --- /dev/null +++ b/doc/dygateway_nginx_conf.markdown @@ -0,0 +1,252 @@ +dygateway系统的配置参数主要包括 + +* 0.系统配置 +* 1.ab灰度系统部分的配置参数 +* 2.dyupsc动态upstream部分的配置参数 +* 3.redis数据库读写配置 + +如下是repo中自带的一个nginx.conf demo。 +```python +worker_processes auto; + +pid logs/nginx-uid.pid; +error_log logs/error.log ; + +events { + worker_connections 32768; + accept_mutex off; + multi_accept on; +} + +http { + include mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"' + ' $request_time $upstream_response_time'; + access_log logs/access.log main; + + sendfile on; + +# keepalive_timeout 0; +# keepalive_requests 0; + + upstream stable{ + keepalive 1000; + server localhost:8040; + } + + upstream beta1 { + keepalive 1000; + server localhost:8020; + server localhost:8021; + } + + upstream beta2 { + keepalive 1000; + server localhost:8021; + } + + upstream beta3 { + keepalive 1000; + server 127.1.0.1:8022; + +# check interval=3000 rise=2 fall=5 timeout=1000 type=http; +# check_http_send "HEAD / HTTP/1.0\r\n\r\n"; +# check_http_expect_alive http_2xx http_3xx; + } + + upstream beta4 { + keepalive 1000; + server localhost:8023; + } + + lua_code_cache on; + lua_package_path "/usr/local/dygateway/luacode/ab/?.lua;/usr/local/dygateway/luacode/ab/lib/?.lua;/usr/local/dygateway/luacode/dyupsc/?.lua;;"; + lua_shared_dict sysConfig 1m; + lua_shared_dict kv_upstream 10m; + lua_shared_dict rt_locks 100k; + lua_shared_dict up_locks 100k; + + lua_need_request_body on; + + # the size depends on the number of servers in upstream {}: + lua_shared_dict dyupsc 1m; + + init_worker_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/init_process_timer.lua'; + + server { + server_name localhost; + listen 8030 backlog=16384; + + set $domain_name localhost; + set $redis_host '127.0.0.1'; + set $redis_port '6379'; + set $redis_uds '/tmp/redis.sock'; + set $redis_connect_timeout 10000; + set $redis_dbid 0; + + set $redis_pool_size 1000; + set $redis_keepalive_timeout 90000; #(keepalive_time, in ms) + + set $runtime_prefix 'ab:test:runtimeInfo'; + set $policy_prefix 'ab:test:policies'; + + set $default_backend 'stable'; + set $shdict_expire 60; + + set $rt_cache_lock rt_locks; #set name of cache locks, should be same as lua_shared_dict + set $up_cache_lock up_locks; + set $lock_expire 0.001 ; #wait for cache_lock 0.001 seconds + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Connection ""; + proxy_http_version 1.1; + + set $backend $default_backend; + rewrite_by_lua_file '/usr/local/dygateway/luacode/ab/diversion/diversion.lua'; + + proxy_pass http://$backend; + + } + location /fake_location{ + + dyups_interface; + } + + location = /ab_admin { + content_by_lua_file '/usr/local/dygateway/luacode/ab/admin/ab_action.lua'; + + } + + location = /dyupsc_admin { + content_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/action.lua'; + } + + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + deny all; + } + +# location /status { +# check_status; +# +# access_log off; +# allow 127.0.0.1; +# deny all; +# } + } +} + + +``` + +系统配置 +============= +系统内部的代码依赖采用相对路径方式,而系统对nginx的依赖采用绝对路径方式。 +因此在配置文件指定接口代码时采用绝对路径,比如当dygateway安装在/usr/local/目录下时,所有lua代码都在/usr/local/dygateway/luacode/文件夹下。比如绝对路径为: +```python + init_worker_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/init_process_timer.lua'; + + location / { + + rewrite_by_lua_file '/usr/local/dygateway/luacode/ab/diversion/diversion.lua'; + proxy_pass http://$backend; + } + + location = /ab_admin { + content_by_lua_file '/usr/local/dygateway/luacode/ab/admin/ab_action.lua'; + } + + location = /dyupsc_admin { + content_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/action.lua'; + } +``` +系统的内部依赖,只要正确指定了lualib path就可以 +```python + lua_package_path "/usr/local/dygateway/luacode/ab/?.lua;/usr/local/dygateway/luacode/ab/lib/?.lua;/usr/local/dygateway/luacode/dyupsc/?.lua;;"; +``` +如果相对lua 代码路径有所调整的话,需要修改这些路径依赖。 + +ab 灰度系统的配置参数 +================ +####ab 灰度系统的 domain_name 配置 + +domain_name十分重要,它是灰度系统的运行时策略前缀,如果为空或者与下发策略不一样,会导致系统找不到运行时信息,影响灰度策略的管理和分流功能。 + +```bash + set domain_name localhost; +``` + +####ab 灰度系统的 redis 读写相关配置 +```bash + set $redis_host '127.0.0.1'; --本机redis的IP + set $redis_port '6379'; --本机redis的port + set $redis_uds '/tmp/redis.sock'; --本机redis的uds设置,优先使用uds + set $redis_connect_timeout 10000; + set $redis_dbid 0; + + set $redis_pool_size 1000; --lua-resty-redis的连接池大小 + set $redis_keepalive_timeout 90000; --(连接池keepalive_time, in ms) +``` +####ab 灰度系统的 策略和运行时信息 相关 +灰度系统的 运行时信息 和 策略库 的前缀,在redis数据库中的key名前缀。采用统一的名称就好,在策略管理下发时,所有部署灰度系统都采用同一前缀的key。 + +```bash + set $runtime_prefix 'ab:test:runtimeInfo'; + set $policy_prefix 'ab:test:policies'; +``` +####ab 灰度系统的 默认upstream配置 +当做ab测试或者灰度时,大部分用户请求都是打向默认upstream的。另外,当分流功能出现错误,获取不到目标upstream时,也会转向默认upstream。 +```bash + set $default_backend 'stable'; + + + rewrite_by_lua_file '../diversion/diversion.lua'; + proxy_pass http://$backend; +``` +####ab 灰度系统的 系统缓存 和 缓存锁 配置 +缓存的名字就是这个,lua代码里也是它。缓存kv_upstream的大小可以适当增大,避免抖动,缓存时间60s。缓存锁的的本质是一个shared-dict,就是在**系统缓存 配置**中的rt_locks和up_locks。 +在这里改名,是想给系统提供一个不变的缓存锁名字,因此这部分配置名字要对应起来。 +```bash + lua_shared_dict sysConfig 1m; --运行时信息的kv缓存 + lua_shared_dict rt_locks 100k; --缓存锁 + + lua_shared_dict kv_upstream 10m; --用户请求与目标upstream的kv缓存 + lua_shared_dict up_locks 100k; --缓存锁 + + set $shdict_expire 60; --缓存失效时间 + + + set $rt_cache_lock rt_locks; + set $up_cache_lock up_locks; + + set $cache_expire 0.001 ; --缓存锁死锁时间设置为1ms +``` + +dyupsc动态upstream配置参数 +==================== +####动态upstream的 初始化 配置 +```python + lua_shared_dict dyupsc 1m; + + init_worker_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/init_process_timer.lua'; +``` +####动态upstream的 接口配置 + +```python + location /fake_location{ + + dyups_interface; + } + + location = /dyupsc_admin { + content_by_lua_file '/usr/local/dygateway/luacode/dyupsc/admin/action.lua'; + } +``` diff --git "a/doc/nginx_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" "b/doc/nginx_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" new file mode 100644 index 0000000..c3fbb7b --- /dev/null +++ "b/doc/nginx_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" @@ -0,0 +1,185 @@ +nginx.conf的配置过程 +=========================== + +dygateway主要基于ngx_lua开发,需要在nginx配置文件中做大量配置,以配合lua代码实现功能,因此nginx.conf中的配置相当多。首先介绍下配置文件的结构: + +dygateway配置文件结构示意 +------------------------- + +* nginx.conf: 总nginx配置文件 + +```bash + http { + 1. 设置lua路径相关 + 2. lua code相关配置 + 3. nginx的一些通用配置 + + include "upstream.conf"; + include "default.conf"; + include "s1.conf"; + include "s2.conf"; + } +``` + +* default.conf:dygateway的管理相关配置 + +```bash + server { + # ab管理接口 + location ab_admin { + + } + } +``` + +* virtual server conf:单个virtual host配置 + +```bash + #s1.conf:virtual host配置 + + # 设置ngx_lua级别的cache + + lua_shared_dict abc_sysConfig 1m; + lua_shared_dict kv_abc_upstream 100m; + + server { + + # 分流接口 /abc + location /abc { + set $hostkey api.weibo.cn.abc; + set $sysConfig abc_sysConfig; + set $kv_upstream kv_abc_upstream; + + set $backend 'default_upstream'; + rewrite_by_lua_file "luacode/ab/diversion.lua"; + proxy_pass http://$backend; + } + } +``` + +nginx配置过程 +--------------------------------- + +* ***Step*** 1. 在nginx.conf中,http配置块里,添加如下配置。该配置在nginx全局有效。 + +```bash +#打开lua的代码缓存 +lua_code_cache on; + +#lua代码的路径 +lua_package_path "/usr/local/dygateway/luacode/ab/?.lua;/usr/local/dygateway/luacode/ab/lib/?.lua;/usr/local/dygateway/luacode/ab/lib/lua-resty-core/lib/?.lua;;"; + +#ngx_lua获取post数据配置 +lua_need_request_body on; + +``` + +* ***Step*** 2. 在管理server的server配置块内添加: + +```bash +# ab管理功能需要读写redis数据库,所以需要配置 +set $redis_host '127.0.0.1'; --本机redis的IP +set $redis_port '6379'; --本机redis的port +set $redis_uds '/tmp/redis.sock'; --本机redis的uds设置,优先使用uds +set $redis_connect_timeout 10000; --设置连接超时时间 +set $redis_dbid 0; --设置选择redis db0作为存储库 + +set $redis_pool_size 1000; --lua-resty-redis的连接池大小 +set $redis_keepalive_timeout 90000; --(连接池keepalive_time, in ms) + +# ab管理功能配置 +location = /ab_admin { + content_by_lua_file '/usr/local/dygateway/luacode/ab/admin/ab_action.lua'; +} + +``` + +* ***Step*** 3. virtual host s1.conf配置 + +```bash + +# location / 的 运行时信息缓存 +lua_shared_dict root_sysConfig 1m; +# location / 的 info:upstream 缓存 +lua_shared_dict kv_root_upstream 100m; + + +# location /abc 的 运行时信息缓存 +lua_shared_dict abc_sysConfig 1m; +# location /abc 的 info:upstream 缓存 +lua_shared_dict kv_abc_upstream 100m; + +server { + listen 8030 backlog=16384; + server_name api.weibo.cn; + + set $redis_host '127.0.0.1'; + set $redis_port '6379'; + set $redis_uds '/tmp/redis.sock'; + set $redis_connect_timeout 10000; + set $redis_dbid 0; + set $redis_pool_size 1000; + set $redis_keepalive_timeout 90000; #(keepalive_time, in ms) + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Connection ""; + proxy_http_version 1.1; + + + location / { + # 指定该接口的HOST,用于配置运行时信息:api.weibo.cn,(可任意取名,只要进入本loction,运行时信息以此值为key,为本location设置运行时信息时,也以此值为key,需要约定好) + set $hostkey $server_name; + + # 指定sysConfig的名字,与 缓存名字 root_sysConfig 一样(可任意取名,不要与别的lua_shared_dict冲突即可,但要与之前声明的shared_dict名字一样:root_sysConfig) + set $sysConfig root_sysConfig; + # 指定kv_upstream 的名字 与 缓存名字 kv_root_upstream 一样 + set $kv_upstream kv_root_upstream; + + # 设置默认upstream(该upstrema必须存在于upstream.conf,并且应该考虑到大部分请求将分流至默认upstream) + set $backend 'stable'; + + rewrite_by_lua_file '/usr/local/dygateway/luacode/ab/diversion/diversion.lua'; + + proxy_pass http://$backend; + } + + location /abc { + + # 指定该接口的HOST,用于配置运行时信息:api.weibo.cn.abc + set $hostkey $server_name.abc; + + set $sysConfig abc_sysConfig; + set $kv_upstream kv_abc_upstream; + + set $backend 'stable'; + + rewrite_by_lua_file '/usr/local/dygateway/luacode/ab/diversion/diversion.lua'; + proxy_pass http://$backend; + } +} + +``` +* ***Step*** 4. upstream.conf配置 + +```bash + #必须要有默认upstream + + upstream stable { + server 1 + server 2 + ... + } + + #以及其他upstream + upstream bar { + + } + + upstream foo { + + } +``` + + diff --git "a/doc/redis_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" "b/doc/redis_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" new file mode 100644 index 0000000..a799209 --- /dev/null +++ "b/doc/redis_conf_\351\205\215\347\275\256\350\277\207\347\250\213.md" @@ -0,0 +1,15 @@ +* redis的ip&port配置要与nginx.conf中一致,同时补充几点redis的优化配置 + + +```bash +port 6379 +unixsocket /tmp/redis.sock +unixsocketperm 766 + +timeout 0 +tcp-keepalive 120 +tcp-backlog 20000 + +maxclients 262144 +``` + diff --git "a/doc/\345\237\272\344\272\216\345\212\250\346\200\201\347\255\226\347\225\245\347\232\204\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237.pdf" "b/doc/\345\237\272\344\272\216\345\212\250\346\200\201\347\255\226\347\225\245\347\232\204\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237.pdf" new file mode 100755 index 0000000..3ee23c1 Binary files /dev/null and "b/doc/\345\237\272\344\272\216\345\212\250\346\200\201\347\255\226\347\225\245\347\232\204\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237.pdf" differ diff --git "a/doc/\346\263\250\351\207\212\346\226\207\346\241\243.md" "b/doc/\346\263\250\351\207\212\346\226\207\346\241\243.md" new file mode 100644 index 0000000..10a753f --- /dev/null +++ "b/doc/\346\263\250\351\207\212\346\226\207\346\241\243.md" @@ -0,0 +1,73 @@ + + + + + + +系统介绍 +========================= +**dygateway**的功能由**nginx.conf**和**luacode**实现。 + +* **luacode**中包括**ab**和**dyupsc**两部分,前者是灰度系统的lua代码,后者是动态upstream系统的lua代码 + * ab文件夹是灰度系统源码,包括策略管理、运行时信息管理以及分流功能 + * dyupsc文件夹是动态upstream系统源码,包括动态upstream和member功能 + + + diff --git "a/doc/\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237\345\216\213\346\265\213\346\212\245\345\221\212.pdf" "b/doc/\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237\345\216\213\346\265\213\346\212\245\345\221\212.pdf" new file mode 100755 index 0000000..450866f Binary files /dev/null and "b/doc/\347\201\260\345\272\246\345\217\221\345\270\203\347\263\273\347\273\237\345\216\213\346\265\213\346\212\245\345\221\212.pdf" differ diff --git a/lib/abtesting/adapter/policy.lua b/lib/abtesting/adapter/policy.lua index 160f543..1325535 100644 --- a/lib/abtesting/adapter/policy.lua +++ b/lib/abtesting/adapter/policy.lua @@ -6,14 +6,10 @@ local modulename = "abtestingAdapterPolicy" local _M = { _VERSION = "0.0.1" } local mt = { __index = _M } -local ERRORINFO = require('abtesting.error.errcode').info +local ERRORINFO = require('abtesting.error.errcode').info +local fields = require('abtesting.utils.init').fields local separator = ':' -local fields = {} -fields.divtype = 'divtype' -fields.divdata = 'divdata' -fields.idCount = 'idCount' - --- -- policyIO new function -- @param database opened redis. @@ -107,10 +103,10 @@ end -- @param id the policy identify -- @return allways returned SUCCESS _M.del = function(self, id) - local database = self.database + local database = self.database + local baseLibrary = self.baseLibrary - local policyLib = table.concat({self.baseLibrary, id}, separator) - ngx.log(ngx.ERR, policyLib) + local policyLib = baseLibrary .. ':' .. id .. ':' local keys, err = database:keys(policyLib..'*') if not keys then diff --git a/lib/abtesting/adapter/policygroup.lua b/lib/abtesting/adapter/policygroup.lua new file mode 100644 index 0000000..e01c8f5 --- /dev/null +++ b/lib/abtesting/adapter/policygroup.lua @@ -0,0 +1,191 @@ +--- +-- @classmod abtesting.adapter.policy +-- @release 0.0.1 +local modulename = "abtestingAdapterPolicyGroup" + +local _M = { _VERSION = "0.0.1" } +local mt = { __index = _M } + +local ERRORINFO = require('abtesting.error.errcode').info +local policyModule = require('abtesting.adapter.policy') +local fields = require('abtesting.utils.init').fields + +local separator = ':' +--- +-- policyIO new function +-- @param database opened redis. +-- @param baseLibrary a library(prefix of redis key) of policies. +-- @return runtimeInfoIO object +_M.new = function(self, database, groupLibrary, baseLibrary) + if not database then + error{ERRORINFO.PARAMETER_NONE, 'need avaliable redis db'} + end + if not baseLibrary then + error{ERRORINFO.PARAMETER_NONE, 'need avaliable policy baselib'} + end + + self.database = database + self.groupLibrary = groupLibrary + self.baseLibrary = baseLibrary + self.idCountKey = table.concat({groupLibrary, fields.idCount}, separator) + + local ok, err = database:exists(self.idCountKey) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + + if 0 == ok then + local ok, err = database:set(self.idCountKey, '-1') + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + end + return setmetatable(self, mt) +end + +--- +-- get id for current policy +-- @return the id +_M.getIdCount = function(self) + local database = self.database + local key = self.idCountKey + local idCount, err = database:incr(key) + if not idCount then error{ERRORINFO.REDIS_ERROR, err} end + + return idCount +end + +--- +-- private function, set diversion type +-- @param id identify a policy +-- @param divtype diversion type (ipange/uid/...) +-- @return allways returned SUCCESS +_M._setDivtype = function(self, id, divtype) + local database = self.database + local key = table.concat({self.baseLibrary, id, fields.divtype}, separator) + local ok, err = database:set(key, divtype) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end +end + +--- +-- private function, set diversion data +-- @param id identify a policy +-- @param divdata diversion data +-- @param modulename module name of diversion data (decision by diversion type) +-- @return allways returned SUCCESS +_M._setDivdata = function(self, id, divdata, modulename) + local divModule = require(modulename) + local database = self.database + local key = table.concat({self.baseLibrary, id, fields.divdata}, separator) + + divModule:new(database, key):set(divdata) +end + +_M.set = function(self, policyGroup) + + local database = self.database + local baseLibrary = self.baseLibrary + local policyMod = policyModule:new(database, baseLibrary) + + local steps = #policyGroup + local group = {} + for idx = 1, steps do + local policy = policyGroup[idx] + local id = policyMod:set(policy) + group[idx] = id + end + + local groupLibrary = self.groupLibrary + local groupid = self:getIdCount() + local groupKey = table.concat({groupLibrary, groupid}, separator) + database:init_pipeline() + for idx = 1, steps do + database:rpush(groupKey, group[idx]) + end + local ok, err = database:commit_pipeline() + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + + local ret = {} + ret.groupid = groupid + ret.group = group + + return ret +end + +_M.check = function(self, policyGroup) + + local steps = #policyGroup + local policyMod = policyModule:new(self.database, self.baseLibrary) + for idx = 1, steps do + local policy = policyGroup[idx] + local chkinfo = policyMod:check(policy) + local valid = chkinfo[1] + local info = chkinfo[2] + local desc = chkinfo[3] + if not valid then + local extra = 'policy NO.'..idx..' ' + if not desc then + desc = extra .. 'not valid' + else + desc = extra .. desc + end + return {valid, info, desc} + end + end + return {true} +end + +--- +-- delete a policy from specified redis lib +-- @param id the policy identify +-- @return allways returned SUCCESS +_M.del = function(self, id) + local database = self.database + local groupLibrary = self.groupLibrary + local baseLibrary = self.baseLibrary + + local groupKey = table.concat({groupLibrary, id}, separator) + + local group, err = database:lrange(groupKey, 0, -1) + if not group or type(group) ~= 'table' then + error{ERRORINFO.REDIS_ERROR, err} + end + + local tmpkeys = {} + local idx = 0 + for _, policyid in pairs(group) do + idx = idx + 1 + + local policyLib = table.concat({self.baseLibrary, policyid}, separator) + local keys, err = database:keys(policyLib..'*') + if not keys then error{ERRORINFO.REDIS_ERROR, err} end + + tmpkeys[idx] = keys + end + + database:init_pipeline() + for _, v in pairs(tmpkeys) do + for _, vv in pairs(v) do + database:del(vv) + end + end + database:del(groupKey) + + local ok, err = database:commit_pipeline() + if not ok then error{ERRORINFO.REDIS_ERROR, err} end +end + +_M.get = function(self, id) + + local database = self.database + local groupLibrary = self.groupLibrary + local groupKey = table.concat({groupLibrary, id}, separator) + + local group, err = database:lrange(groupKey, 0, -1) + if not group or type(group) ~= 'table' then + error{ERRORINFO.REDIS_ERROR, err} + end + + local ret = {} + ret.groupid = id + ret.group = group + + return ret +end +return _M diff --git a/lib/abtesting/adapter/runtime.lua b/lib/abtesting/adapter/runtime.lua index 7a87e3a..f3bdc9f 100644 --- a/lib/abtesting/adapter/runtime.lua +++ b/lib/abtesting/adapter/runtime.lua @@ -8,15 +8,9 @@ local metatable = {__index = _M} _M._VERSION = "0.0.1" -local ERRORINFO = require('abtesting.error.errcode').info +local ERRORINFO = require('abtesting.error.errcode').info +local fields = require('abtesting.utils.init').fields ---- --- @field fields --- @warning this is a conf, may other file later -local fields= {} -fields.divModulename = 'divModulename' -fields.divDataKey = 'divDataKey' -fields.userInfoModulename = 'userInfoModulename' local separator = ':' --- diff --git a/lib/abtesting/adapter/runtimegroup.lua b/lib/abtesting/adapter/runtimegroup.lua new file mode 100644 index 0000000..70aabe2 --- /dev/null +++ b/lib/abtesting/adapter/runtimegroup.lua @@ -0,0 +1,173 @@ +--- +-- @classmod abtesting.adapter.runtime +-- @release 0.0.1 +local modulename = "abtestingAdapterRuntimeGroup" + +local _M = {} +local metatable = {__index = _M} + +_M._VERSION = "0.0.1" + +local ERRORINFO = require('abtesting.error.errcode').info +local runtimeModule = require('abtesting.adapter.runtime') +local systemConf = require('abtesting.utils.init') +local policyModule = require('abtesting.adapter.policy') +local policyGroupModule = require('abtesting.adapter.policygroup') +local prefixConf = systemConf.prefixConf +local divtypes = systemConf.divtypes +local policyLib = prefixConf.policyLibPrefix +local policyGroupLib = prefixConf.policyGroupPrefix +local indices = systemConf.indices +local fields = systemConf.fields + +local separator = ':' + +--- +-- runtimeInfoIO new function +-- @param database opened redis +-- @param baseLibrary a library(prefix of redis key) of runtime info +-- @return runtimeInfoIO object +_M.new = function(self, database, baseLibrary) + if not database then + error{ERRORINFO.PARAMETER_NONE, 'need a object of redis'} + end if not baseLibrary then + error{ERRORINFO.PARAMETER_NONE, 'need a library of runtime info'} + end + + self.database = database + self.baseLibrary = baseLibrary + + return setmetatable(self, metatable) +end + +--- +-- set runtime info(diversion modulename and diversion metadata key) +-- @param domain is a domain name to search runtime info +-- @param ... now is diversion modulename and diversion data key +-- @return if returned, the return value always SUCCESS +_M.set = function(self, domain, policyGroupId, divsteps) + local database = self.database + local baseLibrary = self.baseLibrary + local prefix = baseLibrary .. ':' .. domain + + local policyGroupMod = policyGroupModule:new(database, policyGroupLib, policyLib) + local policyGroup = policyGroupMod:get(policyGroupId) + local groupid = policyGroup.groupid + local group = policyGroup.group + +-- 添加 group为空错误 + if #group < 1 then + error{ERRORINFO.PARAMETER_TYPE_ERROR, 'blank policyGroupId'} + end + + if divsteps and divsteps > #group then + error{ERRORINFO.PARAMETER_TYPE_ERROR, 'divsteps is deeper than policyGroupID'} + end + + if not divsteps then divsteps = #group end + + for i = 1, divsteps do + local idx = indices[i] + local policyId = group[i] + + local policyMod = policyModule:new(database, policyLib) + local policy = policyMod:get(policyId) + + local divtype = policy.divtype + local divdata = policy.divdata + if divtype == ngx.null or + divdata == ngx.null then + error{ERRORINFO.POLICY_BLANK_ERROR, 'policy NO.'..policyId} + end + + -- if not divtypes[divtype] then + -- -- unsupported divtype + -- end + + local divModulename = table.concat({'abtesting', 'diversion', divtype}, '.') + local divDataKey = table.concat({policyLib, policyId, fields.divdata}, ':') + local userInfoModulename= table.concat({'abtesting', 'userinfo', divtypes[divtype]}, '.') + + local runtimeMod = runtimeModule:new(database, prefix) + runtimeMod:set(idx, divModulename, divDataKey, userInfoModulename) + end + + local divStep = prefix .. ':' .. fields.divsteps + database:set(divStep, divsteps) + + return ERRORINFO.SUCCESS +end + +--- +-- get runtime info(diversion modulename and diversion metadata key) +-- @param domain is a domain name to search runtime info +-- @return a table of diversion modulename and diversion metadata key +_M.get = function(self, domain) + local database = self.database + local baseLibrary = self.baseLibrary + local prefix = baseLibrary .. ':' .. domain + + local ret = {} + + local divStep = prefix .. ':' .. fields.divsteps + local ok, err = database:get(divStep) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + + local divsteps = tonumber(ok) + if not divsteps then + ret.divsteps = 0 + ret.runtimegroup = {} + return ret + end + + local runtimeGroup = {} + for i = 1, divsteps do + local idx = indices[i] + local runtimeMod = runtimeModule:new(database, prefix) + local runtimeInfo = runtimeMod:get(idx) + local rtInfo = {} + rtInfo[fields.divModulename] = runtimeInfo[1] + rtInfo[fields.divDataKey] = runtimeInfo[2] + rtInfo[fields.userInfoModulename] = runtimeInfo[3] + + runtimeGroup[idx] = rtInfo + end + + ret.divsteps = divsteps + ret.runtimegroup = runtimeGroup + return ret + +end + +--- +-- delete runtime info(diversion modulename and diversion metadata key) +-- @param domain a domain of delete +-- @return if returned, the return value always SUCCESS +_M.del = function(self, domain) + local database = self.database + local baseLibrary = self.baseLibrary + local prefix = baseLibrary .. ':' .. domain + + local divStep = prefix .. ':' .. fields.divsteps + local ok, err = database:get(divStep) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + + local divsteps = tonumber(ok) + if not divsteps or divsteps == ngx.null or divsteps == null then + local ok, err = database:del(divStep) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + return nil + end + + for i = 1, divsteps do + local idx = indices[i] + local runtimeMod = runtimeModule:new(database, prefix) + local ok, err = runtimeMod:del(idx) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end + end + + local ok, err = database:del(divStep) + if not ok then error{ERRORINFO.REDIS_ERROR, err} end +end + +return _M diff --git a/lib/abtesting/diversion/url.lua b/lib/abtesting/diversion/url.lua new file mode 100644 index 0000000..3c4edc3 --- /dev/null +++ b/lib/abtesting/diversion/url.lua @@ -0,0 +1,91 @@ +local modulename = "abtestingDiversionUrl" + +local _M = {} +local mt = { __index = _M } +_M._VERSION = "0.0.1" + +local ERRORINFO = require('abtesting.error.errcode').info + +local k_url = 'url' +local k_upstream = 'upstream' + +_M.new = function(self, database, policyLib) + if not database then + error{ERRORINFO.PARAMETER_NONE, 'need avaliable redis db'} + end if not policyLib then + error{ERRORINFO.PARAMETER_NONE, 'need avaliable policy lib'} + end + + self.database = database + self.policyLib = policyLib + return setmetatable(self, mt) +end + +-- policy is in format as {{url = '/a/b/c/d', upstream = '192.132.23.125'}} +_M.check = function(self, policy) + for _, v in pairs(policy) do + local url = v[k_url] + local upstream = v[k_upstream] + + if not url or not upstream then + local info = ERRORINFO.POLICY_INVALID_ERROR + local desc = ' need '..k_url..' and '..k_upstream + return {false, info, desc} + end + +-- if suffix < 0 or suffix > 9 then +-- local info = ERRORINFO.POLICY_INVALID_ERROR +-- local desc = 'suffix is not between [0 and 10]' +-- return {false, info, desc} +-- end + end + + return {true} +end + +_M.set = function(self, policy) + local database = self.database + local policyLib = self.policyLib + + database:init_pipeline() + for _, v in pairs(policy) do + database:hset(policyLib, v[k_url], v[k_upstream]) + end + local ok, err = database:commit_pipeline() + if not ok then + error{ERRORINFO.REDIS_ERROR, err} + end + +end + +_M.get = function(self) + local database = self.database + local policyLib = self.policyLib + + local data, err = database:hgetall(policyLib) + if not data then + error{ERRORINFO.REDIS_ERROR, err} + end + + return data +end + +_M.getUpstream = function(self, url) + + local url = url; + local database = self.database + local policyLib = self.policyLib + + local upstream, err = database:hget(policyLib , url) + if not upstream then error{ERRORINFO.REDIS_ERROR, err} end + + if upstream == ngx.null then + return nil + else + return upstream + end + +end + + +return _M diff --git a/lib/abtesting/error/errcode.lua b/lib/abtesting/error/errcode.lua index 6d638d5..4c15c65 100644 --- a/lib/abtesting/error/errcode.lua +++ b/lib/abtesting/error/errcode.lua @@ -6,40 +6,45 @@ _M._VERSION = '0.0.1' _M.info = { -- index code desc -- SUCCESS - ["SUCCESS"] = { 200, 'success '}, + ["SUCCESS"] = { 200, 'success '}, -- System Level ERROR - ['REDIS_ERROR'] = { 40101, 'redis error for '}, - ['POLICY_DB_ERROR'] = { 40102, 'policy in db error '}, - ['RUNTIME_DB_ERROR'] = { 40103, 'runtime info in db error '}, + ['REDIS_ERROR'] = { 40101, 'redis error for '}, + ['POLICY_DB_ERROR'] = { 40102, 'policy in db error '}, + ['RUNTIME_DB_ERROR'] = { 40103, 'runtime info in db error '}, - ['LUA_RUNTIME_ERROR'] = { 40201, 'lua runtime error '}, - ['BLANK_INFO_ERROR'] = { 40202, 'errinfo blank in handler '}, + ['LUA_RUNTIME_ERROR'] = { 40201, 'lua runtime error '}, + ['BLANK_INFO_ERROR'] = { 40202, 'errinfo blank in handler '}, -- Service Level ERROR -- input or parameter error - ['PARAMETER_NONE'] = { 50101, 'expected parameter for '}, - ['PARAMETER_ERROR'] = { 50102, 'parameter error for '}, - ['PARAMETER_NEEDED'] = { 50103, 'need parameter for '}, + ['PARAMETER_NONE'] = { 50101, 'expected parameter for '}, + ['PARAMETER_ERROR'] = { 50102, 'parameter error for '}, + ['PARAMETER_NEEDED'] = { 50103, 'need parameter for '}, ['PARAMETER_TYPE_ERROR'] = { 50104, 'parameter type error for '}, -- input policy error ['POLICY_INVALID_ERROR'] = { 50201, 'policies invalid for ' }, - ['POLICY_BUSY_ERROR'] = { 50202, 'policy is busy and policyID is ' }, + ['POLICY_BUSY_ERROR'] = { 50202, 'policy is busy and policyID is ' }, -- redis connect error - ['REDIS_CONNECT_ERROR'] = { 50301, 'redis connect error for '}, + ['REDIS_CONNECT_ERROR'] = { 50301, 'redis connect error for '}, ['REDIS_KEEPALIVE_ERROR'] = { 50302, 'redis keepalive error for '}, -- runtime error - ['POLICY_BLANK_ERROR'] = { 50401, 'policy contains no data '}, + ['POLICY_BLANK_ERROR'] = { 50401, 'policy contains no data '}, ['RUNTIME_BLANK_ERROR'] = { 50402, 'expect runtime info for '}, - ['MODULE_BLANK_ERROR'] = { 50403, 'no required module for '}, + ['MODULE_BLANK_ERROR'] = { 50403, 'no required module for '}, ['USERINFO_BLANK_ERROR'] = { 50404, 'no userinfo fetched from '}, + + ['ARG_BLANK_ERROR'] = { 50405, 'no arg fetched from req '}, + ['ACTION_BLANK_ERROR'] = { 50406, 'no action fetched from '}, + + ['DOACTION_ERROR'] = { 50501, 'error during action of '}, -- unknown reason - ['UNKNOWN_ERROR'] = { 50501, 'unknown reason '}, + ['UNKNOWN_ERROR'] = { 50601, 'unknown reason '}, } return _M diff --git a/lib/abtesting/userinfo/cityParser.lua b/lib/abtesting/userinfo/cityParser.lua index 4b20713..10c9f97 100644 --- a/lib/abtesting/userinfo/cityParser.lua +++ b/lib/abtesting/userinfo/cityParser.lua @@ -5,7 +5,6 @@ local _M = { _M.get = function() local u = ngx.var.arg_city - ngx.log(ngx.ERR, u) return u end return _M diff --git a/lib/abtesting/userinfo/urlParser.lua b/lib/abtesting/userinfo/urlParser.lua new file mode 100644 index 0000000..49c52f6 --- /dev/null +++ b/lib/abtesting/userinfo/urlParser.lua @@ -0,0 +1,10 @@ + +local _M = { + _VERSION = '0.01' +} + +_M.get = function() + local u = ngx.var.uri + return u +end +return _M diff --git a/lib/abtesting/utils/cache.lua b/lib/abtesting/utils/cache.lua new file mode 100644 index 0000000..345e42b --- /dev/null +++ b/lib/abtesting/utils/cache.lua @@ -0,0 +1,126 @@ +local modulename = "abtestingCache" + +local _M = {} +_M._VERSION = '0.0.1' + +local ERRORINFO = require('abtesting.error.errcode').info +local systemConf = require('abtesting.utils.init') + +local prefixConf = systemConf.prefixConf +local runtimeLib = prefixConf.runtimeInfoPrefix + +local indices = systemConf.indices +local fields = systemConf.fields + +local divConf = systemConf.divConf +local shdict_expire = divConf.shdict_expire or 60 + +_M.new = function(self, sharedDict) + if not sharedDict then + error{ERRORINFO.ARG_BLANK_ERROR, 'cache name valid from nginx.conf'} + end + + self.cache = ngx.shared[sharedDict] + if not self.cache then + error{ERRORINFO.PARAMETER_ERROR, 'cache name [' .. sharedDict .. '] valid from nginx.conf'} + end + + return setmetatable(self, { __index = _M } ) +end + +local isNULL = function(v) + return not v or v == ngx.null +end + +local areNULL = function(v1, v2, v3) + if isNULL(v1) or isNULL(v2) or isNULL(v3) then + return true + end + return false +end + +_M.getSteps = function(self, hostname) + local cache = self.cache + local k_divsteps = runtimeLib..':'..hostname..':'..fields.divsteps + local divsteps = cache:get(k_divsteps) + return tonumber(divsteps) +end + +_M.getRuntime = function(self, hostname, divsteps) + local cache = self.cache + local runtimegroup = {} + local prefix = runtimeLib .. ':' .. hostname + for i = 1, divsteps do + local idx = indices[i] + local k_divModname = prefix .. ':'..idx..':'..fields.divModulename + local k_divDataKey = prefix .. ':'..idx..':'..fields.divDataKey + local k_userInfoModname = prefix .. ':'..idx..':'..fields.userInfoModulename + + local divMod, err1 = cache:get(k_divModname) + local divPolicy, err2 = cache:get(k_divDataKey) + local userInfoMod, err3 = cache:get(k_userInfoModname) + + if areNULL(divMod, divPolicy, userInfoMod) then + return false + end + + local runtime = {} + runtime[fields.divModulename ] = divMod + runtime[fields.divDataKey ] = divPolicy + runtime[fields.userInfoModulename] = userInfoMod + runtimegroup[idx] = runtime + end + + return true, runtimegroup + +end + +_M.setRuntime = function(self, hostname, divsteps, runtimegroup) + local cache = self.cache + local prefix = runtimeLib .. ':' .. hostname + local expire = shdict_expire + + for i = 1, divsteps do + local idx = indices[i] + + local k_divModname = prefix .. ':'..idx..':'..fields.divModulename + local k_divDataKey = prefix .. ':'..idx..':'..fields.divDataKey + local k_userInfoModname = prefix .. ':'..idx..':'..fields.userInfoModulename + + local runtime = runtimegroup[idx] + local ok1, err = cache:set(k_divModname, runtime[fields.divModulename], expire) + local ok2, err = cache:set(k_divDataKey, runtime[fields.divDataKey], expire) + local ok3, err = cache:set(k_userInfoModname, runtime[fields.userInfoModulename], expire) + if areNULL(ok1, ok2, ok3) then return false end + + end + + local k_divsteps = prefix ..':'..fields.divsteps + local ok, err = cache:set(k_divsteps, divsteps, shdict_expire) + if not ok then return false end + + return true +end + +_M.getUpstream = function(self, divsteps, usertable) + local upstable = {} + local cache = self.cache + for i = 1, divsteps do + local idx = indices[i] + local info = usertable[idx] + -- ups will be an actually value or nil + if info then + local ups = cache:get(info) + upstable[idx] = ups + end + end + return upstable +end + +_M.setUpstream = function(self, info, upstream) + local cache = self.cache + local expire = shdict_expire + cache:set(info, upstream, expire) +end + +return _M diff --git a/lib/abtesting/utils/init.lua b/lib/abtesting/utils/init.lua index 49911c0..f511eb3 100644 --- a/lib/abtesting/utils/init.lua +++ b/lib/abtesting/utils/init.lua @@ -18,18 +18,22 @@ _M.divtypes = { ["uidrange"] = 'uidParser', ["uidsuffix"] = 'uidParser', ["uidappoint"] = 'uidParser', - ["arg_city"] = 'cityParser' -} + ["arg_city"] = 'cityParser', + + ["url"] = 'urlParser' +} _M.prefixConf = { - ["policyLibPrefix"] = ngx.var.policy_prefix, - ["runtimeInfoPrefix"] = ngx.var.runtime_prefix, - ["domainname"] = ngx.var.server_name, + ["policyLibPrefix"] = 'ab:policies', + ["policyGroupPrefix"] = 'ab:policygroups', + ["runtimeInfoPrefix"] = 'ab:runtimeInfo', + ["domainname"] = ngx.var.domain_name, } _M.divConf = { - ["default_backend"] = ngx.var.default_backend; - ["shdict_expire"] = ngx.var.shdict_expire; + ["default_backend"] = ngx.var.default_backend, + ["shdict_expire"] = 60, -- in s +-- ["shdict_expire"] = ngx.var.shdict_expire, } _M.cacheConf = { @@ -38,4 +42,27 @@ _M.cacheConf = { ['upstreamLock'] = ngx.var.up_cache_lock, } +_M.indices = { + 'first', 'second', 'third', + 'forth', 'fifth', 'sixth', + 'seventh', 'eighth', 'ninth' +} + +_M.fields = { + ['divModulename'] = 'divModulename', + ['divDataKey'] = 'divDataKey', + ['userInfoModulename'] = 'userInfoModulename', + ['divtype'] = 'divtype', + ['divdata'] = 'divdata', + ['idCount'] = 'idCount', + ['divsteps'] = 'divsteps' +} + +_M.loglv = { + + ['err'] = ngx.ERR, + ['info'] = ngx.INFO, ['warn'] = ngx.WARN, + ['debug'] = ngx.DEBUG, +} + return _M diff --git a/lib/abtesting/utils/log.lua b/lib/abtesting/utils/log.lua new file mode 100644 index 0000000..6b0e90e --- /dev/null +++ b/lib/abtesting/utils/log.lua @@ -0,0 +1,37 @@ +local log = ngx.log + +local ERR = ngx.ERR +local INFO = ngx.INFO +local WARN = ngx.WARN +local DEBUG = ngx.DEBUG + +local _M = {} +local mt = {__index = _M} +_M._VERSION = "0.01" + +_M.new = function (self, hostname) + self.tag = hostname + return setmetatable(self, mt) +end + +function _M.info(self, ...) + log(INFO, "ab_div host [", self.tag or 'ab_admin',"] ", ...) +end + + +function _M.warn(self, ...) + log(WARN, "ab_div host [", self.tag or 'ab_admin',"] ", ...) +end + + +function _M.errlog(self, ...) + log(ERR, "ab_div host [", self.tag or 'ab_admin',"] ", ...) +end + + +function _M.debug(self, ...) + log(DEBUG, "ab_div host [", self.tag or 'ab_admin',"] ", ...) +end + + +return _M diff --git a/lib/abtesting/utils/sema.lua b/lib/abtesting/utils/sema.lua new file mode 100644 index 0000000..bf112db --- /dev/null +++ b/lib/abtesting/utils/sema.lua @@ -0,0 +1,9 @@ +local modulename = "abtestingSema" +local _M = {} + +local semaphore = require("lua-resty-core.lib.ngx.semaphore") + +_M.sema = semaphore.new(1) +_M.upsSema = semaphore.new(1) + +return _M diff --git a/lib/abtesting/utils/utils.lua b/lib/abtesting/utils/utils.lua index 1e2fe19..c65793f 100644 --- a/lib/abtesting/utils/utils.lua +++ b/lib/abtesting/utils/utils.lua @@ -3,7 +3,9 @@ local _M = {} _M._VERSION = '0.0.1' local cjson = require('cjson.safe') ---将doresp和dolog,与handler统一起来。handler将返回一个table,结构为: +local log = require("abtesting.utils.log") +--将doresp和dolog,与handler统一起来。 +--handler将返回一个table,结构为: --[[ handler———errinfo————errcode————code | | | @@ -15,21 +17,23 @@ handler———errinfo————errcode————code | |———errstack ]]-- + _M.dolog = function(info, desc, data, errstack) +-- local errlog = 'ab_admin ' local errlog = '' local code, err = info[1], info[2] local errcode = code local errinfo = desc and err..desc or err - errlog = errlog .. ' errcode : '..errcode - errlog = errlog .. ', errinfo : '..errinfo + errlog = errlog .. 'code : '..errcode + errlog = errlog .. ', desc : '..errinfo if data then errlog = errlog .. ', extrainfo : '..data end if errstack then errlog = errlog .. ', errstack : '..errstack end - ngx.log(ngx.ERR, errlog) + return errlog end _M.doresp = function(info, desc, data) @@ -37,8 +41,8 @@ _M.doresp = function(info, desc, data) local code = info[1] local err = info[2] - response.errcode = code - response.errinfo = desc and err..desc or err + response.code = code + response.desc = desc and err..desc or err if data then response.data = data end @@ -46,4 +50,17 @@ _M.doresp = function(info, desc, data) return cjson.encode(response) end +_M.doerror = function(info, extrainfo) + local errinfo = info[1] + local errstack = info[2] + local err, desc = errinfo[1], errinfo[2] + + local dolog, doresp = _M.dolog, _M.doresp + local errlog = dolog(err, desc, extrainfo, errstack) + log:errlog(errlog) + + local response = doresp(err, desc) + return response +end + return _M diff --git a/lib/lua-resty-core/Makefile b/lib/lua-resty-core/Makefile new file mode 100644 index 0000000..834b53e --- /dev/null +++ b/lib/lua-resty-core/Makefile @@ -0,0 +1,21 @@ +OPENRESTY_PREFIX=/usr/local/openresty + +PREFIX ?= /usr/local +LUA_INCLUDE_DIR ?= $(PREFIX)/include +LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) +INSTALL ?= install + +.PHONY: all test install + +all: ; + +install: all + $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty/core/ + $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/ngx/ + $(INSTALL) lib/resty/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/ + $(INSTALL) lib/resty/core/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/core/ + $(INSTALL) lib/ngx/*.lua $(DESTDIR)$(LUA_LIB_DIR)/ngx/ + +test: all + PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t + diff --git a/lib/lua-resty-core/README.markdown b/lib/lua-resty-core/README.markdown new file mode 100644 index 0000000..9a784f3 --- /dev/null +++ b/lib/lua-resty-core/README.markdown @@ -0,0 +1,265 @@ +Name +==== + +lua-resty-core - New FFI-based Lua API for the ngx_lua module + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Prerequisites](#prerequisites) +* [API Implemented](#api-implemented) + * [resty.core.hash](#restycorehash) + * [resty.core.base64](#restycorebase64) + * [resty.core.uri](#restycoreuri) + * [resty.core.regex](#restycoreregex) + * [resty.core.exit](#restycoreexit) + * [resty.core.shdict](#restycoreshdict) + * [resty.core.var](#restycorevar) + * [resty.core.ctx](#restycorectx) + * [resty.core.request](#restycorerequest) + * [resty.core.response](#restycoreresponse) + * [resty.core.misc](#restycoremisc) + * [resty.core.time](#restycoretime) + * [resty.core.worker](#restycoreworker) + * [ngx.semaphore](#ngxsemaphore) + * [ngx.balancer](#ngxbalancer) +* [Caveat](#caveat) +* [TODO](#todo) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This library is production ready and under active development. + +Synopsis +======== + +```nginx + # nginx.conf + + http { + # you do NOT need to configure the following line when you + # are using the OpenResty bundle 1.4.3.9+. + lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + + init_by_lua ' + require "resty.core" + '; + + ... + } +``` + +Description +=========== + +This pure Lua library reimplements part of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module's +[Nginx API for Lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) +with LuaJIT FFI and installs the new FFI-based Lua API into the ngx.* and ndk.* namespaces +used by the ngx_lua module. + +In addition, this Lua library implements any significant new Lua APIs of +the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module +as proper Lua modules, like [ngx.semaphore](#ngxsemaphore) and [ngx.balancer](#ngxbalancer). + +The FFI-based Lua API can work with LuaJIT's JIT compiler. ngx_lua's default API is based on the standard +Lua C API, which will never be JIT compiled and the user Lua code is always interpreted (slowly). + +This library is shipped with the OpenResty bundle by default. So you do not really need to worry about the dependencies +and requirements. + +[Back to TOC](#table-of-contents) + +Prerequisites +============= + +* LuaJIT 2.1 (for now, it is the v2.1 git branch in the official luajit-2.0 git repository: http://luajit.org/download.html ) +* [ngx_lua](https://github.com/openresty/lua-nginx-module) v0.10.1 or later. +* [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache) + +[Back to TOC](#table-of-contents) + +API Implemented +=============== + +[Back to TOC](#table-of-contents) + +## resty.core.hash + +* [ngx.md5](https://github.com/openresty/lua-nginx-module#ngxmd5) +* [ngx.md5_bin](https://github.com/openresty/lua-nginx-module#ngxmd5_bin) +* [ngx.sha1_bin](https://github.com/openresty/lua-nginx-module#ngxsha1_bin) + +[Back to TOC](#table-of-contents) + +## resty.core.base64 + +* [ngx.encode_base64](https://github.com/openresty/lua-nginx-module#ngxencode_base64) +* [ngx.decode_base64](https://github.com/openresty/lua-nginx-module#ngxdecode_base64) + +[Back to TOC](#table-of-contents) + +## resty.core.uri + +* [ngx.escape_uri](https://github.com/openresty/lua-nginx-module#ngxescape_uri) +* [ngx.unescape_uri](https://github.com/openresty/lua-nginx-module#ngxunescape_uri) + +[Back to TOC](#table-of-contents) + +## resty.core.regex + +* [ngx.re.match](https://github.com/openresty/lua-nginx-module#ngxrematch) +* [ngx.re.find](https://github.com/openresty/lua-nginx-module#ngxrefind) +* [ngx.re.sub](https://github.com/openresty/lua-nginx-module#ngxresub) +* [ngx.re.gsub](https://github.com/openresty/lua-nginx-module#ngxregsub) + +[Back to TOC](#table-of-contents) + +## resty.core.exit + +* [ngx.exit](https://github.com/openresty/lua-nginx-module#ngxexit) + +[Back to TOC](#table-of-contents) + +## resty.core.shdict + +* [ngx.shared.DICT.get](https://github.com/openresty/lua-nginx-module#ngxshareddictget) +* [ngx.shared.DICT.get_stale](https://github.com/openresty/lua-nginx-module#ngxshareddictget_stale) +* [ngx.shared.DICT.incr](https://github.com/openresty/lua-nginx-module#ngxshareddictincr) +* [ngx.shared.DICT.set](https://github.com/openresty/lua-nginx-module#ngxshareddictset) +* [ngx.shared.DICT.safe_set](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_set) +* [ngx.shared.DICT.add](https://github.com/openresty/lua-nginx-module#ngxshareddictadd) +* [ngx.shared.DICT.safe_add](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_add) +* [ngx.shared.DICT.replace](https://github.com/openresty/lua-nginx-module#ngxshareddictreplace) +* [ngx.shared.DICT.delete](https://github.com/openresty/lua-nginx-module#ngxshareddictdelete) +* [ngx.shared.DICT.flush_all](https://github.com/openresty/lua-nginx-module#ngxshareddictflush_all) + +[Back to TOC](#table-of-contents) + +## resty.core.var + +* [ngx.var.VARIABLE](https://github.com/openresty/lua-nginx-module#ngxvarvariable) + +[Back to TOC](#table-of-contents) + +## resty.core.ctx + +* [ngx.ctx](https://github.com/openresty/lua-nginx-module#ngxctx) + +[Back to TOC](#table-of-contents) + +## resty.core.request + +* [ngx.req.get_headers](https://github.com/openresty/lua-nginx-module#ngxreqget_headers) +* [ngx.req.get_uri_args](https://github.com/openresty/lua-nginx-module#ngxreqget_uri_args) +* [ngx.req.start_time](https://github.com/openresty/lua-nginx-module#ngxreqstart_time) +* [ngx.req.get_method](https://github.com/openresty/lua-nginx-module#ngxreqget_method) +* [ngx.req.set_method](https://github.com/openresty/lua-nginx-module#ngxreqset_method) +* [ngx.req.set_header](https://github.com/openresty/lua-nginx-module#ngxreqset_header) (partial: table-typed header values not supported yet) +* [ngx.req.clear_header](https://github.com/openresty/lua-nginx-module#ngxreqclear_header) + +[Back to TOC](#table-of-contents) + +## resty.core.response + +* [ngx.header.HEADER](https://github.com/openresty/lua-nginx-module#ngxheaderheader) + +[Back to TOC](#table-of-contents) + +## resty.core.misc + +* [ngx.status](https://github.com/openresty/lua-nginx-module#ngxstatus) +* [ngx.is_subrequest](https://github.com/openresty/lua-nginx-module#ngxis_subrequest) +* [ngx.headers_sent](https://github.com/openresty/lua-nginx-module#ngxheaders_sent) + +[Back to TOC](#table-of-contents) + +## resty.core.time + +* [ngx.time](https://github.com/openresty/lua-nginx-module#ngxtime) +* [ngx.now](https://github.com/openresty/lua-nginx-module#ngxnow) + +[Back to TOC](#table-of-contents) + +## resty.core.worker + +* [ngx.worker.exiting](https://github.com/openresty/lua-nginx-module#ngxworkerexiting) +* [ngx.worker.pid](https://github.com/openresty/lua-nginx-module#ngxworkerpid) + +[Back to TOC](#table-of-contents) + +## ngx.semaphore + +This Lua module implements a semaphore API for efficient "light thread" synchronization, +which can work across different requests (but not across nginx worker processes). + +See the [documentation](./lib/ngx/semaphore.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.balancer + +This Lua module implements for defining dynamic upstream balancers in Lua. + +See the [documentation](./lib/ngx/balancer.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +Caveat +====== + +If the user Lua code is not JIT compiled, then use of this library may +lead to performance drop in interpreted mode. You will only observe +speedup when you get a good part of your user Lua code JIT compiled. + +[Back to TOC](#table-of-contents) + +TODO +==== + +* Re-implement `ngx_lua`'s cosocket API with FFI. +* Re-implement `ngx_lua`'s `ngx.get_phase` API function with FFI. +* Re-implement `ngx_lua`'s `ngx.eof` and `ngx.flush` API functions with FFI. + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun "agentzh" Zhang (章亦春) , CloudFlare Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2013-2016, by Yichun "agentzh" Zhang, CloudFlare Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module#readme +* LuaJIT FFI: http://luajit.org/ext_ffi.html + +[Back to TOC](#table-of-contents) + diff --git a/lib/lua-resty-core/lib/ngx/balancer.lua b/lib/lua-resty-core/lib/ngx/balancer.lua new file mode 100644 index 0000000..2d24c13 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/balancer.lua @@ -0,0 +1,104 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_str = ffi.string +local errmsg = base.get_errmsg_ptr() +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local int_out = ffi.new("int[1]") +local getfenv = getfenv +local error = error +local type = type +local tonumber = tonumber + + +ffi.cdef[[ +int ngx_http_lua_ffi_balancer_set_current_peer(ngx_http_request_t *r, + const unsigned char *addr, size_t addr_len, int port, char **err); + +int ngx_http_lua_ffi_balancer_set_more_tries(ngx_http_request_t *r, + int count, char **err); + +int ngx_http_lua_ffi_balancer_get_last_failure(ngx_http_request_t *r, + int *status, char **err); +]] + + +local peer_state_names = { + [1] = "keepalive", + [2] = "next", + [4] = "failed", +} + + +local _M = { version = base.version } + + +function _M.set_current_peer(addr, port) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if not port then + port = 0 + elseif type(port) ~= "number" then + port = tonumber(port) + end + + local rc = C.ngx_http_lua_ffi_balancer_set_current_peer(r, addr, #addr, + port, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_more_tries(count) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_balancer_set_more_tries(r, count, errmsg) + if rc == FFI_OK then + if errmsg[0] == nil then + return true + end + return true, ffi_str(errmsg[0]) -- return the warning + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.get_last_failure() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local state = C.ngx_http_lua_ffi_balancer_get_last_failure(r, + int_out, + errmsg) + + if state == 0 then + return nil + end + + if state == FFI_ERROR then + return nil, nil, ffi_str(errmsg[0]) + end + + return peer_state_names[state] or "unknown", int_out[0] +end + + +return _M diff --git a/lib/lua-resty-core/lib/ngx/balancer.md b/lib/lua-resty-core/lib/ngx/balancer.md new file mode 100644 index 0000000..9c6ec62 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/balancer.md @@ -0,0 +1,221 @@ +Name +==== + +ngx.balancer - Lua API for defining dynamic upstream balancers in Lua + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [set_current_peer](#set_current_peer) + * [set_more_tries](#set_more_tries) + * [get_last_failure](#get_last_failure) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +http { + upstream backend { + server 0.0.0.1; # just an invalid address as a place holder + + balancer_by_lua_block { + local balancer = require "ngx.balancer" + + -- well, usually we calculate the peer's host and port + -- according to some balancing policies instead of using + -- hard-coded values like below + local host = "127.0.0.2" + local port = 8080 + + local ok, err = balancer.set_current_peer(host, port) + if not ok then + ngx.log(ngx.ERR, "failed to set the current peer: ", err) + return ngx.exit(500) + end + } + + keepalive 10; # connection pool + } + + server { + # this is the real entry point + listen 80; + + location / { + # make use of the upstream named "backend" defined above: + proxy_pass http://backend/fake; + } + } + + server { + # this server is just for mocking up a backend peer here... + listen 127.0.0.2:8080; + + location = /fake { + echo "this is the fake backend peer..."; + } + } +} +``` + +Description +=========== + +This Lua module provides API functions to allow defining highly dynamic NGINX load balancers for +any existing nginx upstream modules like [http://nginx.org/en/docs/http/ngx_http_proxy_module.html ngx_proxy] and +[http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html ngx_fastcgi]. + +It allows you to dynamically select a backend peer to connect to (or retry) on a per-request +basis from a list of backend peers which may also be dynamic. + +[Back to TOC](#table-of-contents) + +Methods +======= + +All the methods of this module are static (or module-level). That is, you do not need an object (or instance) +to call these methods. + +[Back to TOC](#table-of-contents) + +set_current_peer +---------------- +**syntax:** `ok, err = balancer.set_current_peer(host, port)` + +**context:** *balancer_by_lua** + +Sets the peer address (host and port) for the current backend query (which may be a retry). + +Domain names in `host` do not make sense. You need to use OpenResty libraries like +[lua-resty-dns](https://github.com/openresty/lua-resty-dns) to obtain IP address(es) from +all the domain names before entering the `balancer_by_lua*` handler (for example, +you can perform DNS lookups in an earlier phase like [access_by_lua*](https://github.com/openresty/lua-nginx-module#access_by_lua) +and pass the results to the `balancer_by_lua*` handler via [ngx.ctx](https://github.com/openresty/lua-nginx-module#ngxctx). + +[Back to TOC](#table-of-contents) + +set_more_tries +-------------- +**syntax:** `ok, err = balancer.set_more_tries(count)` + +**context:** *balancer_by_lua** + +Sets the tries performed when the current attempt (which may be a retry) fails (as determined +by directives like [proxy_next_upstream](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream), depending on what +particular nginx uptream module you are currently using. Note that the current attempt is *excluded* in the `count` number set here. + +Please note that, the total number of tries in a single downstream request cannot exceed the +hard limit configured by directives like [proxy_next_upstream_tries](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream_tries), +depending on what concrete nginx upstream module you are using. When exceeding this limit, +the `count` value will get reduced to meet the limit and the second return value will be +the string `"reduced tries due to limit"`, which is a warning, while the first return value +is still a `true` value. + +[Back to TOC](#table-of-contents) + +get_last_failure +---------------- +**syntax:** `state_name, status_code = balancer.get_last_failure()` + +**context:** *balancer_by_lua** + +Retrieves the failure details about the previous failed attempt (if any) when the `next_upstream` retrying +mechanism is in action. When there was indeed a failed previous attempt, it returned a string descrbing +that attempt's state name, as well as an integer describing the status code of that attempt. + +Possible state names are as follows: +* `"next"` + Failures due to bad status codes sent from the backend server. The origin's response is sane though, which means the backend connection +can still be reused for future requests. +* `"failed"` + Fatal errors while communicating to the backend server (like connection timeouts, connection resets, and etc). In this case, +the backend connection must be aborted and cannot get reused. + +Possible status codes are those HTTP error status codes like `502` and `504`. + +When the current attempt is the first attempt for the current downstream request (which means +there is no previous attempts at all), this +method always returns a single `nil` value. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), CloudFlare Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015, by Yichun "agentzh" Zhang, CloudFlare Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [balancer_by_lua*](https://github.com/openresty/lua-nginx-module#balancer_by_lua_block) directive. +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* OpenResty: http://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/lua-resty-core/lib/ngx/ocsp.lua b/lib/lua-resty-core/lib/ngx/ocsp.lua new file mode 100644 index 0000000..495a2c6 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/ocsp.lua @@ -0,0 +1,149 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_str = ffi.string +local getfenv = getfenv +local error = error +local tonumber = tonumber +local errmsg = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local FFI_BUSY = base.FFI_BUSY + + +ffi.cdef[[ +int ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain( + const char *chain_data, size_t chain_len, char *out, size_t *out_size, + char **err); + +int ngx_http_lua_ffi_ssl_create_ocsp_request(const char *chain_data, + size_t chain_len, unsigned char *out, size_t *out_size, char **err); + +int ngx_http_lua_ffi_ssl_validate_ocsp_response(const unsigned char *resp, + size_t resp_len, const char *chain_data, size_t chain_len, + unsigned char *errbuf, size_t *errbuf_size); + +int ngx_http_lua_ffi_ssl_set_ocsp_status_resp(ngx_http_request_t *r, + const unsigned char *resp, size_t resp_len, char **err); +]] + + +local _M = { version = base.version } + + +function _M.get_ocsp_responder_from_der_chain(certs, maxlen) + + local buf_size = maxlen + if not buf_size then + buf_size = get_string_buf_size() + end + local buf = get_string_buf(buf_size) + + local sizep = get_size_ptr() + sizep[0] = buf_size + + local rc = C.ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain(certs, + #certs, buf, sizep, errmsg) + + if rc == FFI_DECLINED then + return nil + end + + if rc == FFI_OK then + return ffi_str(buf, sizep[0]) + end + + if rc == FFI_BUSY then + return ffi_str(buf, sizep[0]), "truncated" + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.create_ocsp_request(certs, maxlen) + + local buf_size = maxlen + if not buf_size then + buf_size = get_string_buf_size() + end + local buf = get_string_buf(buf_size) + + local sizep = get_size_ptr() + sizep[0] = buf_size + + local rc = C.ngx_http_lua_ffi_ssl_create_ocsp_request(certs, + #certs, buf, sizep, + errmsg) + + if rc == FFI_OK then + return ffi_str(buf, sizep[0]) + end + + if rc == FFI_BUSY then + return nil, ffi_str(errmsg[0]) .. ": " .. tonumber(sizep[0]) + .. " > " .. buf_size + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.validate_ocsp_response(resp, chain, max_errmsg_len) + + local errbuf_size = max_errmsg_len + if not errbuf_size then + errbuf_size = get_string_buf_size() + end + local errbuf = get_string_buf(errbuf_size) + + local sizep = get_size_ptr() + sizep[0] = errbuf_size + + local rc = C.ngx_http_lua_ffi_ssl_validate_ocsp_response( + resp, #resp, chain, #chain, errbuf, sizep) + + if rc == FFI_OK then + return true + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errbuf, sizep[0]) +end + + +function _M.set_ocsp_status_resp(ocsp_resp) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_ocsp_status_resp(r, ocsp_resp, + #ocsp_resp, + errmsg) + + if rc == FFI_DECLINED then + -- no client status req + return true, "no status req" + end + + if rc == FFI_OK then + return true + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errmsg[0]) +end + + +return _M diff --git a/lib/lua-resty-core/lib/ngx/ocsp.md b/lib/lua-resty-core/lib/ngx/ocsp.md new file mode 100644 index 0000000..7fc9bb1 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/ocsp.md @@ -0,0 +1,298 @@ +Name +==== + +ngx.ocsp - Lua API for implementing OCSP stapling in ssl_certificate_by_lua* + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [get_ocsp_responder_from_der_chain](#get_ocsp_responder_from_der_chain) + * [create_ocsp_request](#create_ocsp_request) + * [validate_ocsp_response](#validate_ocsp_response) + * [set_ocsp_status_resp](#set_ocsp_status_resp) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +# Note: you do not need the following line if you are using +# OpenResty 1.9.7.2+. +lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + +server { + listen 443 ssl; + server_name test.com; + + # useless placeholders: just to shut up NGINX configuration + # loader errors: + ssl_certificate /path/to/fallback.crt; + ssl_certificate_key /path/to/fallback.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + local http = require "resty.http.simple" + + -- assuming the user already defines the my_load_certificate_chain() + -- herself. + local pem_cert_chain = assert(my_load_certificate_chain()) + + local der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain) + if not der_cert_chain then + ngx.log(ngx.ERR, "failed to convert certificate chain ", + "from PEM to DER: ", err) + return ngx.exit(ngx.ERROR) + end + + local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain) + if not ocsp_url then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + return ngx.exit(ngx.ERROR) + end + + print("ocsp_url: ", ocsp_url) + + -- use cosocket-based HTTP client libraries like lua-resty-http-simple + -- to send the request (url + ocsp_req as POST params or URL args) to + -- CA's OCSP server. assuming the server returns the OCSP response + -- in the Lua varaible, resp. + + local schema, host, port, ocsp_uri, err = parse_url(ocsp_url) + + local ocsp_req, err = ocsp.create_ocsp_request(der_cert_chain) + if not ocsp_req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return ngx.exit(ngx.ERROR) + end + + local res, err = http.request(host, port, { + path = ocsp_uri, + headers = { Host = host, + ["Content-Type"] = "application/ocsp-request" }, + timeout = 10000, -- 10 sec + method = "POST", + body = ocsp_req, + maxsize = 102400, -- 100KB + }) + + if not res then + ngx.log(ngx.ERR, "OCSP responder query failed: ", err) + return ngx.exit(ngx.ERROR) + end + + local http_status = res.status + + if http_status ~= 200 then + ngx.log(ngx.ERR, "OCSP responder returns bad HTTP status code ", + http_status) + return ngx.exit(ngx.ERROR) + end + + local ocsp_resp = res.body + + if ocsp_resp and #ocsp_resp > 0 then + local ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain) + if not ok then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return ngx.exit(ngx.ERROR) + end + + -- set the OCSP stapling + ok, err = ocsp.set_ocsp_status_resp(resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return ngx.exit(ngx.ERROR) + end + end + } + + location / { + root html; + } +} + +``` + +Description +=========== + +This Lua module provides API to perform OCSP queries, OCSP response validations, and +OCSP stapling planting. + +Usually, this module is used together with the [ngx.ssl](ssl.md) module in the +context of [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +(of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module). + +To load the `ngx.ocsp` module in Lua, just write + +```lua +local ocsp = require "ngx.ocsp" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +get_ocsp_responder_from_der_chain +--------------------------------- +**syntax:** `ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain, max_len)` + +**context:** *any* + +Extracts the OCSP responder URL (like `"http://test.com/ocsp/"`) from the SSL server certificate chain in the DER format. + +Usually the SSL server certificate chain is originally formatted in PEM. You can use the Lua API +provided by the [ngx.ssl](ssl.md) module to do the PEM to DER conversion. + +The optional `max_len` argument specifies the maximum length of OCSP URL allowed. This determines +the buffer size; so do not specify an unnecessarily large value here. It defaults to the internal +string buffer size used throughout this `lua-resty-core` library (usually default to 4KB). + +In case of failures, returns `nil` and a string describing the error. + +[Back to TOC](#table-of-contents) + +create_ocsp_request +------------------- +**syntax:** `ocsp_req, err = ocsp.create_ocsp_request(der_cert_chain, max_len)` + +**context:** *any* + +Builds an OCSP request from the SSL server certificate chain in the DER format, which +can be used to send to the OCSP server for validation. + +The optional `max_len` argument specifies the maximum length of the OCSP request allowed. +This value determines the size of the internal buffer allocated, so do not specify an +unnecessarily large value here. It defaults to the internal string buffer size used +throughout this `lua-resty-core` library (usually defaults to 4KB). + +In case of failures, returns `nil` and a string describing the error. + +The raw OCSP response data can be used as the request body directly if the POST method +is used for the OCSP request. But for GET requests, you need to do base64 encoding and +then URL encoding on the data yourself before appending it to the OCSP URL obtained +by the [get_ocsp_responder_from_der_chain](#get_ocsp_responder_from_der_chain) function. + +[Back to TOC](#table-of-contents) + +validate_ocsp_response +---------------------- +**syntax:** `ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain, max_err_msg_len)` + +**context:** *any* + +Validates the raw OCSP response data specified by the `ocsp_resp` argument using the SSL +server certificate chain in DER format as specified in the `der_cert_chain` argument. + +Returns true when the validation is successful. + +In case of failures, returns `nil` and a string +describing the failure. The maximum +length of the error string is controlled by the optional `max_err_msg` argument (which defaults +to the default internal string buffer size used throughout this `lua-resty-core` library, usually +being 4KB). + +[Back to TOC](#table-of-contents) + +set_ocsp_status_resp +-------------------- +**syntax:** `ok, err = ocsp.set_ocsp_status_resp(ocsp_resp)` + +**context:** *ssl_certificate_by_lua** + +Sets the OCSP response as the OCSP stapling for the current SSL connection. + +Returns `true` in case of successes. If the SSL client does not send a "status request" +at all, then this method still returns `true` but also with a string as the warning +`"no status req"`. + +In case of failures, returns `nil` and a string describing the error. + +The OCSP response is returned from CA's OCSP server. See the [create_ocsp_request](#create_ocsp_request) +function for how to create an OCSP request and also [validate_ocsp_response](#validate_ocsp_response) +for how to validate the OCSP response. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), CloudFlare Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015, by Yichun "agentzh" Zhang, CloudFlare Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ngx.ssl](ssl.md) module. +* the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) directive. +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* OpenResty: http://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/lua-resty-core/lib/ngx/semaphore.lua b/lib/lua-resty-core/lib/ngx/semaphore.lua new file mode 100644 index 0000000..db18a34 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/semaphore.lua @@ -0,0 +1,145 @@ +-- Copyright (C) Yichun Zhang (agentzh) +-- Copyright (C) cuiweixie +-- I hereby assign copyright in this code to the lua-resty-core project, +-- to be licensed under the same terms as the rest of the code. + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local FFI_DECLINED = base.FFI_DECLINED +local ffi_new = ffi.new +local ffi_str = ffi.string +local ffi_gc = ffi.gc +local C = ffi.C +local type = type +local error = error +local tonumber = tonumber +local getfenv = getfenv +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local setmetatable = setmetatable +local co_yield = coroutine._yield +local ERR_BUF_SIZE = 128 + + +local errmsg = base.get_errmsg_ptr() + + +ffi.cdef[[ + struct ngx_http_lua_semaphore_s; + typedef struct ngx_http_lua_semaphore_s ngx_http_lua_semaphore_t; + + int ngx_http_lua_ffi_semaphore_new(ngx_http_lua_semaphore_t **psem, + int n, char **errmsg); + + int ngx_http_lua_ffi_semaphore_post(ngx_http_lua_semaphore_t *sem, int n); + + int ngx_http_lua_ffi_semaphore_count(ngx_http_lua_semaphore_t *sem); + + int ngx_http_lua_ffi_semaphore_wait(ngx_http_request_t *r, + ngx_http_lua_semaphore_t *sem, int wait_ms, + unsigned char *errstr, size_t *errlen); + + void ngx_http_lua_ffi_semaphore_gc(ngx_http_lua_semaphore_t *sem); +]] + + +local psem = ffi_new("ngx_http_lua_semaphore_t *[1]") + + +local _M = { version = base.version } +local mt = { __index = _M } + + +function _M.new(n) + n = tonumber(n) or 0 + if n < 0 then + return error("no negative number") + end + + local ret = C.ngx_http_lua_ffi_semaphore_new(psem, n, errmsg) + if ret == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + local sem = psem[0] + + ffi_gc(sem, C.ngx_http_lua_ffi_semaphore_gc) + + return setmetatable({ sem = sem }, mt) +end + + +function _M.wait(self, seconds) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + return error("not a semaphore instance") + end + + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local milliseconds = tonumber(seconds) * 1000 + if milliseconds < 0 then + return error("no negative number") + end + + local cdata_sem = self.sem + + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + + local ret = C.ngx_http_lua_ffi_semaphore_wait(r, cdata_sem, + milliseconds, err, errlen) + + if ret == FFI_ERROR then + return nil, ffi_str(err, errlen[0]) + end + + if ret == FFI_OK then + return true + end + + if ret == FFI_DECLINED then + return nil, "timeout" + end + + return co_yield() +end + + +function _M.post(self, n) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + return error("not a semaphore instance") + end + + local cdata_sem = self.sem + + local num = n and tonumber(n) or 1 + if num < 1 then + return error("no negative number") + end + + -- always return NGX_OK + C.ngx_http_lua_ffi_semaphore_post(cdata_sem, num) + + return true +end + + +function _M.count(self) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + return error("not a semaphore instance") + end + + return C.ngx_http_lua_ffi_semaphore_count(self.sem) +end + + +return _M diff --git a/lib/lua-resty-core/lib/ngx/semaphore.md b/lib/lua-resty-core/lib/ngx/semaphore.md new file mode 100644 index 0000000..cb7f490 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/semaphore.md @@ -0,0 +1,352 @@ +Name +==== + +ngx.semaphore - light thread semaphore for OpenResty/ngx_lua. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) + * [Synchronizing threads in the same context](#synchronizing-threads-in-the-same-context) + * [Synchronizing threads in different contexts](#synchronizing-threads-in-different-contexts) +* [Description](#description) +* [Methods](#methods) + * [new](#new) + * [post](#post) + * [wait](#wait) + * [count](#count) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +Synchronizing threads in the same context +----------------------------------------- + +```nginx +location = /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sema = semaphore.new() + + local function handler() + ngx.say("sub thread: waiting on sema...") + + local ok, err = sema:wait(1) -- wait for a second at most + if not ok then + ngx.say("sub thread: failed to wait on sema: ", err) + else + ngx.say("sub thread: waited successfully.") + end + end + + local co = ngx.thread.spawn(handler) + + ngx.say("main thread: sleeping for a little while...") + + ngx.sleep(0.1) -- wait a bit + + ngx.say("main thread: posting to sema...") + + sema:post(1) + + ngx.say("main thread: end.") + } +} +``` + +The example location above produces a response output like this: + +``` +sub thread: waiting on sema... +main thread: sleeping for a little while... +main thread: posting to sema... +main thread: end. +sub thread: waited successfully. +``` + +[Back to TOC](#table-of-contents) + +Synchronizing threads in different contexts +------------------------------------------- + +```nginx +location = /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sema = semaphore.new() + + local outputs = {} + local i = 1 + + local function out(s) + outputs[i] = s + i = i + 1 + end + + local function handler() + out("timer thread: sleeping for a little while...") + + ngx.sleep(0.1) -- wait a bit + + out("timer thread: posting on sema...") + + sema:post(1) + end + + assert(ngx.timer.at(0, handler)) + + out("main thread: waiting on sema...") + + local ok, err = sema:wait(1) -- wait for a second at most + if not ok then + out("main thread: failed to wait on sema: ", err) + else + out("main thread: waited successfully.") + end + + out("main thread: end.") + + ngx.say(table.concat(outputs, "\n")) + } +} +``` + +The example location above produces a response body like this + +``` +main thread: waiting on sema... +timer thread: sleeping for a little while... +timer thread: posting on sema... +main thread: waited successfully. +main thread: end. +``` + +The same applies to different request contexts as long as these requests are served +by the same nginx worker process. + +[Back to TOC](#table-of-contents) + +Description +=========== + +This module provides an efficient semaphore API for the OpenResty/ngx_lua module. With +semaphores, "light threads" (created by [ngx.thread.spawn](https://github.com/openresty/lua-nginx-module#ngxthreadspawn), +[ngx.timer.at](https://github.com/openresty/lua-nginx-module#ngxtimerat), and etc.) can +synchronize among each other very efficiently without constant polling and sleeping. + +"Light threads" in different contexts (like in different requests) can share the same +semaphore instance as long as these "light threads" reside in the same NGINX worker +process and the [lua_code_cache](https://github.com/openresty/lua-nginx-module#lua_code_cache) +directive is turned on (which is the default). For inter-process "light thread" synchronization, +it is recommended to use the [lua-resty-lock](https://github.com/openresty/lua-resty-lock) library instead +(which is a bit less efficient than this semaphore API though). + +This semaphore API has a pure userland implementation which does not involve any system calls nor +block any operating system threads. It works closely with the event model of NGINX without +introducing any extra delay. + +Like other APIs provided by this `lua-resty-core` library, the LuaJIT FFI feature is required. + +[Back to TOC](#table-of-contents) + +Methods +======= + +[Back to TOC](#table-of-contents) + +new +--- +**syntax:** *sema, err = semaphore_module.new(n?)* + +**context:** **init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua, log_by_lua*, ngx.timer.** + +Creates and returns a new semaphore instance that has `n` (default to `0`) resources. + +For example, + +```lua + local semaphore = require "ngx.semaphore" + local sema, err = semaphore.new() + if not sema then + ngx.say("create semaphore failed: ", err) + end +``` + +Often the semaphore object created is shared on the NGINX worker process by mounting in a custom Lua module, as +documented below: + +https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker + +[Back to TOC](#table-of-contents) + +post +-------- +**syntax:** *sema:post(n?)* + +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua, log_by_lua*, ngx.timer.** + +Releases `n` (default to `1`) "resources" to the semaphore instance. + +This will not yield the current running "light thread". + +At most `n` "light threads" will be waken up when the current running "light thread" later yields (or terminates). + +```lua +-- typically, we get the semaphore instance from upvalue or globally shared data +-- See https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker + +local semaphore = require "ngx.semaphore" +local sema = semaphore.new() + +sema:post(2) -- releases 2 resources +``` + +[Back to TOC](#table-of-contents) + +wait +------------ +**syntax:** *ok, err = sema:wait(timeout)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.** + +Requests a resource from the semaphore instance. + +Returns `true` immediately when there is resources available for the current running "light thread". +Otherwise the current "light thread" will enter the waiting queue and yield execution. +The current "light thread" will be automatically waken up and the `wait` function call +will return `true` when there is resources available for it, or return `nil` and a string describing +the error in case of failure (like `"timeout"`). + +When the `timeout` argument is 0, it means "no wait", that is, when there is no readily available +"resources" for the current running "light thread", this `wait` function call returns immediately +`nil` and the error string `"timeout"`. + +"Light threads" created by different contexts (like request handlers) can wait on the +same semaphore instance without problem. + +See [Synopsis](#synopsis) for code examples. + +[Back to TOC](#table-of-contents) + +count +-------- +**syntax:** `count = sema:count()` + +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, +content_by_lua*, header_filter_by_lua*, body_filter_by_lua, log_by_lua*, ngx.timer.** + +Returns the number of resources readily available in the `sema` semaphore instance (if any). + +When the returned number is negative, it means the number of "light threads" waiting on +this semaphore. + +Consider the following example, + +```lua +local semaphore = require "ngx.semaphore" +local sema = semaphore.new(0) + +ngx.say("count: ", sema:count()) -- count: 0 + +local function handler(id) + local ok, err = sema:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end +end + +local co1 = ngx.thread.spawn(handler) +local co2 = ngx.thread.spawn(handler) + +ngx.say("count: ", sema:count()) -- count: -2 + +sema:post(1) + +ngx.say("count: ", sema:count()) -- count: -1 + +sema:post(2) + +ngx.say("count: ", sema:count()) -- count: 1 +``` + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Weixie Cui, Kugou Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015, by Yichun "agentzh" Zhang, CloudFlare Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: http://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/lua-resty-core/lib/ngx/ssl.lua b/lib/lua-resty-core/lib/ngx/ssl.lua new file mode 100644 index 0000000..6398dae --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/ssl.lua @@ -0,0 +1,232 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_str = ffi.string +local getfenv = getfenv +local error = error +local tonumber = tonumber +local errmsg = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK + + +ffi.cdef[[ + +struct ngx_ssl_conn_s; +typedef struct ngx_ssl_conn_s ngx_ssl_conn_t; + +int ngx_http_lua_ffi_ssl_set_der_certificate(ngx_http_request_t *r, + const char *data, size_t len, char **err); + +int ngx_http_lua_ffi_ssl_clear_certs(ngx_http_request_t *r, char **err); + +int ngx_http_lua_ffi_ssl_set_der_private_key(ngx_http_request_t *r, + const char *data, size_t len, char **err); + +int ngx_http_lua_ffi_ssl_raw_server_addr(ngx_http_request_t *r, char **addr, + size_t *addrlen, int *addrtype, char **err); + +int ngx_http_lua_ffi_ssl_server_name(ngx_http_request_t *r, char **name, + size_t *namelen, char **err); + +int ngx_http_lua_ffi_cert_pem_to_der(const unsigned char *pem, size_t pem_len, + unsigned char *der, char **err); + +int ngx_http_lua_ffi_priv_key_pem_to_der(const unsigned char *pem, + size_t pem_len, unsigned char *der, char **err); + +int ngx_http_lua_ffi_ssl_get_tls1_version(ngx_http_request_t *r, char **err); +]] + + +local _M = { version = base.version } + + +local charpp = ffi.new("char*[1]") +local intp = ffi.new("int[1]") + + +function _M.clear_certs() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_clear_certs(r, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_der_cert(data) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_der_certificate(r, data, #data, + errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_der_priv_key(data) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_der_private_key(r, data, #data, + errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +local addr_types = { + [0] = "unix", + [1] = "inet", + [2] = "inet6", +} + + +function _M.raw_server_addr() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local sizep = get_size_ptr() + + local rc = C.ngx_http_lua_ffi_ssl_raw_server_addr(r, charpp, sizep, + intp, errmsg) + if rc == FFI_OK then + local typ = addr_types[intp[0]] + if not typ then + return nil, nil, "unknown address type: " .. intp[0] + end + return ffi_str(charpp[0], sizep[0]), typ + end + + return nil, nil, ffi_str(errmsg[0]) +end + + +function _M.server_name() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local sizep = get_size_ptr() + + local rc = C.ngx_http_lua_ffi_ssl_server_name(r, charpp, sizep, errmsg) + if rc == FFI_OK then + return ffi_str(charpp[0], sizep[0]) + end + + if rc == FFI_DECLINED then + return nil + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.cert_pem_to_der(pem) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local outbuf = get_string_buf(#pem) + + local sz = C.ngx_http_lua_ffi_cert_pem_to_der(pem, #pem, outbuf, errmsg) + if sz > 0 then + return ffi_str(outbuf, sz) + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.priv_key_pem_to_der(pem) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local outbuf = get_string_buf(#pem) + + local sz = C.ngx_http_lua_ffi_priv_key_pem_to_der(pem, #pem, outbuf, errmsg) + if sz > 0 then + return ffi_str(outbuf, sz) + end + + return nil, ffi_str(errmsg[0]) +end + + +local function get_tls1_version() + + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local ver = C.ngx_http_lua_ffi_ssl_get_tls1_version(r, errmsg) + + ver = tonumber(ver) + + if ver >= 0 then + return ver + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errmsg[0]) +end +_M.get_tls1_version = get_tls1_version + + +do + _M.SSL3_VERSION = 0x0300 + _M.TLS1_VERSION = 0x0301 + _M.TLS1_1_VERSION = 0x0302 + _M.TLS1_2_VERSION = 0x0303 + + local map = { + [_M.SSL3_VERSION] = "SSLv3", + [_M.TLS1_VERSION] = "TLSv1", + [_M.TLS1_1_VERSION] = "TLSv1.1", + [_M.TLS1_2_VERSION] = "TLSv1.2", + } + + function _M.get_tls1_version_str() + local ver, err = get_tls1_version() + if not ver then + return nil, err + end + return map[ver] + end +end + + +return _M diff --git a/lib/lua-resty-core/lib/ngx/ssl.md b/lib/lua-resty-core/lib/ngx/ssl.md new file mode 100644 index 0000000..a7f5a29 --- /dev/null +++ b/lib/lua-resty-core/lib/ngx/ssl.md @@ -0,0 +1,374 @@ +Name +==== + +ngx.ssl - Lua API for controling NGINX downstream SSL handshakes + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [clear_certs](#clear_certs) + * [cert_pem_to_der](#cert_pem_to_der) + * [set_der_cert](#set_der_cert) + * [priv_key_pem_to_der](#priv_key_pem_to_der) + * [set_der_priv_key](#set_der_priv_key) + * [server_name](#server_name) + * [raw_server_addr](#raw_server_addr) + * [get_tls1_version](#get_tls1_version) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +# Note: you do not need the following line if you are using +# OpenResty 1.9.7.2+. +lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + +server { + listen 443 ssl; + server_name test.com; + + # useless placeholders: just to shut up NGINX configuration + # loader errors: + ssl_certificate /path/to/fallback.crt; + ssl_certificate_key /path/to/fallback.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + -- clear the fallback certificates and private keys + -- set by the ssl_certificate and ssl_certificate_key + -- directives above: + local ok, err = ssl.clear_certs() + if not ok then + ngx.log(ngx.ERR, "failed to clear existing (fallback) certificates") + return ngx.exit(ngx.ERROR) + end + + -- assuming the user already defines the my_load_certificate_chain() + -- herself. + local pem_cert_chain = assert(my_load_certificate_chain()) + + local der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain) + if not der_cert_chain then + ngx.log(ngx.ERR, "failed to convert certificate chain ", + "from PEM to DER: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, err = ssl.set_der_cert(der_cert_chain) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return ngx.exit(ngx.ERROR) + end + + -- assuming the user already defines the my_load_private_key() + -- function herself. + local der_pkey = assert(my_load_private_key()) + + local ok, err = ssl.set_der_priv_key(der_pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + return ngx.exit(ngx.ERROR) + end + } + + location / { + root html; + } +} +``` + +Description +=========== + +This Lua module provides API functions to control the SSL handshake process in contexts like +[ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +(of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module). + +For web servers serving many (like millions of) https sites, it is often desired to lazily +load and cache the SSL certificate chain and private key data for the https sites actually +being served by a particular server. This Lua module provides API to support such use cases +in the context of the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +directive. + +To load the `ngx.ssl` module in Lua, just write + +```lua +local ssl = require "ngx.ssl" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +clear_certs +----------- +**syntax:** `ok, err = ssl.clear_certs()` + +**context:** *ssl_certificate_by_lua** + +Clears any existing SSL certificates and/or private keys set on the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +[Back to TOC](#table-of-contents) + +cert_pem_to_der +--------------- +**syntax:** `der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain)` + +**context:** *any* + +Converts the PEM-formated SSL certificate chain data into the DER format (for later uses +in the [set_der_cert](#set_der_cert) +function, for example). + +In case of failures, returns `nil` and a string describing the error. + +It is known that the `openssl` command-line utility may not convert the whole SSL +certificate chain from PEM to DER correctly. So always use this Lua function to do +the conversion. You can always use libraries like [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache#readme) +and/or ngx_lua APIs like [lua_shared_dict](https://github.com/openresty/lua-nginx-module#lua_shared_dict) +to do the caching of the DER-formated results, for example. + +This function can be called in whatever contexts. + +[Back to TOC](#table-of-contents) + +set_der_cert +------------ +**syntax:** `ok, err = ssl.set_der_cert(der_cert_chain)` + +**context:** *ssl_certificate_by_lua** + +Sets the DER-formated SSL certificate chain data for the current SSL connection. Note that +the DER data is +directly in the Lua string argument. *No* external file names are supported here. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Note that, the SSL certificate chain is usually encoded in the PEM format. So you need +to use the [cert_pem_to_der](#cert_pem_to_der) +function to do the conversion first. + +[Back to TOC](#table-of-contents) + +priv_key_pem_to_der +------------------- +**syntax:** `der_priv_key, err = ssl.priv_key_pem_to_der(pem_priv_key)` + +**context:** *any* + +Converts the PEM-formated SSL private key data into the DER format (for later uses +in the [set_der_priv_key](#set_der_priv_key) +function, for example). + +In case of failures, returns `nil` and a string describing the error. + +Alternatively, you can do the PEM to DER conversion *offline* with the `openssl` command-line utility, like below + +```bash +openssl rsa -in key.pem -outform DER -out key.der +``` + +This function can be called in whatever contexts. + +[Back to TOC](#table-of-contents) + +set_der_priv_key +---------------- +**syntax:** `ok, err = ssl.set_der_priv_key(der_cert_chain)` + +**context:** *ssl_certificate_by_lua** + +Sets the DER-formated prviate key for the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Usually, the private keys are encoded in the PEM format. You can either use the +[priv_key_pem_to_der](#priv_key_pem_to_der) function +to do the PEM to DER conversion or just use +the `openssl` command-line utility offline, like below + +```bash +openssl rsa -in key.pem -outform DER -out key.der +``` + +[Back to TOC](#table-of-contents) + +server_name +----------- +**syntax:** `name, err = ssl.server_name()` + +**context:** *any* + +Returns the TLS SNI (Server Name Indication) name set by the client. Returns `nil` +when the client does not set it. + +In case of failures, it returns `nil` *and* a string describing the error. + +Usually we use this SNI name as the domain name (like `www.openresty.org`) to +identify the current web site while loading the corresponding SSL certificate +chain and private key for the site. + +Please note that not all https clients set the SNI name, so when the SNI name is +missing from the client handshake request, we use the server IP address accessed +by the client to identify the site. See the [raw_server_addr](#raw_server_addr) method +for more details. + +This function can be called in whatever contexts where downstream https is used. + +[Back to TOC](#table-of-contents) + +raw_server_addr +--------------- +**syntax:** `addr_data, addr_type, err = ssl.raw_server_addr()` + +**context:** *any* + +Returns the raw server address actually accessed by the client in the current SSL connection. + +The first two return values are strings representing the address data and the address type, respectively. +The address values are interpreted differently according to the address type values: + +* `unix` +: The address data is a file path for the UNIX domain socket. +* `inet` +: The address data is a binary IPv4 address of 4 bytes long. +* `inet6` +: The address data is a binary IPv6 address of 16 bytes long. + +Returns two `nil` values and a Lua string describing the error. + +The following code snippet shows how to print out the UNIX domain socket address and +the IPv4 address as human-readable strings: + +```lua +local ssl = require "ngx.ssl" +local byte = string.byte + +local addr, addrtyp, err = ssl.raw_server_addr() +if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return +end + +if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + +elseif addrtyp == "unix" then -- UNIX + print("Using unix socket file ", addr) + +else -- IPv6 + -- leave as an exercise for the readers +end +``` + +This function can be called in whatever contexts where downstream https is used. + +[Back to TOC](#table-of-contents) + +get_tls1_version +---------------- +**syntax:** `ver, err = ssl.get_tls1_version()` + +**context:** *any* + +Returns the TLS 1.x version number used by the current SSL connection. Returns `nil` and +a string describing the error otherwise. + +Typical return values are + +* `SSLv3` +* `TLSv1` +* `TLSv1.1` +* `TLSv1.2` + +This function can be called in whatever contexts where downstream https is used. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), CloudFlare Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015, by Yichun "agentzh" Zhang, CloudFlare Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ngx.ocsp](ocsp.md) module. +* the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) directive. +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* OpenResty: http://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/lua-resty-core/lib/resty/core.lua b/lib/lua-resty-core/lib/resty/core.lua new file mode 100644 index 0000000..71bb946 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core.lua @@ -0,0 +1,24 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +require "resty.core.uri" +require "resty.core.hash" +require "resty.core.base64" +require "resty.core.regex" +require "resty.core.exit" +require "resty.core.shdict" +require "resty.core.var" +require "resty.core.ctx" +require "resty.core.misc" +require "resty.core.request" +require "resty.core.response" +require "resty.core.time" +require "resty.core.worker" + + +local base = require "resty.core.base" + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/base.lua b/lib/lua-resty-core/lib/resty/core/base.lua new file mode 100644 index 0000000..a4da997 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/base.lua @@ -0,0 +1,187 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_new = ffi.new +local error = error +local setmetatable = setmetatable +local floor = math.floor +local ceil = math.ceil + + +local str_buf_size = 4096 +local str_buf +local size_ptr +local FREE_LIST_REF = 0 + + +if not ngx.config + or not ngx.config.ngx_lua_version + or ngx.config.ngx_lua_version < 10000 +then + error("ngx_lua 0.10.1+ required") +end + + +if string.find(jit.version, " 2.0") then + ngx.log(ngx.WARN, "use of lua-resty-core with LuaJIT 2.0 is " + .. "not recommended; use LuaJIT 2.1+ instead") +end + + +local ok, new_tab = pcall(require, "table.new") +if not ok then + new_tab = function (narr, nrec) return {} end +end + + +local ok, clear_tab = pcall(require, "table.clear") +if not ok then + clear_tab = function (tab) + for k, _ in pairs(tab) do + tab[k] = nil + end + end +end + + +-- XXX for now LuaJIT 2.1 cannot compile require() +-- so we make the fast code path Lua only in our own +-- wrapper so that most of the require() calls in hot +-- Lua code paths can be JIT compiled. +do + local orig_require = require + local pkg_loaded = package.loaded + local function my_require(name) + local mod = pkg_loaded[name] + if mod then + return mod + end + return orig_require(name) + end + getfenv(0).require = my_require +end + + +if not pcall(ffi.typeof, "ngx_str_t") then + ffi.cdef[[ + typedef struct { + size_t len; + const unsigned char *data; + } ngx_str_t; + ]] +end + + +if not pcall(ffi.typeof, "ngx_http_request_t") then + ffi.cdef[[ + struct ngx_http_request_s; + typedef struct ngx_http_request_s ngx_http_request_t; + ]] +end + + +if not pcall(ffi.typeof, "ngx_http_lua_ffi_str_t") then + ffi.cdef[[ + typedef struct { + int len; + const unsigned char *data; + } ngx_http_lua_ffi_str_t; + ]] +end + + +local c_buf_type = ffi.typeof("char[?]") + + +local _M = new_tab(0, 16) + + +_M.version = "0.1.4" +_M.new_tab = new_tab +_M.clear_tab = clear_tab + + +local errmsg + + +function _M.get_errmsg_ptr() + if not errmsg then + errmsg = ffi_new("char *[1]") + end + return errmsg +end + + +if not ngx then + return error("no existing ngx. table found") +end + + +function _M.set_string_buf_size(size) + if size <= 0 then + return + end + if str_buf then + str_buf = nil + end + str_buf_size = ceil(size) +end + + +function _M.get_string_buf_size() + return str_buf_size +end + + +function _M.get_size_ptr() + if not size_ptr then + size_ptr = ffi_new("size_t[1]") + end + + return size_ptr +end + + +function _M.get_string_buf(size, must_alloc) + -- ngx.log(ngx.ERR, "str buf size: ", str_buf_size) + if size > str_buf_size or must_alloc then + return ffi_new(c_buf_type, size) + end + + if not str_buf then + str_buf = ffi_new(c_buf_type, str_buf_size) + end + + return str_buf +end + + +function _M.ref_in_table(tb, key) + if key == nil then + return -1 + end + local ref = tb[FREE_LIST_REF] + if ref and ref ~= 0 then + tb[FREE_LIST_REF] = tb[ref] + + else + ref = #tb + 1 + end + tb[ref] = key + + -- print("ref key_id returned ", ref) + return ref +end + + +_M.FFI_OK = 0 +_M.FFI_NO_REQ_CTX = -100 +_M.FFI_BAD_CONTEXT = -101 +_M.FFI_ERROR = -1 +_M.FFI_BUSY = -3 +_M.FFI_DONE = -4 +_M.FFI_DECLINED = -5 + + +return _M diff --git a/lib/lua-resty-core/lib/resty/core/base64.lua b/lib/lua-resty-core/lib/resty/core/base64.lua new file mode 100644 index 0000000..83c0bf3 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/base64.lua @@ -0,0 +1,90 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + +local ffi_string = ffi.string +local C = ffi.C +local ngx = ngx +local type = type +local tostring = tostring +local error = error +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local floor = math.floor + + +ffi.cdef[[ + size_t ngx_http_lua_ffi_encode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + int no_padding); + + int ngx_http_lua_ffi_decode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + size_t *dlen); +]] + + +local function base64_encoded_length(len, no_padding) + return no_padding and floor((len * 8 + 5) / 6) or + floor((len + 2) / 3) * 4 +end + + +ngx.encode_base64 = function (s, no_padding) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + + local slen = #s + local no_padding_bool = false; + local no_padding_int = 0; + + if no_padding then + if no_padding ~= true then + return error("boolean argument only") + end + + no_padding_bool = true + no_padding_int = 1; + end + + local dlen = base64_encoded_length(slen, no_padding_bool) + local dst = get_string_buf(dlen) + local r_dlen = C.ngx_http_lua_ffi_encode_base64(s, slen, dst, + no_padding_int) + -- if dlen ~= r_dlen then error("discrepancy in len") end + return ffi_string(dst, r_dlen) +end + + +local function base64_decoded_length(len) + return floor((len + 3) / 4) * 3 +end + + +ngx.decode_base64 = function (s) + if type(s) ~= 'string' then + return error("string argument only") + end + local slen = #s + local dlen = base64_decoded_length(slen) + -- print("dlen: ", tonumber(dlen)) + local dst = get_string_buf(dlen) + local pdlen = get_size_ptr() + local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen) + if ok == 0 then + return nil + end + return ffi_string(dst, pdlen[0]) +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/ctx.lua b/lib/lua-resty-core/lib/resty/core/ctx.lua new file mode 100644 index 0000000..c75261f --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/ctx.lua @@ -0,0 +1,82 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local debug = require 'debug' +local base = require "resty.core.base" +local misc = require "resty.core.misc" + + +local register_getter = misc.register_ngx_magic_key_getter +local register_setter = misc.register_ngx_magic_key_setter +local registry = debug.getregistry() +local new_tab = base.new_tab +local ref_in_table = base.ref_in_table +local getfenv = getfenv +local C = ffi.C +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_OK = base.FFI_OK +local error = error + + +ffi.cdef[[ +int ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r); +int ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref); +]] + + +local _M = { + _VERSION = base.version +} + + +local function get_ctx_table() + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + local ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref(r) + if ctx_ref == FFI_NO_REQ_CTX then + return error("no request ctx found") + end + + local ctxs = registry.ngx_lua_ctx_tables + if ctx_ref < 0 then + local ctx = new_tab(0, 4) + ctx_ref = ref_in_table(ctxs, ctx) + if C.ngx_http_lua_ffi_set_ctx_ref(r, ctx_ref) ~= FFI_OK then + return nil + end + return ctx + end + return ctxs[ctx_ref] +end +register_getter("ctx", get_ctx_table) + + +local function set_ctx_table(ctx) + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + local ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref(r) + if ctx_ref == FFI_NO_REQ_CTX then + return error("no request ctx found") + end + + local ctxs = registry.ngx_lua_ctx_tables + if ctx_ref < 0 then + ctx_ref = ref_in_table(ctxs, ctx) + C.ngx_http_lua_ffi_set_ctx_ref(r, ctx_ref) + return + end + ctxs[ctx_ref] = ctx +end +register_setter("ctx", set_ctx_table) + + +return _M diff --git a/lib/lua-resty-core/lib/resty/core/exit.lua b/lib/lua-resty-core/lib/resty/core/exit.lua new file mode 100644 index 0000000..10cbc07 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/exit.lua @@ -0,0 +1,49 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_string = ffi.string +local C = ffi.C +local ngx = ngx +local error = error +local base = require "resty.core.base" +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local base = require "resty.core.base" +local getfenv = getfenv +local co_yield = coroutine._yield + + +ffi.cdef[[ + int ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, + unsigned char *err, size_t *errlen); +]] + + +local ERR_BUF_SIZE = 128 +local FFI_DONE = base.FFI_DONE + + +ngx.exit = function (rc) + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + local r = getfenv(0).__ngx_req + if r == nil then + return error("no request found") + end + errlen[0] = ERR_BUF_SIZE + local rc = C.ngx_http_lua_ffi_exit(r, rc, err, errlen) + if rc == 0 then + -- print("yielding...") + return co_yield() + end + if rc == FFI_DONE then + return + end + return error(ffi_string(err, errlen[0])) +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/hash.lua b/lib/lua-resty-core/lib/resty/core/hash.lua new file mode 100644 index 0000000..79c934a --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/hash.lua @@ -0,0 +1,80 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_string = ffi.string +local ffi_new = ffi.new +local C = ffi.C +local ngx = ngx +local type = type +local tostring = tostring +local error = error +local base = require "resty.core.base" + + +ffi.cdef[[ + void ngx_http_lua_ffi_md5_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + void ngx_http_lua_ffi_md5(const unsigned char *src, size_t len, + unsigned char *dst); + + int ngx_http_lua_ffi_sha1_bin(const unsigned char *src, size_t len, + unsigned char *dst); +]] + + +local MD5_DIGEST_LEN = 16 +local md5_buf = ffi_new("unsigned char[?]", MD5_DIGEST_LEN) + +ngx.md5_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + C.ngx_http_lua_ffi_md5_bin(s, #s, md5_buf) + return ffi_string(md5_buf, MD5_DIGEST_LEN) +end + + +local MD5_HEX_DIGEST_LEN = MD5_DIGEST_LEN * 2 +local md5_hex_buf = ffi_new("unsigned char[?]", MD5_HEX_DIGEST_LEN) + +ngx.md5 = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + C.ngx_http_lua_ffi_md5(s, #s, md5_hex_buf) + return ffi_string(md5_hex_buf, MD5_HEX_DIGEST_LEN) +end + + +local SHA_DIGEST_LEN = 20 +local sha_buf = ffi_new("unsigned char[?]", SHA_DIGEST_LEN) + +ngx.sha1_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local ok = C.ngx_http_lua_ffi_sha1_bin(s, #s, sha_buf) + if ok == 0 then + return error("SHA-1 support missing in Nginx") + end + return ffi_string(sha_buf, SHA_DIGEST_LEN) +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/misc.lua b/lib/lua-resty-core/lib/resty/core/misc.lua new file mode 100644 index 0000000..5b7a690 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/misc.lua @@ -0,0 +1,155 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +local ffi = require "ffi" + + +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local new_tab = base.new_tab +local C = ffi.C +local getmetatable = getmetatable +local ngx_magic_key_getters = new_tab(0, 4) +local ngx_magic_key_setters = new_tab(0, 2) +local ngx = ngx +local getfenv = getfenv +local type = type +local error = error +local tonumber = tonumber + + +local _M = new_tab(0, 3) +_M._VERSION = base.version + + +local function register_getter(key, func) + ngx_magic_key_getters[key] = func +end +_M.register_ngx_magic_key_getter = register_getter + + +local function register_setter(key, func) + ngx_magic_key_setters[key] = func +end +_M.register_ngx_magic_key_setter = register_setter + + +local mt = getmetatable(ngx) + + +local old_index = mt.__index +mt.__index = function (tb, key) + local f = ngx_magic_key_getters[key] + if f then + return f() + end + return old_index(tb, key) +end + + +local old_newindex = mt.__newindex +mt.__newindex = function (tb, key, ctx) + local f = ngx_magic_key_setters[key] + if f then + return f(ctx) + end + return old_newindex(tb, key, ctx) +end + + +ffi.cdef[[ + int ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r); + int ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int r); + int ngx_http_lua_ffi_is_subrequest(ngx_http_request_t *r); + int ngx_http_lua_ffi_headers_sent(ngx_http_request_t *r); +]] + + +-- ngx.status + +local function get_status() + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_get_resp_status(r) + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return rc +end +register_getter("status", get_status) + + +local function set_status(status) + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + if type(status) ~= 'number' then + status = tonumber(status) + end + + local rc = C.ngx_http_lua_ffi_set_resp_status(r, status) + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return +end +register_setter("status", set_status) + + +-- ngx.is_subrequest + +local function is_subreq() + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_is_subrequest(r) + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return rc == 1 and true or false +end +register_getter("is_subrequest", is_subreq) + + +-- ngx.headers_sent + +local function headers_sent() + local r = getfenv(0).__ngx_req + + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_headers_sent(r) + + if rc == FFI_NO_REQ_CTX then + return error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return rc == 1 +end +register_getter("headers_sent", headers_sent) + + +return _M diff --git a/lib/lua-resty-core/lib/resty/core/regex.lua b/lib/lua-resty-core/lib/resty/core/regex.lua new file mode 100644 index 0000000..763c7a3 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/regex.lua @@ -0,0 +1,856 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +local bit = require "bit" +require "resty.core.time" -- for ngx.now used by resty.lrucache +local lrucache = require "resty.lrucache" + +local lrucache_get = lrucache.get +local lrucache_set = lrucache.set +local ffi_string = ffi.string +local ffi_new = ffi.new +local ffi_gc = ffi.gc +local ffi_copy = ffi.copy +local ffi_cast = ffi.cast +local C = ffi.C +local bor = bit.bor +local band = bit.band +local lshift = bit.lshift +local sub = string.sub +local fmt = string.format +local byte = string.byte +local setmetatable = setmetatable +local concat = table.concat +local ngx = ngx +local type = type +local tostring = tostring +local error = error +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local new_tab = base.new_tab +local floor = math.floor +local print = print +local tonumber = tonumber +local ngx_log = ngx.log +local ngx_ERR = ngx.ERR + + +if not ngx.re then + ngx.re = {} +end + + +local MAX_ERR_MSG_LEN = 128 + + +local FLAG_COMPILE_ONCE = 0x01 +local FLAG_DFA = 0x02 +local FLAG_JIT = 0x04 +local FLAG_DUPNAMES = 0x08 +local FLAG_NO_UTF8_CHECK = 0x10 + + +local PCRE_CASELESS = 0x0000001 +local PCRE_MULTILINE = 0x0000002 +local PCRE_DOTALL = 0x0000004 +local PCRE_EXTENDED = 0x0000008 +local PCRE_ANCHORED = 0x0000010 +local PCRE_UTF8 = 0x0000800 +local PCRE_DUPNAMES = 0x0080000 +local PCRE_JAVASCRIPT_COMPAT = 0x2000000 + + +local PCRE_ERROR_NOMATCH = -1 + + +local regex_match_cache +local regex_sub_func_cache = new_tab(0, 4) +local regex_sub_str_cache = new_tab(0, 4) +local max_regex_cache_size +local regex_cache_size = 0 +local script_engine + + +ffi.cdef[[ + typedef struct { + ngx_str_t value; + void *lengths; + void *values; + } ngx_http_lua_complex_value_t; + + typedef struct { + void *pool; + unsigned char *name_table; + int name_count; + int name_entry_size; + + int ncaptures; + int *captures; + + void *regex; + void *regex_sd; + + ngx_http_lua_complex_value_t *replace; + + const char *pattern; + } ngx_http_lua_regex_t; + + ngx_http_lua_regex_t * + ngx_http_lua_ffi_compile_regex(const unsigned char *pat, + size_t pat_len, int flags, + int pcre_opts, unsigned char *errstr, + size_t errstr_size); + + int ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + const unsigned char *s, size_t len, int pos); + + void ngx_http_lua_ffi_destroy_regex(ngx_http_lua_regex_t *re); + + int ngx_http_lua_ffi_compile_replace_template(ngx_http_lua_regex_t *re, + const unsigned char + *replace_data, + size_t replace_len); + + struct ngx_http_lua_script_engine_s; + typedef struct ngx_http_lua_script_engine_s *ngx_http_lua_script_engine_t; + + ngx_http_lua_script_engine_t *ngx_http_lua_ffi_create_script_engine(void); + + void ngx_http_lua_ffi_init_script_engine(ngx_http_lua_script_engine_t *e, + const unsigned char *subj, + ngx_http_lua_regex_t *compiled, + int count); + + void ngx_http_lua_ffi_destroy_script_engine( + ngx_http_lua_script_engine_t *e); + + size_t ngx_http_lua_ffi_script_eval_len(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv); + + size_t ngx_http_lua_ffi_script_eval_data(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv, + unsigned char *dst); + + uint32_t ngx_http_lua_ffi_max_regex_cache_size(void); +]] + + +local c_str_type = ffi.typeof("const char *") + +local cached_re_opts = new_tab(0, 4) + +local _M = { + version = base.version +} + + +local buf_grow_ratio = 2 + +function _M.set_buf_grow_ratio(ratio) + buf_grow_ratio = ratio +end + + +local function get_max_regex_cache_size() + if max_regex_cache_size then + return max_regex_cache_size + end + max_regex_cache_size = C.ngx_http_lua_ffi_max_regex_cache_size() + return max_regex_cache_size +end + + +local function parse_regex_opts(opts) + local t = cached_re_opts[opts] + if t then + return t[1], t[2] + end + + local flags = 0 + local pcre_opts = 0 + local len = #opts + + for i = 1, len do + local opt = byte(opts, i) + if opt == byte("o") then + flags = bor(flags, FLAG_COMPILE_ONCE) + + elseif opt == byte("j") then + flags = bor(flags, FLAG_JIT) + + elseif opt == byte("i") then + pcre_opts = bor(pcre_opts, PCRE_CASELESS) + + elseif opt == byte("s") then + pcre_opts = bor(pcre_opts, PCRE_DOTALL) + + elseif opt == byte("m") then + pcre_opts = bor(pcre_opts, PCRE_MULTILINE) + + elseif opt == byte("u") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + + elseif opt == byte("U") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + flags = bor(flags, FLAG_NO_UTF8_CHECK) + + elseif opt == byte("x") then + pcre_opts = bor(pcre_opts, PCRE_EXTENDED) + + elseif opt == byte("d") then + flags = bor(flags, FLAG_DFA) + + elseif opt == byte("a") then + pcre_opts = bor(pcre_opts, PCRE_ANCHORED) + + elseif opt == byte("D") then + pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) + flags = bor(flags, FLAG_DUPNAMES) + + elseif opt == byte("J") then + pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) + + else + return error(fmt('unknown flag "%s" (flags "%s")', + sub(opts, i, i), opts)) + end + end + + cached_re_opts[opts] = {flags, pcre_opts} + return flags, pcre_opts +end + + +local function collect_named_captures(compiled, flags, res) + local name_count = compiled.name_count + local name_table = compiled.name_table + local entry_size = compiled.name_entry_size + + local ind = 0 + local dup_names = (band(flags, FLAG_DUPNAMES) ~= 0) + for i = 1, name_count do + local n = bor(lshift(name_table[ind], 8), name_table[ind + 1]) + -- ngx.say("n = ", n) + local name = ffi_string(name_table + ind + 2) + local cap = res[n] + if dup_names then + -- unmatched captures (false) are not collected + if cap then + local old = res[name] + if old then + old[#old + 1] = cap + else + res[name] = {cap} + end + end + else + res[name] = cap + end + + ind = ind + entry_size + end +end + + +local function collect_captures(compiled, rc, subj, flags, res) + local cap = compiled.captures + local ncap = compiled.ncaptures + local name_count = compiled.name_count + + if not res then + res = new_tab(ncap, name_count) + end + + local i = 0 + local n = 0 + while i <= ncap do + if i > rc then + res[i] = false + else + local from = cap[n] + if from >= 0 then + local to = cap[n + 1] + res[i] = sub(subj, from + 1, to) + else + res[i] = false + end + end + i = i + 1 + n = n + 2 + end + + if name_count > 0 then + collect_named_captures(compiled, flags, res) + end + + return res +end + + +local function destroy_compiled_regex(compiled) + C.ngx_http_lua_ffi_destroy_regex(ffi_gc(compiled, nil)) +end + + +local function re_match_compile(regex, opts) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled, key + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + + -- FIXME: better put this in the outer scope when fixing the ngx.re API's + -- compatibility in the init_by_lua* context. + if not regex_match_cache then + local sz = get_max_regex_cache_size() + if sz <= 0 then + compile_once = false + else + regex_match_cache = lrucache.new(sz) + end + end + + if compile_once then + key = regex .. '\0' .. opts + compiled = lrucache_get(regex_match_cache, key) + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = C.ngx_http_lua_ffi_compile_regex(regex, #regex, + flags, pcre_opts, + errbuf, MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, C.ngx_http_lua_ffi_destroy_regex) + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + -- print("inserting compiled regex into cache") + lrucache_set(regex_match_cache, key, compiled) + end + end + + return compiled, compile_once, flags +end + + +local function re_match_helper(subj, regex, opts, ctx, want_caps, res, nth) + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + if not want_caps then + return nil, nil, compile_once + end + return nil, compile_once + end + + -- exec the compiled regex + + local rc + do + local pos + if ctx then + pos = ctx.pos + if not pos or pos <= 0 then + pos = 0 + else + pos = pos - 1 + end + + else + pos = 0 + end + + rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) + end + + if rc == PCRE_ERROR_NOMATCH then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + if not want_caps then + return nil, nil, "pcre_exec() failed: " .. rc + end + return nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not want_caps then + return nil, nil, "capture size too small" + end + return nil, "capture size too small" + end + + rc = 1 + end + + -- print("cap 0: ", compiled.captures[0]) + -- print("cap 1: ", compiled.captures[1]) + + if ctx then + ctx.pos = compiled.captures[1] + 1 + end + + if not want_caps then + if not nth or nth < 0 then + nth = 0 + end + + if nth > compiled.ncaptures then + return nil, nil, "nth out of bound" + end + + if nth >= rc then + return nil, nil + end + + local from = compiled.captures[nth * 2] + 1 + local to = compiled.captures[nth * 2 + 1] + + if from < 0 or to < 0 then + return nil, nil + end + + return from, to + end + + res = collect_captures(compiled, rc, subj, flags, res) + + if not compile_once then + destroy_compiled_regex(compiled) + end + + return res +end + + +function ngx.re.match(subj, regex, opts, ctx, res) + return re_match_helper(subj, regex, opts, ctx, true, res) +end + + +function ngx.re.find(subj, regex, opts, ctx, nth) + return re_match_helper(subj, regex, opts, ctx, false, nil, nth) +end + + +local function new_script_engine(subj, compiled, count) + if not script_engine then + script_engine = C.ngx_http_lua_ffi_create_script_engine() + if script_engine == nil then + return nil + end + ffi_gc(script_engine, C.ngx_http_lua_ffi_destroy_script_engine) + end + + C.ngx_http_lua_ffi_init_script_engine(script_engine, subj, compiled, + count) + return script_engine +end + + +local function check_buf_size(buf, buf_size, pos, len, new_len, must_alloc) + if new_len > buf_size then + buf_size = buf_size * buf_grow_ratio + if buf_size < new_len then + buf_size = new_len + end + local new_buf = get_string_buf(buf_size, must_alloc) + ffi_copy(new_buf, buf, len) + buf = new_buf + pos = buf + len + end + return buf, buf_size, pos, new_len +end + + +local function re_sub_compile(regex, opts, replace, func) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + if compile_once then + if func then + local subcache = regex_sub_func_cache[opts] + if subcache then + -- print("cache hit!") + compiled = subcache[regex] + end + + else + local subcache = regex_sub_str_cache[opts] + if subcache then + local subsubcache = subcache[regex] + if subsubcache then + -- print("cache hit!") + compiled = subsubcache[replace] + end + end + end + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = C.ngx_http_lua_ffi_compile_regex(regex, #regex, flags, + pcre_opts, errbuf, + MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, C.ngx_http_lua_ffi_destroy_regex) + + if func == nil then + local rc = + C.ngx_http_lua_ffi_compile_replace_template(compiled, + replace, #replace) + if rc ~= 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, "failed to compile the replacement template" + end + end + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + if regex_cache_size < get_max_regex_cache_size() then + -- print("inserting compiled regex into cache") + if func then + local subcache = regex_sub_func_cache[opts] + if not subcache then + regex_sub_func_cache[opts] = {[regex] = compiled} + + else + subcache[regex] = compiled + end + + else + local subcache = regex_sub_str_cache[opts] + if not subcache then + regex_sub_str_cache[opts] = + {[regex] = {[replace] = compiled}} + + else + local subsubcache = subcache[regex] + if not subsubcache then + subcache[regex] = {[replace] = compiled} + + else + subsubcache[replace] = compiled + end + end + end + + regex_cache_size = regex_cache_size + 1 + else + compile_once = false + end + end + end + + return compiled, compile_once, flags +end + + +local function re_sub_func_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, nil, replace) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + -- Note: we have to always allocate the string buffer because + -- the user might call whatever resty.core's API functions recursively + -- in the user callback function. + local dst_buf = get_string_buf(dst_buf_size, true) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, + subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local res = collect_captures(compiled, rc, subj, flags) + + local bit = replace(res) + local bit_len = #bit + + local new_dst_len = dst_len + prefix_len + bit_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ffi_copy(dst_pos, bit, bit_len) + dst_pos = dst_pos + bit_len + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_str_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, replace, nil) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + local dst_buf = get_string_buf(dst_buf_size) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, + subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local cv = compiled.replace + if cv.lengths ~= nil then + local e = new_script_engine(subj, compiled, rc) + if e == nil then + return nil, nil, "failed to create script engine" + end + + local bit_len = C.ngx_http_lua_ffi_script_eval_len(e, cv) + local new_dst_len = dst_len + prefix_len + bit_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + C.ngx_http_lua_ffi_script_eval_data(e, cv, dst_pos) + dst_pos = dst_pos + bit_len + end + + else + local bit_len = cv.value.len + + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + dst_len + prefix_len + bit_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ffi_copy(dst_pos, cv.value.data, bit_len) + dst_pos = dst_pos + bit_len + end + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_helper(subj, regex, replace, opts, global) + local repl_type = type(replace) + if repl_type == "function" then + return re_sub_func_helper(subj, regex, replace, opts, global) + end + + if repl_type ~= "string" then + replace = tostring(replace) + end + + return re_sub_str_helper(subj, regex, replace, opts, global) +end + + +function ngx.re.sub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, false) +end + + +function ngx.re.gsub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, true) +end + + +return _M diff --git a/lib/lua-resty-core/lib/resty/core/request.lua b/lib/lua-resty-core/lib/resty/core/request.lua new file mode 100644 index 0000000..c737366 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/request.lua @@ -0,0 +1,351 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local new_tab = base.new_tab +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_str = ffi.string +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local setmetatable = setmetatable +local gsub = ngx.re.gsub +local lower = string.lower +local rawget = rawget +local ngx = ngx +local getfenv = getfenv +local type = type +local error = error +local tostring = tostring +local tonumber = tonumber + + +ffi.cdef[[ + typedef struct { + ngx_http_lua_ffi_str_t key; + ngx_http_lua_ffi_str_t value; + } ngx_http_lua_ffi_table_elt_t; + + int ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, + int max); + + int ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, + ngx_http_lua_ffi_table_elt_t *out, int count, int raw); + + int ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, + int max); + + size_t ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, + unsigned char *buf, ngx_http_lua_ffi_table_elt_t *out, int count); + + double ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_method(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_method_name(ngx_http_request_t *r, + char *name, size_t *len); + + int ngx_http_lua_ffi_req_set_method(ngx_http_request_t *r, int method); + + int ngx_http_lua_ffi_req_header_set_single_value(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, const unsigned char *value, + size_t value_len); +]] + + +local table_elt_type = ffi.typeof("ngx_http_lua_ffi_table_elt_t*") +local table_elt_size = ffi.sizeof("ngx_http_lua_ffi_table_elt_t") +local req_headers_mt = { + __index = function (tb, key) + return rawget(tb, (gsub(lower(key), '_', '-', "jo"))) + end +} + + +function ngx.req.get_headers(max_headers, raw) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if not max_headers then + max_headers = -1 + end + + if not raw then + raw = 0 + else + raw = 1 + end + + local n = C.ngx_http_lua_ffi_req_get_headers_count(r, max_headers) + if n == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + if n == 0 then + return {} + end + + local raw_buf = get_string_buf(n * table_elt_size) + local buf = ffi_cast(table_elt_type, raw_buf) + + local rc = C.ngx_http_lua_ffi_req_get_headers(r, buf, n, raw) + if rc == 0 then + local headers = new_tab(0, n) + for i = 0, n - 1 do + local h = buf[i] + + local key = h.key + key = ffi_str(key.data, key.len) + + local value = h.value + value = ffi_str(value.data, value.len) + + local existing = headers[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + headers[key] = {existing, value} + end + + else + headers[key] = value + end + end + if raw == 0 then + return setmetatable(headers, req_headers_mt) + end + return headers + end + + return nil +end + + +function ngx.req.get_uri_args(max_args) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if not max_args then + max_args = -1 + end + + local n = C.ngx_http_lua_ffi_req_get_uri_args_count(r, max_args) + if n == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + if n == 0 then + return {} + end + + local args_len = C.ngx_http_lua_ffi_req_get_querystring_len(r) + + local strbuf = get_string_buf(args_len + n * table_elt_size) + local kvbuf = ffi_cast(table_elt_type, strbuf + args_len) + + local nargs = C.ngx_http_lua_ffi_req_get_uri_args(r, strbuf, kvbuf, n) + + local args = new_tab(0, nargs) + for i = 0, nargs - 1 do + local arg = kvbuf[i] + + local key = arg.key + key = ffi_str(key.data, key.len) + + local value = arg.value + local len = value.len + if len == -1 then + value = true + else + value = ffi_str(value.data, len) + end + + local existing = args[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + args[key] = {existing, value} + end + + else + args[key] = value + end + end + return args +end + + +function ngx.req.start_time() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + return tonumber(C.ngx_http_lua_ffi_req_start_time(r)) +end + + +do + local methods = { + [0x0002] = "GET", + [0x0004] = "HEAD", + [0x0008] = "POST", + [0x0010] = "PUT", + [0x0020] = "DELETE", + [0x0040] = "MKCOL", + [0x0080] = "COPY", + [0x0100] = "MOVE", + [0x0200] = "OPTIONS", + [0x0400] = "PROPFIND", + [0x0800] = "PROPPATCH", + [0x1000] = "LOCK", + [0x2000] = "UNLOCK", + [0x4000] = "PATCH", + [0x8000] = "TRACE", + } + + function ngx.req.get_method() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + do + local id = C.ngx_http_lua_ffi_req_get_method(r) + if id == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + local method = methods[id] + if method then + return method + end + end + + local buf = get_string_buf(32) + local sizep = get_size_ptr() + sizep[0] = 32 + + local rc = C.ngx_http_lua_ffi_req_get_method_name(r, buf, sizep) + if rc ~= 0 then + return nil + end + + return ffi_str(buf, sizep[0]) + end +end -- do + + +function ngx.req.set_method(method) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(method) ~= "number" then + return error("bad method number") + end + + local rc = C.ngx_http_lua_ffi_req_set_method(r, method) + if rc == FFI_OK then + return + end + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + if rc == FFI_DECLINED then + return error("unsupported HTTP method: " .. method) + end + + return error("unknown error: " .. rc) +end + + +do + local orig_func = ngx.req.set_header + + function ngx.req.set_header(name, value) + if type(value) == "table" then + return orig_func(name, value) + end + + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc + if not value then + rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, + #name, nil, 0) + + else + if type(value) ~= "string" then + value = tostring(value) + end + + rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, + #name, value, #value) + end + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return error("error") + end +end -- do + + +function ngx.req.clear_header(name) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, #name, + nil, 0) + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + return error("error") +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/response.lua b/lib/lua-resty-core/lib/resty/core/response.lua new file mode 100644 index 0000000..2944d75 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/response.lua @@ -0,0 +1,165 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_str = ffi.string +local new_tab = base.new_tab +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_DECLINED = base.FFI_DECLINED +local get_string_buf = base.get_string_buf +local getmetatable = getmetatable +local type = type +local tostring = tostring +local getfenv = getfenv +local error = error +local ngx = ngx + + +local MAX_HEADER_VALUES = 100 +local errmsg = base.get_errmsg_ptr() +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") + + +ffi.cdef[[ + int ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, + const char *key_data, size_t key_len, int is_nil, + const char *sval, size_t sval_len, ngx_http_lua_ffi_str_t *mvals, + size_t mvals_len, char **errmsg); + + int ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, + unsigned char *key_buf, ngx_http_lua_ffi_str_t *values, + int max_nvalues); +]] + + +local function set_resp_header(tb, key, value) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local rc + if value == nil then + rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, true, nil, 0, + nil, 0, errmsg) + else + local sval, sval_len, mvals, mvals_len, buf + + if type(value) == "table" then + mvals_len = #value + buf = get_string_buf(ffi_str_size * mvals_len) + mvals = ffi_cast(ffi_str_type, buf) + for i = 1, mvals_len do + local s = value[i] + if type(s) ~= "string" then + s = tostring(s) + value[i] = s + end + local str = mvals[i - 1] + str.data = s + str.len = #s + end + + sval_len = 0 + + else + if type(value) ~= "string" then + sval = tostring(value) + else + sval = value + end + sval_len = #sval + + mvals_len = 0 + end + + rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, false, sval, + sval_len, mvals, mvals_len, + errmsg) + end + + if rc == 0 or rc == FFI_DECLINED then + return + end + + if rc == FFI_NO_REQ_CTX then + return error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + -- rc == FFI_ERROR + return error(ffi_str(errmsg[0])) +end + + +local function get_resp_header(tb, key) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + + local key_buf = get_string_buf(key_len + ffi_str_size * MAX_HEADER_VALUES) + local values = ffi_cast(ffi_str_type, key_buf + key_len) + local n = C.ngx_http_lua_ffi_get_resp_header(r, key, key_len, key_buf, + values, MAX_HEADER_VALUES) + + -- print("retval: ", n) + + if n == FFI_BAD_CONTEXT then + return error("API disabled in the current context") + end + + if n == 0 then + return nil + end + + if n == 1 then + local v = values[0] + return ffi_str(v.data, v.len) + end + + if n > 0 then + local ret = new_tab(n, 0) + for i = 1, n do + local v = values[i - 1] + ret[i] = ffi_str(v.data, v.len) + end + return ret + end + + -- n == FFI_ERROR + return error("no memory") +end + + +do + local mt = getmetatable(ngx.header) + mt.__newindex = set_resp_header + mt.__index = get_resp_header +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/shdict.lua b/lib/lua-resty-core/lib/resty/core/shdict.lua new file mode 100644 index 0000000..5ae98ff --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/shdict.lua @@ -0,0 +1,382 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local tonumber = tonumber +local tostring = tostring +local next = next +local type = type +local error = error +local ngx_shared = ngx.shared +local getmetatable = getmetatable + + +ffi.cdef[[ + int ngx_http_lua_ffi_shdict_get(void *zone, const unsigned char *key, + size_t key_len, int *value_type, unsigned char **str_value_buf, + size_t *str_value_len, double *num_value, int *user_flags, + int get_stale, int *is_stale); + + int ngx_http_lua_ffi_shdict_incr(void *zone, const unsigned char *key, + size_t key_len, double *value, char **err); + + int ngx_http_lua_ffi_shdict_store(void *zone, int op, + const unsigned char *key, size_t key_len, int value_type, + const unsigned char *str_value_buf, size_t str_value_len, + double num_value, int exptime, int user_flags, char **errmsg, + int *forcible); + + int ngx_http_lua_ffi_shdict_flush_all(void *zone); +]] + + +if not pcall(function () return C.free end) then + ffi.cdef[[ + void free(void *ptr); + ]] +end + + +local value_type = ffi_new("int[1]") +local user_flags = ffi_new("int[1]") +local num_value = ffi_new("double[1]") +local is_stale = ffi_new("int[1]") +local forcible = ffi_new("int[1]") +local str_value_buf = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function check_zone(zone) + if not zone or type(zone) ~= "table" then + return error("bad \"zone\" argument") + end + + zone = zone[1] + if type(zone) ~= "userdata" then + return error("bad \"zone\" argument") + end + + return zone +end + + +local function shdict_store(zone, op, key, value, exptime, flags) + zone = check_zone(zone) + + if not exptime then + exptime = 0 + elseif exptime < 0 then + return error('bad "exptime" argument') + end + + if not flags then + flags = 0 + end + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local str_value_buf + local str_value_len = 0 + local num_value = 0 + local valtyp = type(value) + + -- print("value type: ", valtyp) + -- print("exptime: ", exptime) + + if valtyp == "string" then + valtyp = 4 -- LUA_TSTRING + str_value_buf = value + str_value_len = #value + + elseif valtyp == "number" then + valtyp = 3 -- LUA_TNUMBER + num_value = value + + elseif value == nil then + valtyp = 0 -- LUA_TNIL + + elseif valtyp == "boolean" then + valtyp = 1 -- LUA_TBOOLEAN + num_value = value and 1 or 0 + + else + return nil, "bad value type" + end + + local rc = C.ngx_http_lua_ffi_shdict_store(zone, op, key, key_len, + valtyp, str_value_buf, + str_value_len, num_value, + exptime * 1000, flags, errmsg, + forcible) + + -- print("rc == ", rc) + + if rc == 0 then -- NGX_OK + return true, nil, forcible[0] == 1 + end + + -- NGX_DECLINED or NGX_ERROR + return false, ffi_str(errmsg[0]), forcible[0] == 1 +end + + +local function shdict_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0, key, value, exptime, flags) +end + + +local function shdict_safe_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0004, key, value, exptime, flags) +end + + +local function shdict_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0001, key, value, exptime, flags) +end + + +local function shdict_safe_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0005, key, value, exptime, flags) +end + + +local function shdict_replace(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0002, key, value, exptime, flags) +end + + +local function shdict_delete(zone, key) + return shdict_set(zone, key, nil) +end + + +local function shdict_get(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = C.ngx_http_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 0, + is_stale) + if rc ~= 0 then + return error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + return error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags + end + + return val +end + + +local function shdict_get_stale(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = C.ngx_http_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 1, + is_stale) + if rc ~= 0 then + return error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + return error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags, is_stale[0] == 1 + end + + return val, nil, is_stale[0] == 1 +end + + +local function shdict_incr(zone, key, value) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + if type(value) ~= "number" then + value = tonumber(value) + end + num_value[0] = value + + local rc = C.ngx_http_lua_ffi_shdict_incr(zone, key, key_len, num_value, + errmsg) + if rc ~= 0 then -- ~= NGX_OK + return nil, ffi_str(errmsg[0]) + end + + return tonumber(num_value[0]) +end + + +local function shdict_flush_all(zone) + zone = check_zone(zone) + + C.ngx_http_lua_ffi_shdict_flush_all(zone) +end + + +if ngx_shared then + local name, dict = next(ngx_shared, nil) + if dict then + local mt = getmetatable(dict) + if mt then + mt = mt.__index + if mt then + mt.get = shdict_get + mt.get_stale = shdict_get_stale + mt.incr = shdict_incr + mt.set = shdict_set + mt.safe_set = shdict_safe_set + mt.add = shdict_add + mt.safe_add = shdict_safe_add + mt.replace = shdict_replace + mt.delete = shdict_delete + mt.flush_all = shdict_flush_all + end + end + end +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/time.lua b/lib/lua-resty-core/lib/resty/core/time.lua new file mode 100644 index 0000000..6e45512 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/time.lua @@ -0,0 +1,31 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local tonumber = tonumber +local C = ffi.C +local ngx = ngx + + +ffi.cdef[[ +double ngx_http_lua_ffi_now(void); +long ngx_http_lua_ffi_time(void); +]] + + +function ngx.now() + return tonumber(C.ngx_http_lua_ffi_now()) +end + + +function ngx.time() + return tonumber(C.ngx_http_lua_ffi_time()) +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/uri.lua b/lib/lua-resty-core/lib/resty/core/uri.lua new file mode 100644 index 0000000..af0e655 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/uri.lua @@ -0,0 +1,64 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_string = ffi.string +local C = ffi.C +local ngx = ngx +local type = type +local tostring = tostring +local base = require "resty.core.base" +local get_string_buf = base.get_string_buf + + +ffi.cdef[[ + size_t ngx_http_lua_ffi_uri_escaped_length(const unsigned char *src, + size_t len); + + void ngx_http_lua_ffi_escape_uri(const unsigned char *src, size_t len, + unsigned char *dst); + + size_t ngx_http_lua_ffi_unescape_uri(const unsigned char *src, + size_t len, unsigned char *dst); +]] + + +ngx.escape_uri = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local slen = #s + local dlen = C.ngx_http_lua_ffi_uri_escaped_length(s, slen) + -- print("dlen: ", tonumber(dlen)) + if dlen == slen then + return s + end + local dst = get_string_buf(dlen) + C.ngx_http_lua_ffi_escape_uri(s, slen, dst) + return ffi_string(dst, dlen) +end + + +ngx.unescape_uri = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local slen = #s + local dlen = slen + local dst = get_string_buf(dlen) + dlen = C.ngx_http_lua_ffi_unescape_uri(s, slen, dst) + return ffi_string(dst, dlen) +end + + +return { + version = base.version, +} diff --git a/lib/lua-resty-core/lib/resty/core/var.lua b/lib/lua-resty-core/lib/resty/core/var.lua new file mode 100644 index 0000000..3fda66a --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/var.lua @@ -0,0 +1,127 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C +local type = type +local getfenv = getfenv +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local error = error +local tostring = tostring +local ngx_var = ngx.var +local getmetatable = getmetatable + + +ffi.cdef[[ + int ngx_http_lua_ffi_var_get(ngx_http_request_t *r, + const char *name_data, size_t name_len, char *lowcase_buf, + int capture_id, char **value, size_t *value_len, char **err); + + int ngx_http_lua_ffi_var_set(ngx_http_request_t *r, + const unsigned char *name_data, size_t name_len, + unsigned char *lowcase_buf, const unsigned char *value, + size_t value_len, unsigned char *errbuf, size_t errlen); +]] + + +local value_ptr = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function var_get(self, name) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local value_len = get_size_ptr() + local rc + if type(name) == "number" then + rc = C.ngx_http_lua_ffi_var_get(r, nil, 0, nil, name, value_ptr, + value_len, errmsg) + + else + if type(name) ~= "string" then + return error("bad variable name") + end + + local name_len = #name + local lowcase_buf = get_string_buf(name_len) + + rc = C.ngx_http_lua_ffi_var_get(r, name, name_len, lowcase_buf, 0, + value_ptr, value_len, errmsg) + end + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return ffi_str(value_ptr[0], value_len[0]) + end + + if rc == -5 then -- NGX_DECLINED + return nil + end + + if rc == -1 then -- NGX_ERROR + return error(ffi_str(errmsg[0])) + end +end + + +local function var_set(self, name, value) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + if type(name) ~= "string" then + return error("bad variable name") + end + local name_len = #name + + local errlen = 256 + local lowcase_buf = get_string_buf(name_len + errlen) + + local value_len + if value == nil then + value_len = 0 + else + if type(value) ~= 'string' then + value = tostring(value) + end + value_len = #value + end + + local errbuf = lowcase_buf + name_len + local rc = C.ngx_http_lua_ffi_var_set(r, name, name_len, lowcase_buf, + value, value_len, errbuf, errlen) + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return + end + + if rc == -1 then -- NGX_ERROR + return error(ffi_str(errbuf, errlen)) + end +end + + +if ngx_var then + local mt = getmetatable(ngx_var) + if mt then + mt.__index = var_get + mt.__newindex = var_set + end +end + + +return { + version = base.version +} diff --git a/lib/lua-resty-core/lib/resty/core/worker.lua b/lib/lua-resty-core/lib/resty/core/worker.lua new file mode 100644 index 0000000..2c0fac6 --- /dev/null +++ b/lib/lua-resty-core/lib/resty/core/worker.lua @@ -0,0 +1,29 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local C = ffi.C + + +ffi.cdef[[ +int ngx_http_lua_ffi_worker_pid(void); +int ngx_http_lua_ffi_worker_exiting(void); +]] + + +function ngx.worker.exiting() + return C.ngx_http_lua_ffi_worker_exiting() ~= 0 and true or false +end + + +function ngx.worker.pid() + return C.ngx_http_lua_ffi_worker_pid() +end + + +return { + _VERSION = base.version +} diff --git a/lib/lua-resty-core/t/balancer.t b/lib/lua-resty-core/t/balancer.t new file mode 100644 index 0000000..c527dfe --- /dev/null +++ b/lib/lua-resty-core/t/balancer.t @@ -0,0 +1,652 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 4); + +$ENV{TEST_NGINX_CWD} = cwd(); + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: set current peer (separate addr and port) +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[warn] + + + +=== TEST 2: set current peer & next upstream (3 tries) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){3}$# +--- no_error_log +[warn] + + + +=== TEST 3: set current peer & next upstream (no retries) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){1}$# +--- no_error_log +[warn] + + + +=== TEST 4: set current peer & next upstream (3 tries exceeding the limit) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 2; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){2}$# +--- error_log +set more tries: reduced tries due to limit + + + +=== TEST 5: get last peer failure status (404) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 404; + } +--- request + GET /t +--- response_body_like: 404 Not Found +--- error_code: 404 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: next 404 +last peer failure: next 404 + +--- no_error_log +[warn] + + + +=== TEST 6: get last peer failure status (500) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 500; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 500 +last peer failure: failed 500 + +--- no_error_log +[warn] + + + +=== TEST 7: get last peer failure status (503) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 503; + } +--- request + GET /t +--- response_body_like: 503 Service Temporarily Unavailable +--- error_code: 503 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 502 +last peer failure: failed 502 + +--- no_error_log +[warn] + + + +=== TEST 8: get last peer failure status (connect failed) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 404; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 502 +last peer failure: failed 502 + +--- no_error_log +[warn] + + + +=== TEST 9: set current peer (port embedded in addr) +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3:12345")) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[warn] + + + +=== TEST 10: keepalive before balancer +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 0.0.0.1; + keepalive 10; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3:12345")) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- grep_error_log eval: qr/load balancing method redefined in/ +--- grep_error_log_out eval +[ +"load balancing method redefined in +", +"", +] +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[crit] + + + +=== TEST 11: keepalive after balancer +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + keepalive 1; + } +--- config + location = /t { + content_by_lua_block { + local res0 = ngx.location.capture("/tt") + local res1 = ngx.location.capture("/tt") + local res2 = ngx.location.capture("/tt") + + if res2.status == ngx.HTTP_OK then + ngx.print(res2.body) + end + } + } + + location = /tt { + proxy_pass http://backend/back; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location = /back { + echo "hello keepalive!"; + } +--- request + GET /t +--- response_body +hello keepalive! +--- error_code: 200 +--- grep_error_log eval: qr{\S+ keepalive peer:.*?connection} +--- grep_error_log_out eval +["free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +", +"get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +", +] +--- no_error_log +[warn] + + + +=== TEST 12: set_current_peer called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_current_peer("127.0.0.1", 1234) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] + + + +=== TEST 13: get_last_failure called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local state, status, err = balancer.get_last_failure() + if not state and err then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] + + + +=== TEST 14: set_more_tries called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_CWD/lib/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_more_tries(1) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] diff --git a/lib/lua-resty-core/t/cert/chain/chain.der b/lib/lua-resty-core/t/cert/chain/chain.der new file mode 100644 index 0000000..ee6d9ba Binary files /dev/null and b/lib/lua-resty-core/t/cert/chain/chain.der differ diff --git a/lib/lua-resty-core/t/cert/chain/chain.pem b/lib/lua-resty-core/t/cert/chain/chain.pem new file mode 100644 index 0000000..21b704f --- /dev/null +++ b/lib/lua-resty-core/t/cert/chain/chain.pem @@ -0,0 +1,172 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4100 (0x1004) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=Signing-CA-2 + Validity + Not Before: Sep 20 05:27:46 2014 GMT + Not After : Aug 27 05:27:46 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d8:53:ac:ac:5c:3f:f9:80:a8:96:4b:b0:58:db: + 5a:86:de:ca:30:02:d9:19:c8:f6:14:c5:40:c9:41: + eb:bb:7a:d1:e1:f9:96:3b:54:d5:e8:bf:ac:50:5a: + 49:11:da:99:60:44:e0:25:68:40:36:7c:f6:ce:b4: + 9c:b9:58:d6:ea:e7:44:98:63:eb:a2:72:f8:e9:69: + b4:4a:4d:68:86:41:ca:67:58:61:e6:70:e8:08:fe: + ad:c2:75:59:24:0e:f0:2f:1a:70:83:8c:a3:77:64: + e8:4d:d5:c5:28:62:a9:53:d1:a1:22:f5:36:43:a7: + 46:00:aa:97:54:72:d4:72:47 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 1F:DB:C0:D9:3C:4B:77:A8:9A:AC:33:1F:7B:70:C4:CF:BA:C8:07:DD + X509v3 Authority Key Identifier: + keyid:39:77:77:A3:4E:92:8B:E2:25:20:72:64:35:0A:7A:87:A8:58:A9:F8 + + Signature Algorithm: sha1WithRSAEncryption + 1e:cd:83:66:b1:db:ea:5c:37:7e:bc:31:44:52:72:03:ae:9b: + 44:20:2c:ad:00:20:a5:dc:cf:9d:c8:c8:8f:df:cf:24:26:9c: + 43:83:f4:d2:ff:eb:d9:e4:7d:25:cf:1f:b8:aa:63:58:03:b9: + da:52:42:f8:fe:2e:71:cc:8f:de:26:34:cd:da:5c:7a:3b:64: + 07:18:27:a1:61:b6:58:32:96:10:97:f2:7f:00:c4:44:43:b7: + 9d:e2:31:69:4f:c2:95:c5:a3:32:d1:c0:00:c6:ef:58:b9:0f: + e6:08:3a:0d:c9:c0:14:f7:26:8c:43:13:55:1b:93:71:72:c7: + ad:2f +-----BEGIN CERTIFICATE----- +MIICijCCAfOgAwIBAgICEAQwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0yMCAXDTE0MDkyMDA1Mjc0NloYDzIxMTQwODI3MDUyNzQ2 +WjBJMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA2FOsrFw/+YColkuwWNtaht7KMALZGcj2FMVAyUHru3rR4fmWO1TV +6L+sUFpJEdqZYETgJWhANnz2zrScuVjW6udEmGPronL46Wm0Sk1ohkHKZ1hh5nDo +CP6twnVZJA7wLxpwg4yjd2ToTdXFKGKpU9GhIvU2Q6dGAKqXVHLUckcCAwEAAaN7 +MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg +Q2VydGlmaWNhdGUwHQYDVR0OBBYEFB/bwNk8S3eomqwzH3twxM+6yAfdMB8GA1Ud +IwQYMBaAFDl3d6NOkoviJSByZDUKeoeoWKn4MA0GCSqGSIb3DQEBBQUAA4GBAB7N +g2ax2+pcN368MURScgOum0QgLK0AIKXcz53IyI/fzyQmnEOD9NL/69nkfSXPH7iq +Y1gDudpSQvj+LnHMj94mNM3aXHo7ZAcYJ6FhtlgylhCX8n8AxERDt53iMWlPwpXF +ozLRwADG71i5D+YIOg3JwBT3JoxDE1Ubk3Fyx60v +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4098 (0x1002) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=San Francisco, O=OpenResty, CN=Root CA + Validity + Not Before: Sep 20 05:09:05 2014 GMT + Not After : Aug 27 05:09:05 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=Signing-CA-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b9:9a:3d:b6:31:dd:b6:8a:f1:9f:61:25:79:70: + f6:ea:4b:6a:0f:0c:72:ea:45:fc:4d:51:cf:f5:71: + 88:94:9c:f9:04:40:99:fd:2d:17:15:3a:de:5f:70: + 4a:06:79:13:fb:81:49:ad:da:59:44:12:81:74:9d: + d8:19:3e:4e:e8:c7:00:ee:f9:96:81:7a:bf:09:e6: + 88:b0:e3:b2:e8:ca:e3:72:23:e4:86:83:41:ca:b3: + 49:c0:f5:76:8a:d7:b5:fc:a3:12:1b:2b:0b:b4:57: + 10:24:97:40:be:cb:17:e7:c5:de:93:1b:59:94:ff: + 34:3f:cd:4d:14:76:09:0e:f3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 12:57:8E:2C:9B:CA:C9:8D:F8:88:B1:4D:EE:A6:6D:F3:99:C3:AF:E1 + X509v3 Authority Key Identifier: + keyid:56:65:C9:8B:65:55:27:2E:AB:14:F0:26:46:BD:BB:9E:A1:2B:41:58 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 1e:fb:6f:3e:12:bd:45:11:59:52:d5:60:ff:7c:73:9e:32:ce: + 76:fa:0b:b6:4a:58:68:db:92:a4:a0:d2:63:24:27:9c:6a:c5: + 6c:fa:84:d4:b5:80:93:b0:79:8f:33:c6:06:99:49:81:99:f4: + 52:ba:bd:ff:6e:f5:69:3f:65:e0:59:51:ce:16:66:2f:39:b5: + 31:ff:18:2a:a4:8e:14:77:7b:a2:2c:54:4b:f0:a5:2c:83:12: + c4:d5:1c:4a:5f:7b:31:26:ed:63:ba:d5:83:e2:b5:1d:c3:f3: + 34:a0:ba:dd:ee:87:ee:70:71:ae:1b:c5:97:9b:08:a6:9c:ad: + c0:c2 +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwYDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQ +BgNVBAoMCU9wZW5SZXN0eTEQMA4GA1UEAwwHUm9vdCBDQTAgFw0xNDA5MjAwNTA5 +MDVaGA8yMTE0MDgyNzA1MDkwNVowTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh +bGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UEAwwMU2lnbmluZy1D +QS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5mj22Md22ivGfYSV5cPbq +S2oPDHLqRfxNUc/1cYiUnPkEQJn9LRcVOt5fcEoGeRP7gUmt2llEEoF0ndgZPk7o +xwDu+ZaBer8J5oiw47LoyuNyI+SGg0HKs0nA9XaK17X8oxIbKwu0VxAkl0C+yxfn +xd6TG1mU/zQ/zU0UdgkO8wIDAQABo1AwTjAdBgNVHQ4EFgQUEleOLJvKyY34iLFN +7qZt85nDr+EwHwYDVR0jBBgwFoAUVmXJi2VVJy6rFPAmRr27nqErQVgwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAe+28+Er1FEVlS1WD/fHOeMs52+gu2 +Slho25KkoNJjJCecasVs+oTUtYCTsHmPM8YGmUmBmfRSur3/bvVpP2XgWVHOFmYv +ObUx/xgqpI4Ud3uiLFRL8KUsgxLE1RxKX3sxJu1jutWD4rUdw/M0oLrd7ofucHGu +G8WXmwimnK3Awg== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4099 (0x1003) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=Signing-CA-1 + Validity + Not Before: Sep 20 05:25:04 2014 GMT + Not After : Aug 27 05:25:04 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=Signing-CA-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a4:d0:ae:16:a8:8f:9d:2c:ee:12:f5:0c:5e:29: + 65:9b:cc:9b:67:6f:40:24:d7:44:ff:d4:de:8d:d4: + 36:1c:e1:37:2b:df:ff:69:35:6d:0b:4f:ae:9a:16: + e7:a9:c6:24:d3:8e:a4:c3:2f:25:d8:f3:66:73:8e: + 84:8e:9c:a6:c7:f9:ce:8c:b7:9d:60:26:85:4c:8f: + f4:43:17:af:9d:94:1a:f5:21:7b:1c:2b:9c:ee:fe: + 4a:ca:6d:c7:cf:ee:2a:02:28:1f:6e:13:94:85:3f: + 50:a3:03:18:bd:6c:f9:b5:9d:37:b9:27:61:29:75: + d3:39:77:5e:83:41:aa:8c:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 39:77:77:A3:4E:92:8B:E2:25:20:72:64:35:0A:7A:87:A8:58:A9:F8 + X509v3 Authority Key Identifier: + keyid:12:57:8E:2C:9B:CA:C9:8D:F8:88:B1:4D:EE:A6:6D:F3:99:C3:AF:E1 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 3b:4b:b6:31:51:72:9a:ef:42:60:5e:98:60:71:d7:26:4a:46: + f1:0e:1f:08:be:e6:1b:5f:e2:fd:28:54:8d:b1:c5:09:6f:04: + cb:69:dc:39:5e:67:e0:91:9f:10:94:bc:35:90:4a:65:fe:58: + bd:e9:9d:18:f0:b2:c4:2c:6e:05:00:a4:63:59:6a:85:cf:0e: + 28:3a:ad:34:1c:1e:8c:08:cf:ac:79:18:e6:2b:16:49:9c:0b: + 09:66:50:29:53:78:04:9e:3d:27:40:c4:0c:72:d6:8c:d6:b1: + 9c:f5:f2:f8:8c:9c:0b:0d:e1:4b:9b:ec:c9:65:0c:1e:fe:27: + 07:96 +-----BEGIN CERTIFICATE----- +MIICYzCCAcygAwIBAgICEAMwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0xMCAXDTE0MDkyMDA1MjUwNFoYDzIxMTQwODI3MDUyNTA0 +WjBNMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MRUwEwYDVQQDDAxTaWduaW5nLUNBLTIwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAKTQrhaoj50s7hL1DF4pZZvMm2dvQCTXRP/U3o3UNhzhNyvf +/2k1bQtPrpoW56nGJNOOpMMvJdjzZnOOhI6cpsf5zoy3nWAmhUyP9EMXr52UGvUh +exwrnO7+Ssptx8/uKgIoH24TlIU/UKMDGL1s+bWdN7knYSl10zl3XoNBqowhAgMB +AAGjUDBOMB0GA1UdDgQWBBQ5d3ejTpKL4iUgcmQ1CnqHqFip+DAfBgNVHSMEGDAW +gBQSV44sm8rJjfiIsU3upm3zmcOv4TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBADtLtjFRcprvQmBemGBx1yZKRvEOHwi+5htf4v0oVI2xxQlvBMtp3Dle +Z+CRnxCUvDWQSmX+WL3pnRjwssQsbgUApGNZaoXPDig6rTQcHowIz6x5GOYrFkmc +CwlmUClTeASePSdAxAxy1ozWsZz18viMnAsN4Uub7MllDB7+JweW +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/chain/root-ca.crt b/lib/lua-resty-core/t/cert/chain/root-ca.crt new file mode 100644 index 0000000..d2f3c8f --- /dev/null +++ b/lib/lua-resty-core/t/cert/chain/root-ca.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIJAK3s1yAQ5tdfMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwIBcNMTQw +OTIwMDM1NTU0WhgPMjExNDA4MjcwMzU1NTRaMGAxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQK +DAlPcGVuUmVzdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAN7CcpCjiafBdl1KaExRcuutAF0/eq4/ht7L4/i0nPDzikscFJ/O +aVyH3UpUF/KMq+72vom2bEbUeRROr1rL/JRe9raGlQtvdovHZt6f4c3/Coihtupp +9BXYrBCU4P+Bxai5gtTXGFvLC2a72qKcXDNeH+NxpIaemfPxSvemCYUXAgMBAAGj +UDBOMB0GA1UdDgQWBBRWZcmLZVUnLqsU8CZGvbueoStBWDAfBgNVHSMEGDAWgBRW +ZcmLZVUnLqsU8CZGvbueoStBWDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAGjMH6qkY+61311DERFhDuYzMSSZjH53qzFseq/chlIMGjrgJIMy6rl7T0AU +2hjvW+FOyhf5NqRrAQDTTuLbtXZ/ygiUformE8lR/SNRY/DVj1yarQkWUC5UpqOs +GWG1VW9DHQAMFVkYwPO3XKeTXpEFOxPLHtXBYcVemCT4zo42 +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/chain/test-com.key.der b/lib/lua-resty-core/t/cert/chain/test-com.key.der new file mode 100644 index 0000000..3a19bbc Binary files /dev/null and b/lib/lua-resty-core/t/cert/chain/test-com.key.der differ diff --git a/lib/lua-resty-core/t/cert/chain/test-com.key.pem b/lib/lua-resty-core/t/cert/chain/test-com.key.pem new file mode 100644 index 0000000..883ea79 --- /dev/null +++ b/lib/lua-resty-core/t/cert/chain/test-com.key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDYU6ysXD/5gKiWS7BY21qG3sowAtkZyPYUxUDJQeu7etHh+ZY7 +VNXov6xQWkkR2plgROAlaEA2fPbOtJy5WNbq50SYY+uicvjpabRKTWiGQcpnWGHm +cOgI/q3CdVkkDvAvGnCDjKN3ZOhN1cUoYqlT0aEi9TZDp0YAqpdUctRyRwIDAQAB +AoGBAIl/5elIWYGFPaMKSPSxuECxq2II7WVuTru1BRDnTabE0lMICW185tohuqz4 +NimbAJIoNTCRqv73Pwjz1AobZb6Nm7TDaahhstak6IlTYKcjXVBuM/UU4G13Kz/f +hNVblv2cCn9CkeTNOvPZjYJXw/c4XlHasjDMMh8S83Q9095BAkEA+6oPzEiSsdo5 +RX9D0EV+Uv4ID08johKbcZdGbsp+mo+PQ9CYOlE67QcKf8J4Hp2SFmq7mpTvvS7F +tA/a2WwJswJBANwNwsJre3QPJmJCBAGsIrPrw9rFKLiT0/ajyhT7kKfG4Rw9t55S +lY9VPFOxAJF9lDo4QiFUHi/8Htvd0B78wx0CQFh5cRRgbzIXhgrosu6Ff+Otayf2 +qpBP+lX02M4aYmf0EGnG672U0SKDVy2TMKeSvckjvNCbi6z2xIqJCGdnlAECQFTh ++f6E91oNfgDo9iKvA7PjfeklpE+OtnStOYZeg640SSFbrTilIovnlR2zaUS17DeI ++/lfOUXJOx4UsfNCDQECQD7nndBJDJeSggFSJKcZ0RI59NVG8eGRSX7/3ycbq6+t +guGI7WBvhDH4jNNL8jhuE+XuJuhhzOwP85872AFgIgw= +-----END RSA PRIVATE KEY----- diff --git a/lib/lua-resty-core/t/cert/ocsp/chain.pem b/lib/lua-resty-core/t/cert/ocsp/chain.pem new file mode 100644 index 0000000..4743a36 --- /dev/null +++ b/lib/lua-resty-core/t/cert/ocsp/chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/ocsp/ocsp-req.der b/lib/lua-resty-core/t/cert/ocsp/ocsp-req.der new file mode 100644 index 0000000..f125311 Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/ocsp-req.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-no-certs.der b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-no-certs.der new file mode 100644 index 0000000..01a45cf Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-no-certs.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der new file mode 100644 index 0000000..61c775f Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der new file mode 100644 index 0000000..506dbd2 Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/ocsp-resp.der b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp.der new file mode 100644 index 0000000..1fe910f Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/ocsp-resp.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/revoked-chain.pem b/lib/lua-resty-core/t/cert/ocsp/revoked-chain.pem new file mode 100644 index 0000000..3f98b7c --- /dev/null +++ b/lib/lua-resty-core/t/cert/ocsp/revoked-chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 8 (0x8) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=revoked-test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:ca:50:23:9a:59:70:ea:00:47:ff:72:05:29:9b: + 5d:6d:4b:73:37:a4:ff:38:20:4b:5b:ac:1f:3b:34: + f5:12:f8:8b:0e:02:bc:bd:14:34:39:6f:7d:5b:1f: + d4:15:e7:64:2e:65:fb:b1:a8:aa:f6:96:d3:e6:2b: + 00:0e:f3:8a:ef:99:ab:3e:e6:5d:eb:6d:a6:4a:d0: + aa:ff:a9:d6:9a:41:f0:66:22:0a:38:9c:28:4f:1f: + 0d:cf:a2:79:96:f9:fc:3d:1e:83:70:f5:97:6e:07: + cf:a2:17:87:0d:2a:41:19:3a:44:96:89:e7:0d:cb: + 88:20:86:e1:de:08:8b:0d:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + FB:98:2B:56:90:69:E1:B4:2B:C2:DB:25:7C:13:87:D5:D7:BC:70:B6 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 43:77:33:e9:cc:b1:42:35:94:0a:57:a5:dd:94:21:c0:cc:42: + 04:81:bd:b2:ac:4d:10:68:f3:fe:33:0a:8e:b9:3e:e9:f2:44: + aa:1c:e7:3e:e8:e0:57:40:41:ef:4a:b1:32:b0:f2:75:7c:aa: + 77:d2:64:9d:ba:a1:12:ea:f9:83:31:ba:9f:83:58:1c:38:e9: + d0:a6:dd:04:72:85:d1:2d:c7:3b:b2:71:ef:e4:f6:57:0c:6a: + b6:fc:e5:13:2d:be:a6:c1:f4:4b:4d:c8:69:cc:7c:2e:25:c1: + 8e:80:9e:19:c3:17:b2:21:a7:af:e8:2f:f1:d4:bb:8c:a3:39: + be:49 +-----BEGIN CERTIFICATE----- +MIIDcTCCAtqgAwIBAgIBCDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowaDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRkwFwYDVQQDExByZXZva2VkLXRlc3QuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDKUCOaWXDqAEf/cgUpm11tS3M3pP84IEtbrB87NPUS+IsO +Ary9FDQ5b31bH9QV52QuZfuxqKr2ltPmKwAO84rvmas+5l3rbaZK0Kr/qdaaQfBm +Igo4nChPHw3PonmW+fw9HoNw9ZduB8+iF4cNKkEZOkSWiecNy4gghuHeCIsN2wID +AQABo4IBKzCCAScwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH +ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFPuYK1aQaeG0K8LbJXwTh9XX +vHC2MIGOBgNVHSMEgYYwgYOAFLML9X1RFlF+KDfDog8dLxDAUaOzoWikZjBkMQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVs +dCBDaXR5MRIwEAYDVQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2Et +MYIBAzA8BggrBgEFBQcBAQQwMC4wLAYIKwYBBQUHMAGGIGh0dHA6Ly8xMjcuMC4w +LjE6ODg4OC9vY3NwP2Zvbz0xMA0GCSqGSIb3DQEBBQUAA4GBAEN3M+nMsUI1lApX +pd2UIcDMQgSBvbKsTRBo8/4zCo65PunyRKoc5z7o4FdAQe9KsTKw8nV8qnfSZJ26 +oRLq+YMxup+DWBw46dCm3QRyhdEtxzuyce/k9lcMarb85RMtvqbB9EtNyGnMfC4l +wY6AnhnDF7Ihp6/oL/HUu4yjOb5J +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/ocsp/revoked-ocsp-resp.der b/lib/lua-resty-core/t/cert/ocsp/revoked-ocsp-resp.der new file mode 100644 index 0000000..71d41a7 Binary files /dev/null and b/lib/lua-resty-core/t/cert/ocsp/revoked-ocsp-resp.der differ diff --git a/lib/lua-resty-core/t/cert/ocsp/test-com.crt b/lib/lua-resty-core/t/cert/ocsp/test-com.crt new file mode 100644 index 0000000..d34cb6b --- /dev/null +++ b/lib/lua-resty-core/t/cert/ocsp/test-com.crt @@ -0,0 +1,69 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/ocsp/wrong-issuer-order-chain.pem b/lib/lua-resty-core/t/cert/ocsp/wrong-issuer-order-chain.pem new file mode 100644 index 0000000..098e862 --- /dev/null +++ b/lib/lua-resty-core/t/cert/ocsp/wrong-issuer-order-chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/test.crt b/lib/lua-resty-core/t/cert/test.crt new file mode 100644 index 0000000..a69a011 --- /dev/null +++ b/lib/lua-resty-core/t/cert/test.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqTCCAhICCQClDm1WkreW4jANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x +EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD +DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN +MTQwNzIxMDMyMzQ3WhgPMjE1MTA2MTMwMzIzNDdaMIGXMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG +A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl +c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEA6P18zUvtmaKQK2xePy8ZbFwSyTLw+jW6t9eZ +aiTec8X3ibN9WemrxHzkTRikxP3cAQoITRuZiQvF4Q7DO6wMkz/b0zwfgX5uedGq +047AJP6n/mwlDOjGSNomBLoXQzo7tVe60ikEm3ZyDUqnJPJMt3hImO5XSop4MPMu +Za9WhFcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA4OBb9bOyWB1//93nSXX1mdENZ +IQeyTK0Dd6My76lnZxnZ4hTWrvvd0b17KLDU6JnS2N5ee3ATVkojPidRLWLIhnh5 +0eXrcKalbO2Ce6nShoFvQCQKXN2Txmq2vO/Mud2bHAWwJALg+qi1Iih/gVYB9sct +FLg8zFOzRlYiU+6Mmw== +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/test.crt.der b/lib/lua-resty-core/t/cert/test.crt.der new file mode 100644 index 0000000..0b6ef63 Binary files /dev/null and b/lib/lua-resty-core/t/cert/test.crt.der differ diff --git a/lib/lua-resty-core/t/cert/test.key b/lib/lua-resty-core/t/cert/test.key new file mode 100644 index 0000000..6c13527 --- /dev/null +++ b/lib/lua-resty-core/t/cert/test.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDo/XzNS+2ZopArbF4/LxlsXBLJMvD6Nbq315lqJN5zxfeJs31Z +6avEfORNGKTE/dwBCghNG5mJC8XhDsM7rAyTP9vTPB+Bfm550arTjsAk/qf+bCUM +6MZI2iYEuhdDOju1V7rSKQSbdnINSqck8ky3eEiY7ldKingw8y5lr1aEVwIDAQAB +AoGBANgB66sKMga2SKN5nQdHS3LDCkevCutu1OWM5ZcbB4Kej5kC57xsf+tzPtab +emeIVGhCPOAALqB4YcT+QtMX967oM1MjcFbtH7si5oq6UYyp3i0G9Si6jIoVHz3+ +8yOUaqwKbK+bRX8VS0YsHZmBsPK5ryN50iUwsU08nemoA94BAkEA9GS9Q5OPeFkM +tFxsIQ1f2FSsZAuN/1cpZgJqY+YaAN7MSPGTWyfd7nWG/Zgk3GO9/2ihh4gww+7B +To09GkmW4QJBAPQOHC2V+t2TA98+6Lj6+TYwcGEkhOENfVpH25mQ+kXgF/1Bd6rA +nosT1bdAY+SnmWXbSw6Kv5C20Em+bEX8WjcCQCSRRjhsRdVODbaW9Z7kb2jhEoJN +sEt6cTlQNzcHYPCsZYisjM3g4zYg47fiIfHQAsfKkhDDcfh/KvFj9LaQOEECQQCH +eBWYEDpSJ7rsfqT7mQQgWj7nDThdG/nK1TxGP71McBmg0Gg2dfkLRhVJRQqt74Is +kc9V4Rp4n6F6baL4Lh19AkEA6pZZer0kg3Kv9hjhaITIKUYdfIp9vYnDRWbQlBmR +atV8V9u9q2ETZvqfHpN+9Lu6NYR4yXIEIRf1bnIZ/mr9eQ== +-----END RSA PRIVATE KEY----- diff --git a/lib/lua-resty-core/t/cert/test.key.der b/lib/lua-resty-core/t/cert/test.key.der new file mode 100644 index 0000000..537a4f1 Binary files /dev/null and b/lib/lua-resty-core/t/cert/test.key.der differ diff --git a/lib/lua-resty-core/t/cert/test2.crt b/lib/lua-resty-core/t/cert/test2.crt new file mode 100644 index 0000000..edc3b0d --- /dev/null +++ b/lib/lua-resty-core/t/cert/test2.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChzCCAfACCQDjCkJpJUtZmjANBgkqhkiG9w0BAQUFADCBhjELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x +EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UEAwwJdGVzdDIuY29tMSIwIAYJKoZI +hvcNAQkBFhNvcGVucmVzdHlAZ21haWwuY29tMCAXDTE0MDkxMzAwMTgxMFoYDzIx +MTQwODIwMDAxODEwWjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju +aWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCU9wZW5SZXN0eTES +MBAGA1UEAwwJdGVzdDIuY29tMSIwIAYJKoZIhvcNAQkBFhNvcGVucmVzdHlAZ21h +aWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDy+OVI2u5NBOeB2Cyz +Gnwy9b7Ao4CSi05XtUxh2IoVdzYZz6c4PFb9C1ad52LDdRStiQT5A7+RKLj6Kr7f +JrKFziJxMy4g4Kdn9G659vE7CWu/UAVjRUtc+mTBAEfjdbumizmHLG7DmnNhGl3R +NGiVNLsUInSMGfUlJRzZJXhI4QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEMmRvyN +N7uE24Tc6TR19JadNHK8g3YGktRoXWiqd/y0HY4NRPgvnK/nX7CY/wXa1j+uDO8K +e6/Ldm5RZrjtvfHJmTSAu8zkqTJz8bqRDH7kzL5Ni2Ky2x8r9dtB0ImpOiSlwvZN +snMvbrxEdwBiqlC9prV2f9aG+ACo1KnPL0j6 +-----END CERTIFICATE----- diff --git a/lib/lua-resty-core/t/cert/test2.key b/lib/lua-resty-core/t/cert/test2.key new file mode 100644 index 0000000..82ce6ce --- /dev/null +++ b/lib/lua-resty-core/t/cert/test2.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDy+OVI2u5NBOeB2CyzGnwy9b7Ao4CSi05XtUxh2IoVdzYZz6c4 +PFb9C1ad52LDdRStiQT5A7+RKLj6Kr7fJrKFziJxMy4g4Kdn9G659vE7CWu/UAVj +RUtc+mTBAEfjdbumizmHLG7DmnNhGl3RNGiVNLsUInSMGfUlJRzZJXhI4QIDAQAB +AoGAEqBB83PVENJvbOTFiHVfUAjGtr3R/Wnwd4jOcjHHZB3fZ9sjVoxJntxfp3s1 +dwZir2rxlqVS6i3VAFiGiVTOGo2Vvzhw2J7f58twCECmnLb2f863AkGEYe4dAndD +GHGD0WI0CBMD1sT18YCj561o0Wol5deWH0gM9pr2N3HkeIECQQD6hUKFlFhrpaHP +WNJsl6BxgE6pB5kxLcMcpIQ7P+kHUvtyvCJl5QZJqPrpPGjRsAI5Ph92rpsp/zDp +/IZNWGVjAkEA+Ele31Rt+XbV32MrLKZgBDBk+Pzss5LTn9fZ5v1k/7hrMk2VVWvk +AD6n5QiGe/g59woANpPb1T9l956SBf0d6wJABTXOS17pc9uvANP1FGMW6CVl/Wf2 +DKrJ+weE5IKQwyE7r4gwIvRfbBrClSU3fNzvPueG2f4JphbzmnoxBNzIxwJAYivY +mGNwzHehXx99/byXMHDWK+EN0n8WsBgP75Z3rekEcbJdfpYXY8Via1vwmOnwOW65 +4NqbzHix37PSNw37GwJBALxaGNpREO2Tk+oWOvsD2QyviMVae3mXAJHc6nLVdKDM +q0YvDT6VdeNYYFTkAuzJacsVXOpn6AnUMFj0OBedMhc= +-----END RSA PRIVATE KEY----- diff --git a/lib/lua-resty-core/t/count.t b/lib/lua-resty-core/t/count.t new file mode 100644 index 0000000..74e61bc --- /dev/null +++ b/lib/lua-resty-core/t/count.t @@ -0,0 +1,72 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + -- local verbose = true + local verbose = false + local outfile = "$Test::Nginx::Util::ErrLogFile" + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() + '; +_EOC_ + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: module size +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local base = require "resty.core.base" + local n = 0 + for _, _ in pairs(base) do + n = n + 1 + end + ngx.say("base size: ", n) + '; + } +--- request +GET /re + +--- stap2 +global c +probe process("$LIBLUA_PATH").function("rehashtab") { + c++ + printf("rehash: %d\n", c) +} + +--- response_body +base size: 16 +--- no_error_log +[error] + diff --git a/lib/lua-resty-core/t/ctx.t b/lib/lua-resty-core/t/ctx.t new file mode 100644 index 0000000..a848ea0 --- /dev/null +++ b/lib/lua-resty-core/t/ctx.t @@ -0,0 +1,86 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;\$prefix/html/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get ngx.ctx +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + for i = 1, 100 do + ngx.ctx.foo = i + end + ngx.say("ctx.foo = ", ngx.ctx.foo) + '; + } +--- request +GET /t +--- response_body +ctx.foo = 100 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):2 loop\]/ + + + +=== TEST 2: set ngx.ctx +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + for i = 1, 100 do + ngx.ctx = {foo = i} + end + ngx.say("ctx.foo = ", ngx.ctx.foo) + '; + } +--- request +GET /t +--- response_body +ctx.foo = 100 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):2 loop\]/ + diff --git a/lib/lua-resty-core/t/decode-base64.t b/lib/lua-resty-core/t/decode-base64.t new file mode 100644 index 0000000..632d080 --- /dev/null +++ b/lib/lua-resty-core/t/decode-base64.t @@ -0,0 +1,188 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: string +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64("aGVsbG8=") + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +hello +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: set base64 (nil) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64("") + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: set base64 (number) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64("My4xNA==") + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +3.14 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: set base64 (boolean) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64("dHJ1ZQ==") + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +true +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: string (buf size just smaller than 4096) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64(string.rep("a", 5460)) + end + if not s then + ngx.say("bad base64 string") + else + ngx.say(string.len(s)) + end + '; + } +--- request +GET /base64 +--- response_body +4095 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: string (buf size just a bit bigger than 4096) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.decode_base64(string.rep("a", 5462)) + end + if not s then + ngx.say("bad base64 string") + else + ngx.say(string.len(s)) + end + '; + } +--- request +GET /base64 +--- response_body +4096 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + diff --git a/lib/lua-resty-core/t/encode-base64.t b/lib/lua-resty-core/t/encode-base64.t new file mode 100644 index 0000000..f6bda69 --- /dev/null +++ b/lib/lua-resty-core/t/encode-base64.t @@ -0,0 +1,228 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set base64 (string) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64("hello") + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +aGVsbG8= +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: set base64 (nil) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64(nil) + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: set base64 (number) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64(3.14) + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +My4xNA== +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: set base64 (boolean) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64(true) + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +dHJ1ZQ== +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: set base64 (buf is a little larger than 4096) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64(string.rep("a", 3073)) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /base64 +--- response_body +4100 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: set base64 (buf is just 4096) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.encode_base64(string.rep("a", 3071)) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /base64 +--- response_body +4096 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: set base64 (number) without padding (explicitly specified) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 200 do + s = ngx.encode_base64(3.14, true) + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +My4xNA +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: set base64 (number) with padding (explictly specified) +--- http_config eval: $::HttpConfig +--- config + location = /base64 { + content_by_lua ' + local s + for i = 1, 200 do + s = ngx.encode_base64(3.14, false) + end + ngx.say(s) + '; + } +--- request +GET /base64 +--- response_body +My4xNA== +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + diff --git a/lib/lua-resty-core/t/exit.t b/lib/lua-resty-core/t/exit.t new file mode 100644 index 0000000..86cebb9 --- /dev/null +++ b/lib/lua-resty-core/t/exit.t @@ -0,0 +1,86 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +repeat_each(120); +#repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;\$prefix/html/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + ngx.exit(403) + '; + } +--- request +GET /t +--- response_body_like: 403 Forbidden +--- error_code: 403 +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 2: call ngx.exit() from a custom lua module +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local foo = require "foo" + foo.go() + '; + } +--- user_files +>>> foo.lua +local exit = ngx.exit + +local function go() + exit(403) + return +end + +return { go = go } +--- request +GET /t +--- response_body_like: 403 Forbidden +--- error_code: 403 +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + diff --git a/lib/lua-resty-core/t/md5.t b/lib/lua-resty-core/t/md5.t new file mode 100644 index 0000000..73a5f16 --- /dev/null +++ b/lib/lua-resty-core/t/md5.t @@ -0,0 +1,121 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + require "resty.core" + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set md5 hello +--- http_config eval: $::HttpConfig +--- config + location = /md5 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5("hello") + end + ngx.say(s) + '; + } +--- request +GET /md5 +--- response_body +5d41402abc4b2a76b9719d911017c592 +--- error_log eval +qr/\[TRACE 1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: nil string to ngx.md5 +--- http_config eval: $::HttpConfig +--- config + location = /md5 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5(nil) + end + ngx.say(s) + '; + } +--- request +GET /md5 +--- response_body +d41d8cd98f00b204e9800998ecf8427e +--- error_log eval +qr/\[TRACE 1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: empty string to ngx.md5 +--- http_config eval: $::HttpConfig +--- config + location /md5 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5("") + end + ngx.say(s) + '; + } +--- request +GET /md5 +--- response_body +d41d8cd98f00b204e9800998ecf8427e +--- error_log eval +qr/\[TRACE 1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: number to ngx.md5 +--- http_config eval: $::HttpConfig +--- config + location /md5 { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5(3.14) + end + ngx.say(s) + '; + } +--- request +GET /md5 +--- response_body +4beed3b9c4a886067de0e3a094246f78 +--- error_log eval +qr/\[TRACE 1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + diff --git a/lib/lua-resty-core/t/md5_bin.t b/lib/lua-resty-core/t/md5_bin.t new file mode 100644 index 0000000..decc7e7 --- /dev/null +++ b/lib/lua-resty-core/t/md5_bin.t @@ -0,0 +1,129 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set md5_bin (string) +--- http_config eval: $::HttpConfig +--- config + location = /md5_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5_bin("hello") + end + ngx.say(string.len(s)) + '; + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: set md5_bin (nil) +--- http_config eval: $::HttpConfig +--- config + location = /md5_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5_bin(nil) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: set md5_bin (number) +--- http_config eval: $::HttpConfig +--- config + location = /md5_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5_bin(3.14) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: set md5_bin (boolean) +--- http_config eval: $::HttpConfig +--- config + location = /md5_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.md5_bin(true) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + diff --git a/lib/lua-resty-core/t/misc.t b/lib/lua-resty-core/t/misc.t new file mode 100644 index 0000000..213731e --- /dev/null +++ b/lib/lua-resty-core/t/misc.t @@ -0,0 +1,117 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 1); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;\$prefix/html/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.is_subrequest +--- http_config eval: $::HttpConfig +--- config + location = /t { + return 201; + header_filter_by_lua ' + local rc + for i = 1, 100 do + rc = ngx.is_subrequest + end + ngx.log(ngx.WARN, "is subrequest: ", rc) + '; + } +--- request +GET /t +--- response_body +--- error_code: 201 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +["is subrequest: false,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua:3 loop\]/ +] + + + +=== TEST 2: ngx.headers_sent (false) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local rc + for i = 1, 100 do + rc = ngx.headers_sent + end + ngx.say("headers sent: ", rc) + '; + } +--- request +GET /t +--- response_body +headers sent: false +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):3 loop\]/ + + + +=== TEST 3: ngx.headers_sent (true) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + ngx.send_headers() + local rc + for i = 1, 100 do + rc = ngx.headers_sent + end + ngx.say("headers sent: ", rc) + '; + } +--- request +GET /t +--- response_body +headers sent: true +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + diff --git a/lib/lua-resty-core/t/ocsp.t b/lib/lua-resty-core/t/ocsp.t new file mode 100644 index 0000000..f66a7ad --- /dev/null +++ b/lib/lua-resty-core/t/ocsp.t @@ -0,0 +1,1549 @@ +# vim:set ft=ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 13); + +our $CWD = cwd(); + +no_long_string(); +#no_diff(); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$::CWD/lib/?.lua;;"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: get OCSP responder (good case) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP url found: http://127.0.0.1:8888/ocsp?foo=1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 2: get OCSP responder (not found) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP responder not found + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 3: get OCSP responder (no issuer cert at all) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to get OCSP responder: no issuer certificate in chain + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 4: get OCSP responder (issuer cert not next to the leaf cert) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/wrong-issuer-order-chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to get OCSP responder: issuer certificate not next to leaf + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 5: get OCSP responder (truncated) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data, 6) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + if err then + ngx.log(ngx.WARN, "still get an error: ", err) + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP url found: http:/, +still get an error: truncated + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 6: create OCSP request (good) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local req, err = ocsp.create_ocsp_request(cert_data) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + + local f = assert(io.open("t/cert/ocsp/ocsp-req.der", "r")) + local expected = assert(f:read("*a")) + f:close() + if req ~= expected then + ngx.log(ngx.ERR, "ocsp responder: got unexpected OCSP request") + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP request created with length 68 + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 7: create OCSP request (buffer too small) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to create OCSP request: output buffer too small: 68 > 67 + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 8: create OCSP request (empty string cert chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local cert_data = "" + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return ngx.exit(ngx.ERROR) + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log +lua ssl server name: "test.com" +failed to create OCSP request: d2i_X509_bio() failed + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 9: create OCSP request (no issuer cert in the chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to create OCSP request: no issuer certificate in chain + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 10: validate good OCSP response +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + local ok, err = ocsp.validate_ocsp_response(resp, cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 11: fail to validate OCSP response - no issuer cert +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to validate OCSP response: no issuer certificate in chain + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 12: validate good OCSP response - no certs in response +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-no-certs.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 13: validate OCSP response - OCSP response signed by an unknown cert and the OCSP response contains the unknown cert + +FIXME: we should complain in this case. + +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-signed-by-orphaned.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 14: fail to validate OCSP response - OCSP response signed by an unknown cert and the OCSP response does not contain the unknown cert + +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +failed to validate OCSP response: OCSP_basic_verify() failed + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 15: fail to validate OCSP response - OCSP response returns revoked status +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/revoked-chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return ngx.exit(ngx.ERROR) + end + + local f = assert(io.open("t/cert/ocsp/revoked-ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return ngx.exit(ngx.ERROR) + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log +lua ssl server name: "test.com" +failed to validate OCSP response: certificate status "revoked" in the OCSP response + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 16: good status req from client +FIXME: check the OCSP staple actually received by the ssl client +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = assert(f:read("*a")) + f:close() + + print("resp len: ", #resp) + + local ok, err = ocsp.set_ocsp_status_resp(resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return + end + ngx.log(ngx.WARN, "ocsp status resp set ok: ", err) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true, true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +ocsp status resp set ok: nil, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 17: no status req from client +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = assert(f:read("*a")) + f:close() + + print("resp len: ", #resp) + + local ok, err = ocsp.set_ocsp_status_resp(resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return + end + ngx.log(ngx.WARN, "ocsp status resp set ok: ", err) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + +--- error_log +lua ssl server name: "test.com" +ocsp status resp set ok: no status req, + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/lib/lua-resty-core/t/re-find.t b/lib/lua-resty-core/t/re-find.t new file mode 100644 index 0000000..3b2c889 --- /dev/null +++ b/lib/lua-resty-core/t/re-find.t @@ -0,0 +1,263 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + -- local verbose = true + local verbose = false + local outfile = "$Test::Nginx::Util::ErrLogFile" + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() + '; +_EOC_ + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, 100 do + from, to, err = find(s, "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + '; + } +--- request +GET /re +--- response_body +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + '; + } +--- request +GET /re +--- response_body +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local from, to, err + local find = ngx.re.find + local s = "b" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.say("no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + '; + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 4: nil submatch (2nd) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local s = "hello, 1234" + local from, to, err + for i = 1, 100 do + from, to, err = ngx.re.find(s, "([0-9])|(hello world)", "jo", nil, 2) + end + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 5: nil submatch (1st) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local s = "hello, 1234" + local from, to, err + for i = 1, 400 do + from, to, err = ngx.re.find(s, "(hello world)|([0-9])", "jo", nil, 1) + end + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 6: specify the group (2) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local s = "hello, 1234" + local from, to, err + for i = 1, 100 do + from, to, err = ngx.re.find(s, "([0-9])([0-9]+)", "jo", nil, 2) + end + if from then + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + else + if err then + ngx.say("error: ", err) + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +from: 9 +to: 11 +matched: 234 +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + diff --git a/lib/lua-resty-core/t/re-match.t b/lib/lua-resty-core/t/re-match.t new file mode 100644 index 0000000..6263c75 --- /dev/null +++ b/lib/lua-resty-core/t/re-match.t @@ -0,0 +1,543 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 3); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + -- local verbose = true + local verbose = false + local outfile = "$Test::Nginx::Util::ErrLogFile" + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() + '; +_EOC_ + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- ngx.say("$2: ", m[2]) + -- ngx.say("$3: ", m[3]) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +matched: a +$1: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +matched: a +$1: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("b", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: not matched, no submatch, no jit compile, no regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("b", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 5: submatches, matched, no regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\\d)(\\d+)]]) + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 6: submatches, matched, with regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\\d)(\\d+)]], "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- ngx.say(table.maxn(m)) + -- collectgarbage() + '; + } +--- request +GET /re +--- response_body +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- error_log eval +qr/\[TRACE \d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: named subpatterns w/ extraction (matched) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +hello, 1234 +hello +hello +nil + +--- error_log eval +qr/\[TRACE \d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: named subpatterns w/ extraction (use of duplicate names in non-duplicate mode) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body_like chop +error: pcre_compile\(\) failed: two named subpatterns have the same name + +--- error_log eval +qr/\[TRACE \d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 9: named subpatterns w/ extraction (use of duplicate names in duplicate mode) +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(table.concat(m.first, "|")) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body_like +hello, 1234 +h +ello +h|ello +nil + +--- error_log eval +qr/\[TRACE \d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 10: captures input table in ngx.re.match +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local new_tab = require "table.new" + local clear_tab = require "table.clear" + local m + local res = new_tab(5, 0) + res[5] = "hello" + for i = 1, 100 do + m = ngx.re.match("hello, 1234", "([0-9])([0-9])([0-9])([0-9])", "jo", nil, res) + end + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m[4]) + ngx.say(m[5]) + else + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +1234 +1 +2 +3 +4 +hello +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 11: unmatched captures are false +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m = ngx.re.match("hello!", "(hello)(, .+)?(!)", "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + else + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +hello! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 12: unmatched trailing captures are false +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m = ngx.re.match("hello", "(hello)(, .+)?(!)?", "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + else + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +hello +hello +false +false +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 13: unmatched named captures are false +--- http_config eval: $::HttpConfig +--- config + location /re { + content_by_lua ' + local m = ngx.re.match("hello!", "(?hello)(?, .+)?(?!)", "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m.first) + ngx.say(m.second) + ngx.say(m.third) + else + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +hello! +hello +false +! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + diff --git a/lib/lua-resty-core/t/re-sub.t b/lib/lua-resty-core/t/re-sub.t new file mode 100644 index 0000000..721bfaa --- /dev/null +++ b/lib/lua-resty-core/t/re-sub.t @@ -0,0 +1,328 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 9); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + -- local verbose = true + local verbose = false + local outfile = "$Test::Nginx::Util::ErrLogFile" + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() + '; +_EOC_ + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sub, no submatch, no jit compile, regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local sub = ngx.re.sub + for i = 1, 350 do + s, n, err = sub("abcbd", "b", "B", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + '; + } +--- request +GET /re +--- response_body +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 2: sub, no submatch, no jit compile, no regex cache +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local sub = ngx.re.sub + for i = 1, 400 do + s, n, err = sub("abcbd", "b", "B") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + '; + } +--- request +GET /re +--- response_body +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 3: func + submatches +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local function f(m) + return "[" .. m[0] .. "(" .. m[1] .. ")]" + end + local sub = ngx.re.sub + for i = 1, 200 do + s, n, err = sub("abcbd", "b(c)", f, "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + '; + } +--- request +GET /re +--- response_body +s: a[bc(c)]bd +n: 1 +--- no_error_log eval +[ +"[error]", +"bad argument type", +qr/NYI (?!bytecode 51 at)/, +] + + + +=== TEST 4: replace template + submatches +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local sub = ngx.re.sub + for i = 1, 350 do + s, n, err = sub("abcbd", "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + '; + } +--- request +GET /re +--- response_body +s: a[bc(c)]bd +n: 1 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 5: replace template + submatches (exceeding buffers) +--- http_config eval: $::HttpConfig +--- config + location = /re { + access_log off; + content_by_lua ' + local m, err + local gsub = ngx.re.gsub + local subj = string.rep("bcbd", 2048) + for i = 1, 10 do + s, n, err = gsub(subj, "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + '; + } +--- request +GET /re +--- response_body eval +"s: " . ("[bc(c)]bd" x 2048) . +"\nn: 2048\n" + +--- no_error_log +[error] +bad argument type + + + +=== TEST 6: ngx.re.gsub: use of ngx.req.get_headers in the user callback +--- http_config eval: $::HttpConfig +--- config + +location = /t { + content_by_lua ' + local data = [[ + INNER + INNER +]] + + -- ngx.say(data) + + local res = ngx.re.gsub(data, "INNER", function(inner_matches) + local header = ngx.req.get_headers()["Host"] + -- local header = ngx.var["http_HEADER"] + return "INNER_REPLACED" + end, "s") + + ngx.print(res) + '; +} + +--- request +GET /t +--- response_body + INNER_REPLACED + INNER_REPLACED + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: ngx.re.gsub: use of ngx.var in the user callback +--- http_config eval: $::HttpConfig +--- config + +location = /t { + content_by_lua ' + local data = [[ + INNER + INNER +]] + + -- ngx.say(data) + + local res = ngx.re.gsub(data, "INNER", function(inner_matches) + -- local header = ngx.req.get_headers()["Host"] + local header = ngx.var["http_HEADER"] + return "INNER_REPLACED" + end, "s") + + ngx.print(res) + '; +} + +--- request +GET /t +--- response_body + INNER_REPLACED + INNER_REPLACED + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: ngx.re.gsub: recursive calling (github openresty/lua-nginx-module#445) +--- http_config eval: $::HttpConfig +--- config + +location = /t { + content_by_lua ' + function test() + local data = [[ + OUTER {FIRST} +]] + + local p1 = "(OUTER)(.+)" + local p2 = "{([A-Z]+)}" + + ngx.print(data) + + local res = ngx.re.gsub(data, p1, function(m) + -- ngx.say("pre: m[1]: [", m[1], "]") + -- ngx.say("pre: m[2]: [", m[2], "]") + + local res = ngx.re.gsub(m[2], p2, function(_) + return "REPLACED" + end, "") + + -- ngx.say("post: m[1]: [", m[1], "]") + -- ngx.say("post m[2]: [", m[2], "]") + return m[1] .. res + end, "") + + ngx.print(res) + end + + test() + '; +} +--- request +GET /t +--- response_body + OUTER {FIRST} + OUTER REPLACED +--- no_error_log +[error] +bad argument type +NYI + diff --git a/lib/lua-resty-core/t/request.t b/lib/lua-resty-core/t/request.t new file mode 100644 index 0000000..3d13035 --- /dev/null +++ b/lib/lua-resty-core/t/request.t @@ -0,0 +1,573 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 10); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on("b", "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.req.get_headers +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local headers + for i = 1, 200 do + headers = ngx.req.get_headers() + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + '; + } +--- request +GET /t +--- response_body +bar: bar +baz: baz +connection: close +foo: foo +host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- error_log eval +qr/\[TRACE \d+ .*? -> 1\]/ +--- no_error_log eval +[ +"[error]", +qr/ -- NYI: (?!return to lower frame)/, +] + + + +=== TEST 2: ngx.req.get_headers (raw) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local headers + for i = 1, 200 do + headers = ngx.req.get_headers(100, true) + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + '; + } +--- request +GET /t +--- response_body +Bar: bar +Baz: baz +Connection: close +Foo: foo +Host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- error_log eval +qr/\[TRACE \d+ .*? -> 1\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: ngx.req.get_headers (count is 2) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local headers + for i = 1, 200 do + headers = ngx.req.get_headers(2, true) + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + '; + } +--- request +GET /t +--- response_body +Connection: close +Host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: ngx.req.get_headers (metatable) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local headers, header + for i = 1, 100 do + headers = ngx.req.get_headers() + header = headers["foo_BAR"] + end + ngx.say("foo_BAR: ", header) + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + '; + } +--- request +GET /t +--- response_body +foo_BAR: foo +baz: baz +connection: close +foo-bar: foo +host: localhost +--- more_headers +Foo-Bar: foo +Baz: baz +--- error_log eval +qr/\[TRACE \d+ .*? -> \d\]/ +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!return to lower frame at)(?!C function 0x[0-9a-f]+ at content_by_lua\(nginx.conf:\d+\):15)/, +] + + + +=== TEST 5: ngx.req.get_uri_args +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local args + for i = 1, 200 do + args = ngx.req.get_uri_args() + end + if type(args) ~= "table" then + ngx.say("bad args type found: ", args) + return + end + local keys = {} + for k, _ in pairs(args) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = args[k] + if type(v) == "table" then + ngx.say(k, ": ", table.concat(v, ", ")) + else + ngx.say(k, ": ", v) + end + end + '; + } +--- request +GET /t?a=3%200&foo%20bar=&a=hello&blah +--- response_body +a: 3 0, hello +blah: true +foo bar: +--- error_log eval +qr/\[TRACE \d+ .*? -> \d+\]/ +--- no_error_log +[error] + -- NYI: +--- wait: 0.2 + + + +=== TEST 6: ngx.req.get_uri_args (empty) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local args + for i = 1, 200 do + args = ngx.req.get_uri_args() + end + if type(args) ~= "table" then + ngx.say("bad args type found: ", args) + return + end + local keys = {} + for k, _ in pairs(args) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = args[k] + if type(v) == "table" then + ngx.say(k, ": ", table.concat(v, ", ")) + else + ngx.say(k, ": ", v) + end + end + '; + } +--- request +GET /t? +--- response_body +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: ngx.req.start_time() +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.start_time() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + '; + } +--- request +GET /t +--- response_body +true +>= 0.099: true +< 0.11: true + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 8: ngx.req.get_method (GET) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + '; + } +--- request +GET /t +--- response_body +method: GET + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 9: ngx.req.get_method (OPTIONS) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + '; + } +--- request +OPTIONS /t +--- response_body +method: OPTIONS + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 10: ngx.req.get_method (POST) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + '; + } +--- request +POST /t +hello +--- response_body +method: POST + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 11: ngx.req.get_method (unknown method) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + '; + } +--- request +BLAH /t +hello +--- response_body +method: BLAH + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 12: ngx.req.get_method (CONNECT) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + '; + } +--- request +CONNECT /t +hello +--- response_body +method: CONNECT + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 13: ngx.req.set_method (GET -> PUT) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + ngx.req.set_method(ngx.HTTP_PUT) + end + ngx.say("method: ", ngx.req.get_method()) + '; + } +--- request +GET /t +--- response_body +method: PUT + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 14: ngx.req.set_header (single number value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + ngx.req.set_header("foo", i) + end + ngx.say("header foo: ", ngx.var.http_foo) + '; + } +--- request +GET /t +--- response_body +header foo: 500 + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 15: ngx.req.set_header (nil value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + ngx.req.set_header("foo", nil) + end + ngx.say("header foo: ", type(ngx.var.http_foo)) + '; + } +--- request +GET /t +--- response_body +header foo: nil + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 16: ngx.req.clear_header +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + ngx.req.set_header("foo", "hello") + local t + for i = 1, 500 do + t = ngx.req.clear_header("foo") + end + ngx.say("header foo: ", type(ngx.var.http_foo)) + '; + } +--- request +GET /t +--- response_body +header foo: nil + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + diff --git a/lib/lua-resty-core/t/response.t b/lib/lua-resty-core/t/response.t new file mode 100644 index 0000000..8851018 --- /dev/null +++ b/lib/lua-resty-core/t/response.t @@ -0,0 +1,234 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on("b", "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: write to ngx.header.HEADER (single value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + for i = 1, 100 do + ngx.header["Foo"] = i + end + ngx.say("Foo: ", ngx.header["Foo"]) + '; + } +--- request +GET /t +--- response_body +Foo: 100 + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: write to ngx.header.HEADER (nil) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + for i = 1, 200 do + ngx.header["Foo"] = i + ngx.header["Foo"] = nil + end + ngx.say("Foo: ", ngx.header["Foo"]) + '; + } +--- request +GET /t +--- response_body +Foo: nil + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- wait: 0.2 +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: write to ngx.header.HEADER (multi-value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + for i = 1, 200 do + ngx.header["Foo"] = {i, i + 1} + end + local v = ngx.header["Foo"] + if type(v) == "table" then + ngx.say("Foo: ", table.concat(v, ", ")) + else + ngx.say("Foo: ", v) + end + '; + } +--- request +GET /t +--- response_body +Foo: 200, 201 + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: read from ngx.header.HEADER (single value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local v + for i = 1, 100 do + ngx.header["Foo"] = i + v = ngx.header["Foo"] + end + ngx.say("Foo: ", v) + '; + } +--- request +GET /t +--- response_body +Foo: 100 + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log eval +[ +"[error]", +qr/ -- NYI: (?!return to lower frame)/, +"stitch", +] + + + +=== TEST 5: read from ngx.header.HEADER (not found) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local v + for i = 1, 100 do + v = ngx.header["Foo"] + end + ngx.say("Foo: ", v) + '; + } +--- request +GET /t +--- response_body +Foo: nil + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 6: read from ngx.header.HEADER (multi-value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + ngx.header["Foo"] = {"foo", "bar"} + local v + for i = 1, 100 do + v = ngx.header["Foo"] + end + ngx.say("Foo: ", table.concat(v, ", ")) + '; + } +--- request +GET /t +--- response_body +Foo: foo, bar + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 7: set multi values to cache-control and override it with multiple values +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua ' + ngx.header.cache_control = { "private", "no-store" } + ngx.header.cache_control = { "no-cache", "blah", "foo" } + local v + for i = 1, 400 do + v = ngx.header.cache_control + end + ngx.say("Cache-Control: ", table.concat(v, ", ")) + '; + } +--- request + GET /lua +--- response_headers +Cache-Control: no-cache, blah, foo +--- response_body_like chop +^Cache-Control: no-cache[;,] blah[;,] foo$ +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):5 (?:loop|-> \d+)\]/ +--- no_error_log +[error] + -- NYI: +stitch + diff --git a/lib/lua-resty-core/t/semaphore.t b/lib/lua-resty-core/t/semaphore.t new file mode 100644 index 0000000..60ff34b --- /dev/null +++ b/lib/lua-resty-core/t/semaphore.t @@ -0,0 +1,1802 @@ +# vim:set ft=ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 2); + +my $pwd = cwd(); + +no_long_string(); +#no_diff(); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "\"$pwd/lib/?.lua;;\""; +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;;"; +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: basic semaphore in uthread +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait() + ngx.say("enter waiting") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + local co = ngx.thread.spawn(sem_wait) + + ngx.say("back in main thread") + + sem:post() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +enter waiting +back in main thread +still in main thread +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 2: semaphore wait order +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait(id) + ngx.say("enter waiting, id: ", id) + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success, id: ", id) + end + end + + local co1 = ngx.thread.spawn(sem_wait, 1) + local co2 = ngx.thread.spawn(sem_wait, 2) + + ngx.say("back in main thread") + + sem:post(2) + + local ok, err = sem:wait(0) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) -- busy + end + + ngx.say("still in main thread") + + local ok, err = sem:wait(0.01) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) + end + + ngx.sleep(0.01) + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +enter waiting, id: 1 +enter waiting, id: 2 +back in main thread +wait failed in main thread: timeout +still in main thread +wait success, id: 1 +wait success, id: 2 +wait failed in main thread: timeout +main thread end +--- no_error_log +[error] + + + +=== TEST 3: semaphore wait time=0 +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(1) + + local function wait_1s() + ngx.say("enter 1s wait") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err in wait 1s: ", err) + else + ngx.say("wait success in 1s wait") + end + end + + local function wait_0() + local ok, err = sem:wait(0) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + wait_0() + wait_0() + + local co = ngx.thread.spawn(wait_1s) + + ngx.say("back in main thread") + + wait_0() + + sem:post(2) + + wait_0() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + wait_0() + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +wait success +err: timeout +enter 1s wait +back in main thread +err: timeout +err: timeout +still in main thread +wait success in 1s wait +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 4: basic semaphore in subrequest +--- http_config eval: $::HttpConfig +--- config + location = /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + { "/sem_wait"}, + { "/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local g = package.loaded["semaphore_test"] or {} + package.loaded["semaphore_test"] = g + + if not g.test then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + return + end + g.test = sem + end + local sem = g.test + local ok, err = sem:wait(1) + if ok then + ngx.print("wait") + end + } + } + + location /sem_post { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local g = package.loaded["semaphore_test"] or {} + package.loaded["semaphore_test"] = g + + if not g.test then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + g.test = sem + end + local sem = g.test + ngx.sleep(0.001) + collectgarbage("collect") + sem:post() + ngx.print("post") + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- no_error_log +[error] +[crit] + + + +=== TEST 5: semaphore.new in init_by_lua* (w/o shdict) +--- http_config + lua_package_path $TEST_NGINX_LUA_PACKAGE_PATH; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- config + location /test { + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } + } +--- request +GET /test +--- response_body_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 6: semaphore.new in init_by_lua* (with shdict) +--- http_config + lua_shared_dict dogs 1m; + lua_package_path $TEST_NGINX_LUA_PACKAGE_PATH; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- config + location /test { + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } + } +--- request +GET /test +--- response_body_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 7: semaphore in init_worker_by_lua (wait is not allowed) +--- http_config + lua_package_path $TEST_NGINX_LUA_PACKAGE_PATH; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem count: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem wait: ", err) + end + } +--- config + location /t { + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 1, +sem wait: API disabled in the context of init_worker_by_lua*, +", +"", +] + + + +=== TEST 8: semaphore in init_worker_by_lua (new and post) +--- http_config + lua_package_path $TEST_NGINX_LUA_PACKAGE_PATH; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(2) + + local count = sem:count() + ngx.log(ngx.WARN, "sem count: ", count) + + package.loaded.my_sema = sem + } +--- config + location /t { + content_by_lua_block { + local sem = package.loaded.my_sema + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("sem wait successfully.") + } + } +--- request +GET /t +--- response_body +sem wait successfully. +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 2, +", +"" +] +--- no_error_log +[error] + + + +=== TEST 9: semaphore in set_by_lua (wait is not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + set_by_lua_block $res { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of set_by_lua*, +", +"sem: 1, +sem: API disabled in the context of set_by_lua*, +", +] + + + +=== TEST 10: semaphore in rewrite_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + rewrite_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 11: semaphore in access_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + access_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 12: semaphore in content_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + else + ngx.say("ok") + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 13: semaphore in log_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + log_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1 while logging request, +sem: API disabled in the context of log_by_lua* while logging request, +", +"sem: 1 while logging request, +sem: API disabled in the context of log_by_lua* while logging request, +", +] +--- wait: 0.2 + + + +=== TEST 14: semaphore in header_filter_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + header_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of header_filter_by_lua*, +", +"sem: 1, +sem: API disabled in the context of header_filter_by_lua*, +", +] + + + +=== TEST 15: semaphore in body_filter_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + body_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +", +"sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +", +] + + + +=== TEST 16: semaphore in ngx.timer (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local function func_sem() + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + end + + local ok, err = ngx.timer.at(0, func_sem) + if ok then + ngx.sleep(0.01) + ngx.say("ok") + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] +--- wait: 0.2 + + + +=== TEST 17: semaphore post in all phase (in a request) +--- http_config + lua_package_path $TEST_NGINX_LUA_PACKAGE_PATH; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + + local function wait() + local i = 0 + while true do + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + i = i + 1 + if i % 6 == 0 then + ngx.log(ngx.ERR, "sem: 6 times") + end + end + end + + local ok, err = ngx.timer.at(0, wait) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } +--- config + location /test { + set_by_lua_block $res { + local sem = package.loaded.sem + sem:post() + } + rewrite_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + access_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + content_by_lua_block { + local sem = package.loaded.sem + sem:post() + ngx.say("ok") + } + header_filter_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + body_filter_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + } +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 6 times, +", +"sem: 6 times, +", +] +--- wait: 0.2 + + + +=== TEST 18: semaphore wait post in access_by_lua +--- http_config eval: $::HttpConfig +--- config + location /test { + access_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + + local func_wait = function () + ngx.say("enter wait") + + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success") + end + end + local func_post = function () + ngx.say("enter post") + + sem:post() + ngx.say("post success") + end + + local co1 = ngx.thread.spawn(func_wait) + local co2 = ngx.thread.spawn(func_post) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + } + } +--- request +GET /test +--- response_body +enter wait +enter post +post success +wait success +--- no_error_log +[error] + + + +=== TEST 19: semaphore wait post in rewrite_by_lua +--- http_config eval: $::HttpConfig +--- config + location /t { + rewrite_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + + local func_wait = function () + ngx.say("enter wait") + + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success") + end + end + local func_post = function () + ngx.say("enter post") + + sem:post() + ngx.say("post success") + end + + local co1 = ngx.thread.spawn(func_wait) + local co2 = ngx.thread.spawn(func_post) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + } + } +--- request +GET /test +--- response_body +enter wait +enter post +post success +wait success +--- no_error_log +[error] + + + +=== TEST 20: semaphore wait in timer.at +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function func_wait(premature) + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.ERR, "wait success") + end + end + + ngx.timer.at(0, func_wait) + + sem:post() + ngx.sleep(0.01) + ngx.say("ok") + } + } +--- request +GET /test +--- response_body +ok +--- error_log +wait success + + + +=== TEST 21: semaphore post in header_filter_by_lua (subrequest) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(1) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + header_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- no_error_log +[error] + + + +=== TEST 22: semaphore post in body_filter_by_lua (subrequest) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(10) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + body_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 23: semaphore post in set_by_lua +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(10) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + set_by_lua_block $res { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 24: semaphore post in timer.at +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + package.loaded.sem = semaphore.new(0) + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local sem = package.loaded.sem + local ok, err = sem:wait(2) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.status = 500 + ngx.say(err) + end + } + } + + location /sem_post { + content_by_lua_block { + local function func(premature) + local sem = package.loaded.sem + sem:post() + end + ngx.timer.at(0, func, g) + ngx.sleep(0) + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 25: two thread wait for each other +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem_A = semaphore.new(0) + local sem_B = semaphore.new(0) + if not sem_A or not sem_B then + error("create failed") + end + + local function th_A() + for i = 1, 11 do + local ok, err = sem_A:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_B:post(1) + end + ngx.say("count in A: ", sem_A:count()) + end + local function th_B() + for i = 1, 10 do + local ok, err = sem_B:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_A:post(1) + end + ngx.say("count in B: ", sem_B:count()) + end + + local co_A = ngx.thread.spawn(th_A) + local co_B = ngx.thread.spawn(th_B) + + sem_A:post(1) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +count in B: 0 +count in A: 0 +--- no_error_log +[error] + + + +=== TEST 26: kill a light thread that is waiting on a semaphore(no resource) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + local ok, err = ngx.thread.kill(co) + if ok then + ngx.say("ok") + else + ngx.say(err) + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- no_error_log +[error] + + + +=== TEST 27: kill a light thread that is waiting on a semaphore(after post) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + + sem:post() + local ok, err = ngx.thread.kill(co) + + if ok then + ngx.say("ok") + else + ngx.say(err) + end + + ngx.sleep(0.01) + + local count = sem:count() + ngx.say("count: ", count) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +count: 1 +--- no_error_log +[error] + + + +=== TEST 28: kill a thread that is waiting on another thread that is waiting on semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function sem_wait() + ngx.say("sem waiting start") + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("sem wait err: ", err) + end + ngx.say("sem waiting done") + end + + local function thread_wait() + local co = ngx.thread.spawn(sem_wait) + + ngx.say("thread waiting start") + local ok, err = ngx.thread.wait(co) + if not ok then + ngx.say("thread wait err: ", err) + end + ngx.say("thread waiting done") + end + + local co2 = ngx.thread.spawn(thread_wait) + ngx.sleep(0.01) + + local ok, err = ngx.thread.kill(co2) + if ok then + ngx.say("thread kill success") + else + ngx.say("kill err: ", err) + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +sem waiting start +thread waiting start +thread kill success +sem wait err: timeout +sem waiting done +--- no_error_log +[error] + + + +=== TEST 29: a light thread that is going to exit is waiting on a semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + ngx.say("sem waiting") + local ok, err = sem:wait(0.1) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.say("ok") + ngx.exit(200) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +sem waiting +ok +--- error_log +http lua semaphore cleanup + + + +=== TEST 30: main thread wait a light thread that is waiting on a semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + local ok, err = sem:wait(0.001) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.thread.wait(co) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +err: timeout +--- no_error_log +[error] + + + +=== TEST 31: multi wait and mult post with one semaphore +--- http_config eval: $::HttpConfig +--- config + location = /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + ngx.exit(500) + end + + local function func(op, id) + ngx.say(op, ": ", id) + if op == "wait" then + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success: ", id) + end + else + sem:post() + end + end + local tco = {} + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "wait", i) + end + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "post", i) + end + + for i = 1, #tco do + ngx.thread.wait(tco[i]) + end + } + } +--- request +GET /test +--- response_body +wait: 1 +wait: 2 +wait: 3 +post: 1 +post: 2 +post: 3 +wait success: 1 +wait success: 2 +wait success: 3 +--- no_error_log +[error] + + + +=== TEST 32: semaphore wait time is zero +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + local ok, err = sem:wait(0) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /test +--- response_body +timeout +--- no_error_log +[error] + + + +=== TEST 33: test semaphore gc +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if sem then + ngx.say("success") + end + sem = nil + collectgarbage("collect") + } + } +--- request +GET /test +--- response_body +success +--- log_level: debug +--- error_log +in lua gc, semaphore + + + +=== TEST 34: basic semaphore_mm alloc +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if sem then + ngx.say("ok") + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/(new block, alloc semaphore|from head of free queue, alloc semaphore)/ +--- grep_error_log_out eval +[ +"new block, alloc semaphore +", +"from head of free queue, alloc semaphore +", +] + + + +=== TEST 35: basic semaphore_mm free insert tail +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + for i = 1, 2 do + if sems[i] then + sems[i] = nil + ngx.say("ok") + break + end + end + collectgarbage("collect") + } + } +--- log_level: debug +--- request +GET /t +--- response_body +ok +--- error_log +add to free queue tail + + + +=== TEST 36: basic semaphore_mm free insert head +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + if sems[#sems] then + sems[#sems] = nil + ngx.say("ok") + end + collectgarbage("collect") + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- error_log +add to free queue head + + + +=== TEST 37: semaphore_mm free block (load <= 50% & the on the older side) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block * 3] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + + for i = num_per_block + 1, num_per_block * 2 do + sems[i] = nil + end + else + for i = 1, num_per_block do + sems[i] = nil + end + end + + collectgarbage("collect") + ngx.say("ok") + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/free semaphore block/ +--- grep_error_log_out eval +[ +"", +"free semaphore block +", +] +--- timeout: 10 + + + +=== TEST 38: basic semaphore count +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(10) + local count = sem:count() + ngx.say(count) + + sem:wait(0) + local count = sem:count() + ngx.say(count) + + sem:post(3) + local count = sem:count() + ngx.say(count) + } + } +--- request +GET /test +--- response_body +10 +9 +12 +--- no_error_log +[error] + + + +=== TEST 39: basic semaphore count(negative number) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + local count = sem:count() + ngx.say(count) + + local function wait() + sem:wait(0.01) + end + local co = ngx.thread.spawn(wait) + + local count = sem:count() + ngx.say(count) + } + } +--- request +GET /test +--- response_body +0 +-1 +--- no_error_log +[error] diff --git a/lib/lua-resty-core/t/sha1_bin.t b/lib/lua-resty-core/t/sha1_bin.t new file mode 100644 index 0000000..8187941 --- /dev/null +++ b/lib/lua-resty-core/t/sha1_bin.t @@ -0,0 +1,129 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set sha1_bin (string) +--- http_config eval: $::HttpConfig +--- config + location = /sha1_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.sha1_bin("hello") + end + ngx.say(string.len(s)) + '; + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: set sha1_bin (nil) +--- http_config eval: $::HttpConfig +--- config + location = /sha1_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.sha1_bin(nil) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: set sha1_bin (number) +--- http_config eval: $::HttpConfig +--- config + location = /sha1_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.sha1_bin(3.14) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: set sha1_bin (boolean) +--- http_config eval: $::HttpConfig +--- config + location = /sha1_bin { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.sha1_bin(true) + end + ngx.say(string.len(s)) + '; + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + diff --git a/lib/lua-resty-core/t/shdict.t b/lib/lua-resty-core/t/shdict.t new file mode 100644 index 0000000..c62e25e --- /dev/null +++ b/lib/lua-resty-core/t/shdict.t @@ -0,0 +1,917 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 2); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get a string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: get an nonexistent key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + -- dogs:set("foo", "bar") + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: get a boolean value (true) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", true, 0, 5678) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: get a boolean value (false) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", false, 0, 777) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: get a number value (int) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 51203) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: get a number value (double) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 3.1415926, 0, 78) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: get a large string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", string.rep("bbbb", 1024) .. "a", 0, 912) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body eval +"value type: string +value: " . ("bbbb" x 1024) . "a +flags: 912 +" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: get_stale (false) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", "bar", 0, 72) + for i = 1, 100 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +stale: false +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 9: get_stale (true) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0.001, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + ngx.sleep(0.002) + for i = 1, 100 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +stale: true +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):12 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 10: incr int +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, foricible = dogs:set("foo", 56) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, err = dogs:incr("foo", 2) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + '; + } +--- request +GET /t +--- response_body +value: 256 +err: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 11: incr double +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 56) + for i = 1, 100 do + val, err = dogs:incr("foo", 2.1) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + '; + } +--- request +GET /t +--- response_body +value: 266 +err: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 12: set a string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 13: set a boolean value (true) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", true, 0, 5678) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 14: set a boolean value (false) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", false, 0, 777) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 15: set a number value (int) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 51203) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 16: set a number value (double) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 17: set a number value and a nil +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:set("foo", nil) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 18: safe set a number value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:safe_set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 19: add a string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 20: safe add a string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:safe_add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 21: replace a string value +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", "hello") + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:replace("foo", "bar" .. i, 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: string +value: bar100 +flags: 72 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 22: set a number value and delete +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:delete("foo") + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 23: set nil key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local val, flags + local dogs = ngx.shared.dogs + local ok, err = dogs:set(nil, "bar") + if not ok then + ngx.say("failed to set: ", err) + end + '; + } +--- request +GET /t +--- response_body +failed to set: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 24: get nil key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get(nil, "bar") + if not ok then + ngx.say("failed to get: ", err) + end + '; + } +--- request +GET /t +--- response_body +failed to get: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 25: get stale key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get_stale(nil, "bar") + if not ok then + ngx.say("failed to get stale: ", err) + end + '; + } +--- request +GET /t +--- response_body +failed to get stale: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 26: incr key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr(nil, 32) + if not ok then + ngx.say("failed to incr: ", err) + end + '; + } +--- request +GET /t +--- response_body +failed to incr: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 27: flush_all +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + dogs:set("foo", "bah") + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:flush_all() + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + '; + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + diff --git a/lib/lua-resty-core/t/ssl.t b/lib/lua-resty-core/t/ssl.t new file mode 100644 index 0000000..f87c583 --- /dev/null +++ b/lib/lua-resty-core/t/ssl.t @@ -0,0 +1,1558 @@ +# vim:set ft=ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 1); + +our $CWD = cwd(); + +no_long_string(); +#no_diff(); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$::CWD/lib/?.lua;;"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: clear certs +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log +lua ssl server name: "test.com" +sslv3 alert handshake failure + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 2: set DER cert and private key +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test.crt.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 3: read SNI name via ssl.server_name() +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + print("read SNI name from Lua: ", ssl.server_name()) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +read SNI name from Lua: test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 4: read SNI name via ssl.server_name() when no SNI name specified +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local name = ssl.server_name(), + print("read SNI name from Lua: ", name, ", type: ", type(name)) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, nil, true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +read SNI name from Lua: nil, type: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 5: read raw server addr via ssl.raw_server_addr() (unix domain socket) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log eval +[ +'lua ssl server name: "test.com"', +qr/Using unix socket file .*?nginx\.sock/ +] + +--- no_error_log +[error] +[alert] + + + +=== TEST 6: read raw server addr via ssl.raw_server_addr() (IPv4) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.1:12345 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.1", 12345) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +Using IPv4 address: 127.0.0.1 + +--- no_error_log +[error] +[alert] + + + +=== TEST 7: read raw server addr via ssl.raw_server_addr() (IPv6) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen [::1]:12345 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("[::1]", 12345) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +Using IPv6 address: 0.0.0.1 + +--- no_error_log +[error] +[alert] + + + +=== TEST 8: set DER cert chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 9: read PEM cert chain but set DER cert chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 10: tls version - SSLv3 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols SSLv3; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols SSLv3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: SSLv3, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 11: tls version - TLSv1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 12: tls version - TLSv1.1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.1; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.1; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1.1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 13: tls version - TLSv1.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1.2, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 14: ngx.semaphore in ssl_certificate_by_lua* +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen 127.0.0.2:8080 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local semaphore = require "ngx.semaphore" + + local sema = assert(semaphore.new()) + + local function f() + assert(sema:wait(1)) + end + + local t = assert(ngx.thread.spawn(f)) + ngx.sleep(0.25) + + assert(sema:post()) + + assert(ngx.thread.wait(t)) + print("ssl cert by lua done") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.2", 8080) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- grep_error_log eval: qr/http lua semaphore (?:wait yielding|\w[^:,]*)/ +--- grep_error_log_out +http lua semaphore new +http lua semaphore wait +http lua semaphore wait yielding +http lua semaphore post +--- error_log +ssl cert by lua done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 15: read PEM key chain but set DER key chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + pkey_data, err = ssl.priv_key_pem_to_der(pkey_data) + if not pkey_data then + ngx.log(ngx.ERR, "failed to convert pem key to der key: ", err) + return + end + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/lib/lua-resty-core/t/status.t b/lib/lua-resty-core/t/status.t new file mode 100644 index 0000000..e611b78 --- /dev/null +++ b/lib/lua-resty-core/t/status.t @@ -0,0 +1,92 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 7); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;\$prefix/html/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get ngx.status +--- http_config eval: $::HttpConfig +--- config + location = /t { + return 201; + header_filter_by_lua ' + local sum = 0 + for i = 1, 100 do + sum = sum + ngx.status + end + ngx.log(ngx.WARN, "sum: ", sum) + '; + } +--- request +GET /t +--- response_body +--- error_code: 201 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +["sum: 20100,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua:3 loop\]/ +] + + + +=== TEST 2: set ngx.status +--- http_config eval: $::HttpConfig +--- config + location = /t { + return 201; + header_filter_by_lua ' + for i = 100, 200 do + ngx.status = i + end + ngx.log(ngx.WARN, "status: ", ngx.status) + '; + } +--- request +GET /t +--- response_body +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +["status: 200,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua:2 loop\]/ +] + diff --git a/lib/lua-resty-core/t/time.t b/lib/lua-resty-core/t/time.t new file mode 100644 index 0000000..b9e1c7c --- /dev/null +++ b/lib/lua-resty-core/t/time.t @@ -0,0 +1,106 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + -- local verbose = true + local verbose = false + local outfile = "$Test::Nginx::Util::ErrLogFile" + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() + '; +_EOC_ + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.now() +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.now() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + '; + } +--- request +GET /t +--- response_body +true +>= 0.099: true +< 0.11: true + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 2: ngx.time() +--- http_config eval: $::HttpConfig +--- config + location = /t { + access_log off; + content_by_lua ' + local t + for i = 1, 500 do + t = ngx.time() + end + ngx.say(t > 1400960598) + local diff = os.time() - t + ngx.say(diff <= 1) + '; + } +--- request +GET /t +--- response_body +true +true + +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + diff --git a/lib/lua-resty-core/t/uri.t b/lib/lua-resty-core/t/uri.t new file mode 100644 index 0000000..cd531d4 --- /dev/null +++ b/lib/lua-resty-core/t/uri.t @@ -0,0 +1,262 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: unescape_uri (string) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.unescape_uri("hello%20world") + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body +hello world +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: unescape_uri (nil) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.unescape_uri(nil) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: unescape_uri (number) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.unescape_uri(3.14) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body +3.14 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: escape_uri (string, escaped) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri("hello world") + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body +hello%20world +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 5: escape_uri (string, no escaped) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri("helloworld") + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body +helloworld +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 6: escape_uri (nil) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri(nil) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 7: escape_uri (number) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri(3.14) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body +3.14 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 8: escape_uri (larger than 4k, nothing to be escaped) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri(string.rep("a", 4097)) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body eval: "a" x 4097 . "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 9: escape_uri (a little smaller than 4k, need to be escaped) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri(string.rep(" ", 1365)) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body eval: "%20" x 1365 . "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 10: escape_uri (a little bigger than 4k, need to be escaped) +--- http_config eval: $::HttpConfig +--- config + location = /uri { + content_by_lua ' + local s + for i = 1, 100 do + s = ngx.escape_uri(string.rep(" ", 1366)) + end + ngx.say(s) + '; + } +--- request +GET /uri +--- response_body eval: "%20" x 1366 . "\n" +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + diff --git a/lib/lua-resty-core/t/var.t b/lib/lua-resty-core/t/var.t new file mode 100644 index 0000000..a924641 --- /dev/null +++ b/lib/lua-resty-core/t/var.t @@ -0,0 +1,218 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get normal var +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local val + for i = 1, 100 do + val = ngx.var.foo + end + ngx.say("value: ", val) + '; + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 2: get normal var (case) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local val + for i = 1, 100 do + val = ngx.var.FOO + end + ngx.say("value: ", val) + '; + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 3: get capturing var (bad) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local val + for i = 1, 100 do + val = ngx.var[0] + end + ngx.say("value: ", val) + '; + } +--- request +GET /t +--- response_body +value: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: get capturing var +--- http_config eval: $::HttpConfig +--- config + location ~ '^(/t)' { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local val + for i = 1, 100 do + val = ngx.var[1] + end + ngx.say("value: ", val) + '; + } +--- request +GET /t +--- response_body +value: /t +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 5: set normal var (string value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + local val = "hello" + for i = 1, 100 do + ngx.var.foo = val + end + ngx.say("value: ", val) + '; + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: set normal var (nil value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + for i = 1, 100 do + ngx.var.foo = nil + end + ngx.say("value: ", ngx.var.foo) + '; + } +--- request +GET /t +--- response_body +value: nil +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: set normal var (number value) +--- http_config eval: $::HttpConfig +--- config + location = /t { + set $foo hello; + content_by_lua ' + local ffi = require "ffi" + for i = 1, 100 do + ngx.var.foo = i + end + ngx.say("value: ", ngx.var.foo) + '; + } +--- request +GET /t +--- response_body +value: 100 +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + diff --git a/lib/lua-resty-core/t/worker.t b/lib/lua-resty-core/t/worker.t new file mode 100644 index 0000000..49c1dfd --- /dev/null +++ b/lib/lua-resty-core/t/worker.t @@ -0,0 +1,92 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + lua_package_path "$pwd/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua ' + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on("b", "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + '; +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.worker.exiting +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local v + local exiting = ngx.worker.exiting + for i = 1, 400 do + v = exiting() + end + ngx.say(v) + '; + } +--- request +GET /t +--- response_body +false +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch + + + +=== TEST 2: ngx.worker.pid +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua ' + local v + local pid = ngx.worker.pid + for i = 1, 400 do + v = pid() + end + ngx.say(v == tonumber(ngx.var.pid)) + ngx.say(v) + '; + } +--- request +GET /t +--- response_body_like chop +^true +\d+$ +--- error_log eval +qr/\[TRACE \d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch + diff --git a/lib/lua-resty-core/valgrind.suppress b/lib/lua-resty-core/valgrind.suppress new file mode 100644 index 0000000..389b0bc --- /dev/null +++ b/lib/lua-resty-core/valgrind.suppress @@ -0,0 +1,53 @@ +# Valgrind suppression file for LuaJIT 2.0. +{ + + Memcheck:Leak + fun:malloc + fun:ngx_alloc + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_add_event +} +{ + + Memcheck:Cond + fun:index + fun:expand_dynamic_string_token + fun:_dl_map_object + fun:map_doit + fun:_dl_catch_error + fun:do_preload + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_notify_init + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_add_connection + fun:ngx_event_connect_peer +} diff --git a/utils/README b/utils/README new file mode 100644 index 0000000..e113cfc --- /dev/null +++ b/utils/README @@ -0,0 +1,9 @@ + +* conf + * demo conf of nginx and redis + +* html + * some html file + +* pytool + * a cmd line tool for dygateway diff --git a/utils/abtesting.sh b/utils/abtesting.sh new file mode 100755 index 0000000..7e20ddd --- /dev/null +++ b/utils/abtesting.sh @@ -0,0 +1,10 @@ +#killall nginx + +rm *temp -rf + +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/nginx.conf +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/stable.conf +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/beta1.conf +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/beta2.conf +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/beta3.conf +/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/beta4.conf diff --git a/utils/conf/beta1.conf b/utils/conf/beta1.conf index 0456667..9df6fe4 100644 --- a/utils/conf/beta1.conf +++ b/utils/conf/beta1.conf @@ -39,5 +39,10 @@ http { location = /50x.html { root html; } + location /abc { + content_by_lua ' ngx.say("this is beta1 server @ location abc") '; + } + + } } diff --git a/utils/conf/beta2.conf b/utils/conf/beta2.conf index 2b05939..7a99ba7 100644 --- a/utils/conf/beta2.conf +++ b/utils/conf/beta2.conf @@ -38,6 +38,10 @@ http { location = /50x.html { root html; } + location /abc { + content_by_lua ' ngx.say("this is beta2 server @ location abc") '; + } + } } diff --git a/utils/conf/beta3.conf b/utils/conf/beta3.conf index b595f6d..6d03189 100644 --- a/utils/conf/beta3.conf +++ b/utils/conf/beta3.conf @@ -27,7 +27,7 @@ http { server { listen 8022 backlog=16384; - server_name localhost; + server_name api.weibo.cn; location / { root html/beta3; @@ -37,6 +37,11 @@ http { location = /50x.html { root html; } + location /abc { + content_by_lua ' ngx.say("this is beta3 server @ location abc") '; + } + + } } diff --git a/utils/conf/beta4.conf b/utils/conf/beta4.conf index c393096..cfb4126 100644 --- a/utils/conf/beta4.conf +++ b/utils/conf/beta4.conf @@ -33,6 +33,10 @@ http { location = /50x.html { root html; } + location /abc { + content_by_lua ' ngx.say("this is beta4 server @ location abc") '; + } + } } diff --git a/utils/conf/default.conf b/utils/conf/default.conf new file mode 100644 index 0000000..6de632a --- /dev/null +++ b/utils/conf/default.conf @@ -0,0 +1,35 @@ +server { + listen 8080; + server_name localhost 127.0.0.1; + access_log logs/ip-access.log main; + error_log logs/admin.log error; + location / { + root html; + index index.html index.htm; + } + +# location /nginx_status { +# stub_status on; +# access_log off; +# allow 127.0.0.1; +# } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + set $redis_host '127.0.0.1'; + set $redis_port '6379'; + set $redis_uds '/var/run/redis.sock'; + set $redis_connect_timeout 10000; + set $redis_dbid 0; + + set $redis_pool_size 1000; + set $redis_keepalive_timeout 90000; + + location /ab_admin { + content_by_lua_file '../admin/ab_action.lua'; + } + +} diff --git a/utils/conf/fastcgi.conf b/utils/conf/fastcgi.conf new file mode 100644 index 0000000..ac9ff92 --- /dev/null +++ b/utils/conf/fastcgi.conf @@ -0,0 +1,25 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/utils/conf/fastcgi_params b/utils/conf/fastcgi_params new file mode 100644 index 0000000..71e2c2e --- /dev/null +++ b/utils/conf/fastcgi_params @@ -0,0 +1,24 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/utils/conf/nginx.conf b/utils/conf/nginx.conf index 87c0aaf..2fd2542 100644 --- a/utils/conf/nginx.conf +++ b/utils/conf/nginx.conf @@ -1,137 +1,57 @@ +#user www www; worker_processes auto; +# worker_cpu_affinity auto; # openresty-1.9.15 +worker_rlimit_nofile 102400; -pid logs/nginx-uid.pid; -error_log logs/error.log ; +error_log logs/error.log; +pid logs/nginx.pid; events { - worker_connections 32768; - accept_mutex off; - multi_accept on; + use epoll; + worker_connections 10240; } http { - include mime.types; - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"' - ' $request_time $upstream_response_time'; - access_log logs/access.log main; + server_tokens off; + sendfile on; + tcp_nodelay on; + tcp_nopush on; + keepalive_timeout 0; + charset utf-8; - sendfile on; + include mime.types; + default_type application/json; -# keepalive_timeout 0; -# keepalive_requests 0; + log_format main '[$time_local]`$http_x_up_calling_line_id`"$request"`"$http_user_agent"`$staTus`[$remote_addr]`$http_x_log_uid`"$http_referer"`$request_time`$body_bytes_sent`$http_x_forwarded_proto`$http_x_forwarded_for`$http_host`$http_cookie`$upstream_response_time`xd'; + client_header_buffer_size 4k; + large_client_header_buffers 8 4k; + server_names_hash_bucket_size 128; + client_max_body_size 8m; - upstream stable{ - keepalive 1000; - server localhost:8040; - } + client_header_timeout 30s; + client_body_timeout 30s; + send_timeout 30s; + lingering_close off; - upstream beta1 { - keepalive 1000; - server localhost:8020; - } + gzip on; + gzip_vary on; + gzip_min_length 1000; + gzip_comp_level 6; + gzip_types text/plain text/xml text/css application/javascript application/json; + gzip_http_version 1.0; - upstream beta2 { - keepalive 1000; - server localhost:8021; - } + #index index.html index.shtml index.php; - upstream beta3 { - keepalive 1000; - server 127.1.0.1:8022; -# check interval=3000 rise=2 fall=5 timeout=1000 type=http; -# check_http_send "HEAD / HTTP/1.0\r\n\r\n"; -# check_http_expect_alive http_2xx http_3xx; - } + # include upstream.conf; + include upstream.conf; + include default.conf; + include vhost.conf; - upstream beta4 { - keepalive 1000; - server localhost:8023; - } +# lua_code_cache on; + lua_code_cache off; + lua_package_path "../?.lua;../lib/?.lua;../lib/lua-resty-core/lib/?.lua;;"; + lua_need_request_body on; - lua_code_cache off; - lua_package_path "../lib/?.lua;;"; - lua_shared_dict sysConfig 1m; - lua_shared_dict kv_upstream 10m; - lua_shared_dict rt_locks 100k; - lua_shared_dict up_locks 100k; - - lua_need_request_body on; - - server { - server_name localhost; - listen 8030 backlog=16384; - - set $redis_host '127.0.0.1'; - set $redis_port '6379'; - set $redis_uds '/tmp/redis.sock'; - set $redis_connect_timeout 10000; - set $redis_dbid 0; - - set $redis_pool_size 1000; - set $redis_keepalive_timeout 90000; #(keepalive_time, in ms) - - set $runtime_prefix 'ab:test:runtimeInfo'; - set $policy_prefix 'ab:test:policies'; - - set $default_backend 'stable'; - set $shdict_expire 60; - - set $rt_cache_lock rt_locks; #set name of cache locks, should be same as lua_shared_dict - set $up_cache_lock up_locks; - set $lock_expire 0.001 ; #wait for cache_lock 0.001 seconds - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Connection ""; - proxy_http_version 1.1; - - set $backend $default_backend; - rewrite_by_lua_file '../diversion/diversion.lua'; - - proxy_pass http://$backend; - } - location = /admin/policy/set { - content_by_lua_file '../admin/policy/set.lua'; - } - location = /admin/policy/get { - content_by_lua_file '../admin/policy/get.lua'; - } - location = /admin/policy/del { - content_by_lua_file '../admin/policy/del.lua'; - } - location = /admin/policy/check { - content_by_lua_file '../admin/policy/check.lua'; - } - - location = /admin/runtime/set { - content_by_lua_file '../admin/runtime/set.lua'; - } - location = /admin/runtime/get { - content_by_lua_file '../admin/runtime/get.lua'; - } - location = /admin/runtime/del { - content_by_lua_file '../admin/runtime/del.lua'; - } - - location /nginx_status { - stub_status on; - access_log off; - allow 127.0.0.1; - deny all; - } - - location /status { - check_status; - - access_log off; - allow 127.0.0.1; - deny all; - } - } } diff --git a/utils/conf/redis.conf b/utils/conf/redis.conf index cd898c5..f8154ab 100644 --- a/utils/conf/redis.conf +++ b/utils/conf/redis.conf @@ -33,8 +33,8 @@ port 6379 # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # - unixsocket /tmp/redis.sock - unixsocketperm 766 +unixsocket /var/run/redis.sock +unixsocketperm 766 # Close the connection after a client is idle for N seconds (0 to disable) timeout 0 diff --git a/utils/conf/stable.conf b/utils/conf/stable.conf index 3d87fc5..7c75a43 100644 --- a/utils/conf/stable.conf +++ b/utils/conf/stable.conf @@ -41,5 +41,9 @@ http { root html; } + location /abc { + content_by_lua ' ngx.say("this is stable server @ location abc") '; + } + } } diff --git a/utils/conf/upstream.conf b/utils/conf/upstream.conf new file mode 100644 index 0000000..6766d7b --- /dev/null +++ b/utils/conf/upstream.conf @@ -0,0 +1,24 @@ +upstream beta2 { + server 127.0.0.1:8021 weight=10 fail_timeout=1 max_fails=3; + keepalive 256; +} + +upstream beta3 { + server 127.0.0.1:8022 weight=10 fail_timeout=1 max_fails=3; + keepalive 256; +} + +upstream beta1 { + server 127.0.0.1:8020 weight=1 fail_timeout=10 max_fails=1; + keepalive 1000; +} + +upstream beta4 { + server 127.0.0.1:8023 weight=1 fail_timeout=10 max_fails=1; + keepalive 1000; +} + +upstream stable { + server 127.0.0.1:8040 weight=1 fail_timeout=10 max_fails=1; +} + diff --git a/utils/conf/vhost.conf b/utils/conf/vhost.conf new file mode 100644 index 0000000..8868bf1 --- /dev/null +++ b/utils/conf/vhost.conf @@ -0,0 +1,47 @@ +lua_shared_dict api_root_sysConfig 1m; +lua_shared_dict kv_api_root_upstream 100m; + +lua_shared_dict api_abc_sysConfig 1m; +lua_shared_dict kv_api_abc_upstream 100m; + +server { + listen 8030; + server_name api.weibo.cn mapi.weibo.com; + + access_log logs/vhost_access.log main; + error_log logs/vhost_error.log; + + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + + set $redis_host '127.0.0.1'; + set $redis_port '6379'; + set $redis_uds '/var/run/redis.sock'; + set $redis_connect_timeout 10000; + set $redis_dbid 0; + set $redis_pool_size 1000; + set $redis_keepalive_timeout 90000; + + location ~* /abc/(i|f)/ { + set $hostkey $server_name.abc; + set $sysConfig api_abc_sysConfig; + set $kv_upstream kv_api_abc_upstream; + set $backend 'stable'; + rewrite_by_lua_file '../diversion/diversion.lua'; + proxy_pass http://$backend; + } + + location / { + + error_log logs/vhost_error.log debug; + + set $hostkey $server_name; + set $sysConfig api_root_sysConfig; + set $kv_upstream kv_api_root_upstream; + set $backend 'stable'; + rewrite_by_lua_file '../diversion/diversion.lua'; + proxy_pass http://$backend; + } +} + diff --git a/utils/pytool/cmd.txt b/utils/pytool/cmd.txt new file mode 100644 index 0000000..4ca1542 --- /dev/null +++ b/utils/pytool/cmd.txt @@ -0,0 +1,31 @@ +* dygateway命令脚本说明文档,* 号开头的行是注释,文档前面是接口使用说明 +* 所有命令的参数都不应该为空,以一个 | 号隔开 + +host: 127.0.0.1:8080 + +* del_upstream: stable +* get_upstream: + +* add_upstream: stable| server 10.13.112.54:8020; server 10.13.112.54:8021 backup; keepalive 1000; +* get_upstream: +* +* add_member: stable| 10.13.112.54| 8040| 1| 1| 10 +* get_member: stable +* +* del_member: stable| 10.13.112.54| 8040 +* get_member: stable +* +* setup_member: stable| 1| weight=9| fail_timeout=9| max_fails=9 +* get_member: stable +* + add_policy: {"1":{"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]},"2":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]}} | {"1":{"divtype":"uidappoint","divdata":[{"uidset":[1234,5124,653],"upstream":"beta1"},{"uidset":[3214,652,145],"upstream":"beta2"}]},"2":{"divtype":"iprange","divdata":[{"range":{"start":1111,"end":2222},"upstream":"beta1"},{"range":{"start":3333,"end":4444},"upstream":"beta2"},{"range":{"start":7777,"end":8888},"upstream":"beta3"}]}} + +* del_policy: 1 + get_policy: 1| 2| 3 + + +del_runtime: api.weibo.cn +get_runtime: api.weibo.cn + +set_runtime: api.weibo.cn = 2 +get_runtime: api.weibo.cn diff --git a/utils/pytool/command.py b/utils/pytool/command.py new file mode 100644 index 0000000..2f52330 --- /dev/null +++ b/utils/pytool/command.py @@ -0,0 +1,709 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +import requests, string, time +from xml.parsers.expat import ParserCreate +try: + import json +except: + import simplejson as json + +uri_ab = '/ab_admin' +uri_dyupsc = '/dyupsc_admin' + +def checkStr(arg): + if type(arg) == str: + dat = arg.strip() + if dat and dat != '': + return True + return False + +def checkJson(arg): + if type(arg) == str: + dat = arg.strip() + if dat and json.loads(dat): + return True + return False + +def checkInteger(arg): + num = int(arg) + if num == None: + return False + return True + +def checkPort(arg): + num = int(arg) + if not num or\ + num <= 1 or num >= 65536: + return False + return True + + +def checkServerList(arg): + arg = arg.strip() + srvlist = arg.split(';') + for srv in srvlist: + if srv.strip() == '': + continue + items = srv.split() + if 'server' not in items: + return False + return True + +def checkBool(arg): + if arg and type(arg) == str: + arg = arg.lower() + if arg == 'false' or \ + arg == 'true': + return True + return False + +class add_upstream: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'add_upstream' + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + upstream = args[0] + if not checkStr(upstream): + return False, '1st arg: upstream error' + + servers = args[1] + if not checkStr(servers): + return False, '2ed arg: servers error' + + self.cmd['upstream'] = upstream.strip() + self.cmd['servers'] = servers.strip() + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + req = self.url + '?action=add_upstream' + req += '&' + 'upstream=' + self.cmd['upstream'] + req += '&' + 'servers=' + self.cmd['servers'] + r = requests.get(req) + time.sleep(2) + return r.json() + + +class del_upstream: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'del_upstream' + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + upstream = args[0] + if not checkStr(upstream): + return False, '1st arg: upstream error' + + self.cmd['upstream'] = upstream.strip() + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + req = self.url + '?action=remove_upstream' + req += '&' + 'upstream=' + self.cmd['upstream'] + r = requests.get(req) + + time.sleep(1) + + return r.json() + + +class get_upstream: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'get_upstream' + + def check(self): + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + req = self.url + req += '?action=get_upstreams' + r = requests.get(req) + return r.json() + +class add_member: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'add_member' + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + if(len(args) < 6): + return False, 'num of args is less than 6' + + upstream = args[0] + if not checkStr(upstream): + return False, '1st arg: upstream error' + + host = args[1] + if not checkStr(host): + return False, '2ed arg: member host error' + + port = args[2] + if not checkPort(port): + return False, '3rd arg: member port error' + + weight = args[3] + if not checkInteger(weight): + return False, '4th arg: member weight error' + + maxfails = args[4] + if not checkInteger(maxfails): + return False, '5th arg: member maxfails error' + + failtimeout = args[5] + if not checkInteger(failtimeout): + return False, '5th arg: member failtimeout error' + + cmd = self.cmd + cmd['upstream'] = upstream.strip() + cmd['host'] = host.strip() + cmd['port'] = port.strip() + cmd['weight'] = weight.strip() + cmd['maxfails'] = maxfails.strip() + cmd['failtimeout'] = failtimeout.strip() + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + +# str = "".join(['&%s=%s' % (key, self.cmd[key]) for key in self.cmd]) + cmd = self.cmd + req = self.url + '?action=' + 'add_server' + \ + '&upstream=' + cmd['upstream']+ \ + '&port=' + cmd['port'] + \ + '&ip=' + cmd['host'] + \ + '&weight=' + cmd['weight'] + \ + '&maxfails=' + cmd['maxfails']+ \ + '&failtimeout=' + cmd['failtimeout'] + + print req + time.sleep(2) + + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return r.json() + + req = self.url + '?action=' + 'add_peer' + \ + '&upstream=' + cmd['upstream']+ \ + '&port=' + cmd['port'] + \ + '&ip=' + cmd['host'] + + print req + r = requests.get(req) + time.sleep(1) + return r.json() + +class get_member: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'get_member' + + def check(self): + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + req = self.url + '?action=get_primary_peers' + r = requests.get(req) + ret = r.json() + code = ret['code'] + members = ret['data'] + if code != 200: + return r.json() + +# 发现backup_peers被删掉,仍然会被backup返回 + req = self.url + '?action=get_backup_peers' + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return r.json() + + backup = ret['data'] + for ups in backup: + if len(backup[ups]) > 0: + if members[ups]: + for item in backup[ups]: + item['state'] = 'backup' + members[ups].append(item) + return members + +class del_member: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'del_member' + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + if(len(args) < 3): + return False, 'num of args is less than 6' + + upstream = args[0] + if not checkStr(upstream): + return False, '1st arg: upstream error' + + host = args[1] + if not checkStr(host): + return False, '2ed arg: member host error' + + port = args[2] + if not checkPort(port): + return False, '3rd arg: member port error' + + cmd = self.cmd + cmd['upstream'] = upstream.strip() + cmd['host'] = host.strip() + cmd['port'] = port.strip() + + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + + cmd = self.cmd + req = self.url + '?action=' + 'remove_peer' + \ + '&upstream=' + cmd['upstream']+ \ + '&port=' + cmd['port'] + \ + '&ip=' + cmd['host'] + + r = requests.get(req) + ret = r.json() + code = ret['code'] + members = ret['data'] + if code != 200: + return ret + time.sleep(1) + req = self.url + '?action=' + 'remove_server' + \ + '&upstream=' + cmd['upstream']+ \ + '&port=' + cmd['port'] + \ + '&ip=' + cmd['host'] + + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + members = ret['data'] + time.sleep(1) + return members + +class setup_member: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_dyupsc + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'setup_member' + + def check(self): + cmdline = self.cmdline.strip() + cmd = self.cmd + args = cmdline.split('|') + + if(len(args) < 3): + return False, 'num of args is at least 3' + + upstream = args[0] + if not checkStr(upstream): + return False, '1st arg: upstream error' + + mid = args[1] + if not checkInteger(mid): + return False, '2ed arg: member id error' + + for item in args[2:]: + kv = item.split('=') + if len(kv) != 2: + return False, 'arg '+ item +' invalid' + key = kv[0].strip() + val = kv[1].strip() + if key not in ['weight', 'max_fails', 'fail_timeout']: + return False, 'arg '+ item + ':'+key+' not supported' + cmd[key]=val + + cmd['upstream'] = upstream.strip() + cmd['id'] = mid.strip() + + return True, None + + def echo(self): + print json.dumps(self.cmd, indent=1) + + def run(self): + + cmd = self.cmd + baseurl = self.url + '?upstream=' + cmd['upstream']+ \ + '&id=' + cmd['id'] +\ + '&backup=false' + if cmd['weight']: + req = baseurl + req += '&action=' + 'set_peer_weight' + req += '&value=' + cmd['weight'] + print req + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + time.sleep(1) + + if cmd['max_fails']: + req = baseurl + req += '&action=' + 'set_peer_max_fails' + req += '&value=' + cmd['max_fails'] + print req + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + time.sleep(1) + + if cmd['fail_timeout']: + req = baseurl + req += '&action=' + 'set_peer_fail_timeout' + req += '&value=' + cmd['fail_timeout'] + print req + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + time.sleep(1) + return ret + + +class add_policy: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'policygroup_set' + self.policies = [] + + def check(self): + cmdline = self.cmdline.strip() + cmd = self.cmd + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 policygroup is needed' + + for idx, policy in enumerate(args): + if not checkJson(policy): + return False, 'policygroup ['+ policy +'] index ['+ str(idx) +'] invalid' + self.policies.append(policy) + + return True, None + + def echo(self): + print 'action = add_policy' + for idx, policy in enumerate(self.policies): + print '******policy ', idx, ' ******' + print json.dumps(policy, indent=1) + print '**********************' + + def run(self): + + cmd = self.cmd + baseurl = self.url + '?action=' + cmd['action'] + + for policy in self.policies: + r = requests.post(baseurl, data = policy) + ret = r.json() + code = ret['code'] + print json.dumps(r.json(), indent=1) + if code != 200: + return ret + + return r.json() + + +class get_policy: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'policygroup_get' + self.policygroups = [] + + def check(self): + cmdline = self.cmdline.strip() + cmd = self.cmd + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 policygroup_id is needed' + + for idx, pid in enumerate(args): + if not checkInteger(pid): + return False, 'policygroup_id ['+ pid +'] index ['+ str(idx) +'] invalid' + self.policygroups.append(pid.strip()) + + return True, None + + def echo(self): + print 'action = get_policy' + print 'all the id of policygroups are ', self.policygroups + + def run(self): + + cmd = self.cmd + baseurl = self.url + + policygroups = {} # result + for pgid in self.policygroups: # pgid list + req = baseurl + '?action=' + cmd['action']+ '&policygroupid='+pgid + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + + policygroup = {} + + policies = [] + pidlist = ret['data']['group'] + for idx, pid in enumerate(pidlist): + req = baseurl + '?action=policy_get&policyid='+pid + r = requests.get(req) + ret = r.json() + + policy = {} + if ret['code'] == 200: + policy = ret['data'] + + policy['policy_id'] = pid + pkey = 'step = <'+ str(idx) +'>' + policygroup[pkey] = policy + + pgkey = 'policygroup_id = ['+pgid+']' + policygroups[pgkey] = policygroup + + return policygroups + + +class del_policy: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'policygroup_del' + self.policygroups = [] + + def check(self): + cmdline = self.cmdline.strip() + cmd = self.cmd + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 policygroup_id is needed' + + for idx, pid in enumerate(args): + if not checkInteger(pid): + return False, 'policygroup_id ['+ pid +'] index ['+ str(idx) +'] invalid' + self.policygroups.append(pid.strip()) + + return True, None + + def echo(self): + print 'action = del_policy' + print 'all the id of policygroups are ', self.policygroups + + def run(self): + + cmd = self.cmd + baseurl = self.url+ '?action=' + cmd['action'] + + policygroups = {} # result + for pgid in self.policygroups: # pgid list + req = baseurl + '&policygroupid='+pgid + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + return ret + +class get_runtime: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'runtime_get' + self.hostnames = [] + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 hostname is needed' + + for idx, hostname in enumerate(args): + if not checkStr(hostname): + return False, 'hostname ['+ hostname +'] index ['+ str(idx) +'] invalid' + self.hostnames.append(hostname.strip()) + + return True, None + + def echo(self): + print 'action = get_runtime' + print 'all the hostnames are ', self.hostnames + + def run(self): + + baseurl = self.url+ '?action=' + self.cmd['action'] + runtimes = {} + for hostname in self.hostnames: # pgid list + req = baseurl + '&hostname='+ hostname + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + + runtime = ret['data'] + rkey = '<'+hostname+'>' + runtimes[rkey] = runtime + return runtimes + +class del_runtime: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'runtime_del' + self.hostnames = [] + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 hostname is needed' + + for idx, hostname in enumerate(args): + if not checkStr(hostname): + return False, 'hostname ['+ hostname +'] index ['+ str(idx) +'] invalid' + self.hostnames.append(hostname.strip()) + + return True, None + + def echo(self): + print 'action = del_runtime' + print 'all the hostnames are ', self.hostnames + + def run(self): + + baseurl = self.url+ '?action=' + self.cmd['action'] + runtimes = {} + for hostname in self.hostnames: # pgid list + req = baseurl + '&hostname='+ hostname + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + return ret + +class set_runtime: + def __init__(self, host, cmdline): + self.host = host + self.url = 'http://'+ host + uri_ab + self.cmdline = cmdline + self.cmd = {} + self.cmd['action'] = 'runtime_set' + self.hostnames = {} + + def check(self): + cmdline = self.cmdline.strip() + args = cmdline.split('|') + + if(len(args) < 1): + return False, 'at least 1 pair of is needed' + + for idx, host_pid in enumerate(args): + + kv = host_pid.split('=') + if len(kv) < 2: + return False, 'arg '+ kv + ' invalid' + host = kv[0].strip() + pid = kv[1].strip() + + if not checkStr(host): + return False, 'hostname ['+ host +'] index ['+ str(idx) +'] invalid' + if not checkInteger(pid): + return False, 'policyid ['+ pid +'] index ['+ str(idx) +'] invalid' + self.hostnames[host] = pid + + return True, None + + def echo(self): + print 'action = set_runtime' + print 'all the hostnames are ', self.hostnames + + def run(self): + + baseurl = self.url+ '?action=' + self.cmd['action'] + runtimes = {} + for host in self.hostnames: # pgid list + req = baseurl + '&hostname='+ host + '&policygroupid=' + self.hostnames[host] + r = requests.get(req) + ret = r.json() + code = ret['code'] + if code != 200: + return ret + return ret diff --git a/utils/pytool/pydygateway.py b/utils/pytool/pydygateway.py new file mode 100644 index 0000000..f62ee04 --- /dev/null +++ b/utils/pytool/pydygateway.py @@ -0,0 +1,153 @@ +# -*- coding:utf-8 -*- +#!/usr/bin/env python + +import argparse, requests +import sys, string, time +from xml.parsers.expat import ParserCreate +try: + import json +except: + import simplejson as json + +cmdFileMode = 'cmd file mode' +cmdInteractMode = 'cmd interaction mode' + + +specialArgs = ['host'] +commonArgs = ['add_upstream'] +HOST = None + +def doSpecArg(kvCmd): + key = kvCmd[0].strip() + val = kvCmd[1].strip() + + if str.upper(key) == 'HOST': + global HOST + HOST = val + +def parseCmdFile(filename): + f = open(filename) + # error handle + + cmds = [] + lineN = 0 + for line in f.readlines(): + lineN = lineN + 1 + line = line.strip() + if line == '' or line[0] == '*': + continue + + kvCmd = line.split(':', 1) + if len(kvCmd) < 2: + print 'cmd error for invalid number of args in line NO. ' + str(lineN) + sys.exit() + + if kvCmd[0] in specialArgs: + doSpecArg(kvCmd) + else: + if not HOST: + print 'HOST is not assigned' + sys.exit() + + cmdname = kvCmd[0] + cmdmod = __import__('command') + + cmdclass = getattr(cmdmod, cmdname) + cmdobj = cmdclass(HOST, kvCmd[1]) + valid, err = cmdobj.check() + if not valid: + hint = 'cmd [' + line + '] invalid in line No. ' + str(lineN) + if err: + hint += ' : reason is ' + err + print hint + sys.exit() + + cmds.append(cmdobj) + return cmds + +def doCmdFile(filename): + + cmds = parseCmdFile(filename) + + if not cmds or type(cmds) != list or len(cmds) < 1: + print 'parse cmd file' , filename , 'error, please check the file' + sys.exit() + print '\033[1;34;40m' + print 'Your cmds are listed below:' + for cmdobj in cmds: + print '\033[1;32;40m' + print 'No.', cmds.index(cmdobj), 'cmd:', cmdobj.cmd['action'] + print '\033[1;34;40m' + cmdobj.echo() + print '\033[0m' + + print '\n************************************************' + while(True): + c = raw_input('Are cmds correct? If correct they will be executed immediately[Y/N]') + if c == '' or c == 'y' or c == 'Y': + break + else: + print 'terminate this program and edit your cmd file again' + sys.exit() + + print '\n************************************************' + for cmdobj in cmds: + print '\033[1;32;40m' + print '------------------cmd------------------' + print '\033[1;33;40m' + cmdobj.echo() + + ret = cmdobj.run() + print '\033[1;34;40m' + print '-----------------result-----------------\n' + print json.dumps(ret, indent = 1) + + print '\033[1;31;40m' + print '------------------end------------------' + + c = raw_input('\nIs the result correct? If correct it will be continued[Y/N]') + if c == '' or c == 'y' or c == 'Y': + pass + else: + print 'terminate this program and edit your cmd file again' + sys.exit() + print '\033[0m' + + +def doCmdInteract(): + pass + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='dygateway cmd utility') + parser.add_argument('-s', action="store", dest="filename") + result = parser.parse_args(sys.argv[1:]) + + if result.filename: + print cmdFileMode + print '\n************************************************' + + doCmdFile(result.filename) + + else: + print cmdInteractMode + + +### todo list +# checkPort() exception handle +# file.readlines() 会有中间为空的元素吗 + +### acknowledge +# python 参数处理 +# python 异常处理 & 主动异常 +# python list +# python dict +# python file.readline +# python str is or not null +# python input and rawinput +# python 控制终端颜色 +# python upper case and lower case convert + +### know issues +# dyupsc模块需要有一定时间让各个work能够同步upstream信息 +# ab模块的runtime_set无法获得policy_set(添加策略)返回的policyid,需要进一步的商量 diff --git a/utils/stop-all-nginx.sh b/utils/stop-all-nginx.sh new file mode 100644 index 0000000..f2e3133 --- /dev/null +++ b/utils/stop-all-nginx.sh @@ -0,0 +1,8 @@ +rm *temp -rf + +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/nginx.conf -s stop +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/stable.conf -s stop +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta1.conf -s stop +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta2.conf -s stop +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta3.conf -s stop +/usr/local/nginx/sbin/nginx -p `pwd` -c conf/beta4.conf -s stop