ngx_lua 中访问远方 HTTP 服务的两种推荐的做法 (Was Re: [openresty:157] 42分钟,乱入 OpenResty 手册!-))

827 views
Skip to first unread message

agentzh

unread,
Mar 10, 2012, 4:16:33 AM3/10/12
to open...@googlegroups.com
On Sat, Mar 10, 2012 at 10:40 AM, Zoom.Quiet <zoom....@gmail.com> wrote:
在 2012年3月10日 上午10:36,lhmwzy <lhm...@gmail.com> 写道:
> 尽量不要破坏了openresty本身的非阻塞

- 应该的,但是怎么避免?
进一步的:
   - 哪些操作是阻塞的?
   - 怎么进行非阻塞化?
   - 怎么测试真正异步了?
...
这都是乱入后,需要直面的问题,,,


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

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

    resolver 114.114.114.114;

    location = /baidu {
        internal;
        proxy_pass http://www.baidu.com/s?wd=$arg_query;
    }
    
    location = /test { 
        content_by_lua '
            local res =
                ngx.location.capture("/baidu", { args = { query = "openresty" }})
            if res.status ~= 200 then
                ngx.say("failed to query baidu: ", res.status, ": ", res.body)
                return
            end
            ngx.say(res.body)
        ';
    }

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

2. 使用 cosocket API 访问之:

    resolver 114.114.114.114;

    location = /test {
        content_by_lua '
            local sock = ngx.socket.tcp()
            sock:settimeout(1000)
            local ok, err = sock:connect("www.baidu.com", 80)
            if not ok then
                ngx.say("failed to connect to baidu: ", err)
                return
            end
            local req = "GET /s?wd=openresty HTTP/1.0\\r\\nHost: www.baidu.com\\r\\n\\r\\n"
            sock:send(req)
            local read_headers = sock:receiveuntil("\\r\\n\\r\\n")
            headers, err = read_headers()
            if not headers then
                ngx.say("failed to read response headers: ", err)
                return
            end
            local body, err = sock:receive("*a")
            if not body then
                ngx.say("failed to read response body: ", err)
                return
            end
            ngx.say(body)
        ';
    }

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

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

Regards,
-agentzh

Zoom.Quiet

unread,
Mar 10, 2012, 4:50:01 AM3/10/12
to open...@googlegroups.com

- 謝謝! 关键性建议吼!!!!
- inter-proxy 的方式,在固定的外部上游厂商时,方便
- 一般都是需要动态访问的,后一种,可能就常见了

其实,node 发展的这么快,就是因为大家可以快速完成一个npm 包,发布出来满足自个儿的需求
- 但是, lua-resty-* 的模块,是用 lua 实现的吼
- 这样就很方便大家来汇聚
- 不过,要参考什么接口规范,才能进入 openresty 大包呢?
- 推荐什么样的开发调试环境/界面/流程?
类似 http://readthedocs.org/docs/chaos2openresty/en/latest/ch01/try.html#id7
展示一下舒服的工作界面,以便大家也深迷进来吼,,


> Regards,
> -agentzh


--
人生苦短, Pythonic! 冗余不做,日子甭过!备份不做,十恶不赦!
俺: http://about.me/zoom.quiet
文字协议: http://creativecommons.org/licenses/by-sa/2.5/cn/

agentzh

unread,
Mar 10, 2012, 5:06:16 AM3/10/12
to open...@googlegroups.com
On Sat, Mar 10, 2012 at 5:50 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
- inter-proxy 的方式,在固定的外部上游厂商时,方便
- 一般都是需要动态访问的,后一种,可能就常见了


其实 ngx_proxy 也可以支持动态目标地址,别忘了 proxy_pass 配置指令支持 nginx 变量作为值。只要正确配置了 resolver 指令就行了。

其实,node 发展的这么快,就是因为大家可以快速完成一个npm 包,发布出来满足自个儿的需求
- 但是, lua-resty-* 的模块,是用 lua 实现的吼
- 这样就很方便大家来汇聚
- 不过,要参考什么接口规范,才能进入 openresty 大包呢?

现有的 lua-resty-* 模块的实现代码、测试集以及文档便是一种非正式的规范,呵呵 :)

其实 Lua 世界已经有像 luarocks 这样的包管理系统可供使用了。

- 推荐什么样的开发调试环境/界面/流程?
类似 http://readthedocs.org/docs/chaos2openresty/en/latest/ch01/try.html#id7
展示一下舒服的工作界面,以便大家也深迷进来吼,,


我自己的开发/测试工具链是 vim + Test::Nginx + etcproxy + mockeagain. 这些东西都是开源的,也足够简单 :)

Regards,
-agentzh

Zoom.Quiet

unread,
Mar 10, 2012, 5:20:18 AM3/10/12
to open...@googlegroups.com
嗯嗯嗯?! 怎么同一线索突然不在 gmail 中聚合了?!

- 嗯嗯嗯,但是,组合到舒服,有个过程的,
- 具体展示一下真实的使用场景,直觉体验后,就容易入手
ps:
俺问的是 lua-resy-* 的开发流程:
- 是否每次修订,都需要重新加载到 openresty 中再重启 nginx?!
- 俺期待种直觉的脚本开发流程:
+ 先将空的合法组件,加入 openresty (具体怎么来?)
+ 启动 nginx
+ 修订 lua-resty-* 的 lua代码
^ -> 跑测试用例脚本
| |
+---------/ 根据日志反馈,继续

是也乎!?

agentzh

unread,
Mar 10, 2012, 5:35:49 AM3/10/12
to open...@googlegroups.com
On Sat, Mar 10, 2012 at 6:20 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
ps:
俺问的是 lua-resy-* 的开发流程:
- 是否每次修订,都需要重新加载到 openresty 中再重启 nginx?!

我记得 smallfish 先前已经回答过这个问题了。在开发过程中,禁用 lua_code_cache 配置指令之后,就不必在每次修改外部文件中的 Lua 代码后都重启 nginx:


请不要重复提问,谢谢。

Best regards,
-agentzh

Zoom.Quiet

unread,
Mar 10, 2012, 5:43:00 AM3/10/12
to open...@googlegroups.com
在 2012年3月10日 下午6:35,agentzh <age...@gmail.com> 写道:
> On Sat, Mar 10, 2012 at 6:20 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
>>
>> ps:
>> 俺问的是 lua-resy-* 的开发流程:
>> - 是否每次修订,都需要重新加载到 openresty 中再重启 nginx?!
>
>
> 我记得 smallfish 先前已经回答过这个问题了。在开发过程中,禁用 lua_code_cache 配置指令之后,就不必在每次修改外部文件中的

- 这个記得的,
- 意思是 lua-resty-* 的模块,其实就是正常的 业务模块
- 可以先用标准的业务逻辑开发形式来调试
- 基本靠谱后,封装成 nginx 模块,进行加载?

> Lua 代码后都重启 nginx:
>
> http://wiki.nginx.org/HttpLuaModule#lua_code_cache
>
> 请不要重复提问,谢谢。
>

...

Simon

unread,
Mar 12, 2012, 4:22:03 AM3/12/12
to openresty
目前还是用第一种比较方便。

第一种方式还支持ssl,不过不能解压服务器端返回的gzip的内容,需要加个header。

另外访问一个域名下的多个url会麻烦点,是不是只能这样:proxy_pass $domain/$uri;

On 3月10日, 下午5时16分, agentzh <agen...@gmail.com> wrote:


> On Sat, Mar 10, 2012 at 10:40 AM, Zoom.Quiet <zoom.qu...@gmail.com> wrote:
> > 在 2012年3月10日 上午10:36,lhmwzy <lhm...@gmail.com> 写道:
> > > 尽量不要破坏了openresty本身的非阻塞
>
> > - 应该的,但是怎么避免?
> > 进一步的:
> > - 哪些操作是阻塞的?
> > - 怎么进行非阻塞化?
> > - 怎么测试真正异步了?
> > ...
> > 这都是乱入后,需要直面的问题,,,
>
> 非阻塞访问远方 http 服务有两种做法。下面以从 ngx_lua 中访问百度搜索为例,演示一下这两种做法:
>
> 1. 使用 nginx 子请求 + ngx_proxy 模块:
>
> resolver 114.114.114.114;
>
> location = /baidu {
> internal;

> proxy_passhttp://www.baidu.com/s?wd=$arg_query;

> lua-resty-mysql 库一样(见https://github.com/agentzh/lua-resty-memcached,还有https://github.com/agentzh/lua-resty-redis以及https://github.com/agentzh/lua-resty-mysql

Zoom.Quiet

unread,
Mar 16, 2012, 5:40:12 AM3/16/12
to open...@googlegroups.com
在 2012年3月10日 下午5:16,agentzh <age...@gmail.com> 写道:

先尝试使用第一种内部代理:
- 不断的出错:
HTTPConnectionPool(host='localhost', port=9090): Max retries exceeded
with url: /=/pchk

配置:
location ~ ^/=/(\w+) {
content_by_lua_file conf/lua/$1.lua;
lua_code_cache off;
}
...
location = /pish {
internal ;
proxy_pass http://open.pc120.com/phish/?$arg_query ;
}
...

pchk.lua:
...
local genURL = KSC.genValidUrl(args.uri)
local res = ngx.location.capture("/pish?" .. genURL)


if res.status ~= 200 then

ngx.say("failed to query KSC: ", res.status, ": ", res.body)
return
end
html = res.body
ngx.say(res.body)

问题:
- 如果我可以组合成合法的参数 url 就可以不使用 { args = {}} 模式吧?
- 不过,这样一来是怎么和 location 配合呢?
- 比如,俺内部代理的是 http://open.pc120.com/phish/?a=XXX&b=YYY
- 那俺使用 ngx.location.capture 请求的是
/phish/?a=XXX&b=YYY
or
/phish/a=XXX&b=YYY
- 这种内部 url 获取处理中,怎么追查是哪里出问题了?

location = /pish/ {
internal ;
content_by_lua '
ngx.say(ngx.req.get_body_data)
';
proxy_pass http://open.pc120.com/phish/?$arg_query ;
}
这样加临时调试代码,也没有输出,,,

> --
> 邮件自: 列表“openresty”,专用于技术讨论!
> 发言: 请发邮件到 open...@googlegroups.com
> 退订: 请发邮件至 openresty+...@googlegroups.com
> 详情: http://groups.google.com/group/openresty
> 官网: http://openresty.org/
> 仓库: https://github.com/agentzh/ngx_openresty
> 建议: 提问的智慧 http://wiki.woodpecker.org.cn/moin/AskForHelp

Zoom.Quiet

unread,
Mar 16, 2012, 5:53:54 AM3/16/12
to open...@googlegroups.com
使用 angentzh 提供的示范,则 error:
no resolver defined to resolve www.baidu.com, client: 127.0.0.1,
server: localhost, request: "POST /=/test HTTP/1.1", subrequest:
"/baidu", host: "localhost:9090"

- 是版本问题?需要俺使用更新的稳定版本 openresty?

agentzh

unread,
Mar 16, 2012, 6:01:00 AM3/16/12
to open...@googlegroups.com
On Fri, Mar 16, 2012 at 5:40 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
先尝试使用第一种内部代理:
- 不断的出错:
HTTPConnectionPool(host='localhost', port=9090): Max retries exceeded
with url: /=/pchk


这条错误消息是
 
配置:
   location ~ ^/=/(\w+) {
       content_by_lua_file conf/lua/$1.lua;
       lua_code_cache off;
       }
...
   location = /pish {
       internal ;
       proxy_pass http://open.pc120.com/phish/?$arg_query ;
   }

你这里需要对 $arg_query 进行 URI 反转义吧?否则这个例子很难正常工作。比如:

    set_unescape_uri $query $arg_query;
    proxy_pass http://open.pc120.com/phish/?$query ;

 
...

pchk.lua:
...
   local genURL = KSC.genValidUrl(args.uri)
   local res = ngx.location.capture("/pish?" .. genURL)
   if res.status ~= 200 then
       ngx.say("failed to query KSC: ", res.status, ": ", res.body)
       return
   end
   html = res.body
   ngx.say(res.body)

问题:
- 如果我可以组合成合法的参数 url 就可以不使用 { args = {}} 模式吧?

是的。
 
- 不过,这样一来是怎么和 location 配合呢?

该怎么配置,还是怎么配置。{ args = {...} } 只是自动帮你生成查询字符串罢了。

 - 比如,俺内部代理的是 http://open.pc120.com/phish/?a=XXX&b=YYY
 - 那俺使用 ngx.location.capture 请求的是
   /phish/?a=XXX&b=YYY
   or
   /phish/a=XXX&b=YYY
- 这种内部 url 获取处理中,怎么追查是哪里出问题了?

   location = /pish/ {
       internal ;
       content_by_lua '
         ngx.say(ngx.req.get_body_data)
       ';
       proxy_pass http://open.pc120.com/phish/?$arg_query ;
   }
   这样加临时调试代码,也没有输出,,,


一个 Nginx location 中只能有一个 Nginx 模块注册 content 处理程序,所以在这个 location 中,如果你用了 proxy_pass 就不能用 content_by_lua,而如果用了 content_by_lua 就不能用 proxy_pass. 因为它们都注册的是 content handler (“内容处理程序”)。在我的《Nginx 配置指令的执行顺序(五)》教程中对此有详细的讨论:

http://agentzh.org/misc/nginx/agentzh-nginx-tutorials-zhcn.html#02-NginxDirectiveExecOrder05

Regards,
-agentzh

agentzh

unread,
Mar 16, 2012, 6:04:11 AM3/16/12
to open...@googlegroups.com
On Fri, Mar 16, 2012 at 5:53 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
在 2012年3月16日 下午5:40,Zoom.Quiet <zoom....@gmail.com> 写道:
> 在 2012年3月10日 下午5:16,agentzh <age...@gmail.com> 写道:
>> On Sat, Mar 10, 2012 at 10:40 AM, Zoom.Quiet >> 1. 使用 nginx 子请求 + ngx_proxy 模块:

>>
>>     resolver 114.114.114.114;
>>
>>     location = /baidu {
>>         internal;
>>         proxy_pass http://www.baidu.com/s?wd=$arg_query;
>>     }
>>
>>     location = /test {
>>         content_by_lua '
>>             local res =
>>                 ngx.location.capture("/baidu", { args = { query =
>> "openresty" }})
>>             if res.status ~= 200 then
>>                 ngx.say("failed to query baidu: ", res.status, ": ",
>> res.body)
>>                 return
>>             end
>>             ngx.say(res.body)
>>         ';
>>     }
>>
>
使用 angentzh 提供的示范,则 error:
no resolver defined to resolve www.baidu.com, client: 127.0.0.1,
server: localhost, request: "POST /=/test HTTP/1.1", subrequest:
"/baidu", host: "localhost:9090"

- 是版本问题?需要俺使用更新的稳定版本 openresty?


明显是因为你漏了我的示例代码中的下面这一行:

    resolver 114.114.114.114;

咱能直接复制粘贴么?上面的 Nginx 出错信息也指示你未配置 resolver 指令:

http://wiki.nginx.org/HttpCoreModule#resolver

Regards,
-agentzh

Zoom.Quiet

unread,
Mar 16, 2012, 6:18:32 AM3/16/12
to open...@googlegroups.com

- FT! 俺想 DNS 当前可用就没有配置吼,,
- 不明白的代码就没抄,,,
看来都有深意,先用起来,再理解,,,
- 是为了防止各种内部网络的乱解析?

> Regards,
> -agentzh

agentzh

unread,
Mar 16, 2012, 6:33:22 AM3/16/12
to open...@googlegroups.com

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

Regards,
-agentzh

Zoom.Quiet

unread,
Mar 16, 2012, 6:35:19 AM3/16/12
to open...@googlegroups.com

- 这真心明白,只是怎么可以观察到 proxy_pass 真实进行请求的 url ?
- 以便明确lua 的处理是否正确?
- 使用了 114.114.114.114 后
- 已经可以正确获取内部 url 的返回
- 但是,一直报参数错误,不论俺自个儿拼,还是 自动生成 都是同样的服务端錯誤
{"success":0,"errno":-7,"msg":"appkey,q,sign,timestamp"}
- 这是金山云安全的正确反馈
- 说明缺少几个参数
- 其实,现在这种组织
- 将 location 当成了模块的显式界面,非常明确
- 但是, ngx.location.capture 发出的 url
- 或是 proxy_pass 真正处理的 url 全貌是什么,从哪儿获取,以便修正?


>>    location = /pish/ {
>>        internal ;
>>        content_by_lua '
>>          ngx.say(ngx.req.get_body_data)
>>        ';
>>        proxy_pass http://open.pc120.com/phish/?$arg_query ;
>>    }
>>    这样加临时调试代码,也没有输出,,,
>>
>
> 一个 Nginx location 中只能有一个 Nginx 模块注册 content 处理程序,所以在这个 location 中,如果你用了
> proxy_pass 就不能用 content_by_lua,而如果用了 content_by_lua 就不能用 proxy_pass.
> 因为它们都注册的是 content handler (“内容处理程序”)。在我的《Nginx 配置指令的执行顺序(五)》教程中对此有详细的讨论:
>

- 是也乎,是也乎,又忘記了,看来要形成条件反射,对于不同阶段的顺序


> http://agentzh.org/misc/nginx/agentzh-nginx-tutorials-zhcn.html#02-NginxDirectiveExecOrder05
>
> Regards,
> -agentzh

agentzh

unread,
Mar 16, 2012, 6:49:26 AM3/16/12
to open...@googlegroups.com
On Fri, Mar 16, 2012 at 6:35 PM, Zoom.Quiet <zoom....@gmail.com> wrote:
>
> - 这真心明白,只是怎么可以观察到  proxy_pass 真实进行请求的 url ?
> - 以便明确lua 的处理是否正确?

有两种方法:

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

   nc -l 1978

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

   proxy_pass http://127.0.0.1:1978/phish/?$args

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

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

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

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

Regards,
-agentzh
Reply all
Reply to author
Forward
0 new messages