• ngx_lua学习笔记 -- capture + proxy 实现httpclient


    题注

    最近我在学习nginx的lua插件,发现结合nginx的异步io和lua的流程控制能力,还是有很丰富的想象空间的:几乎所有常见的http请求的处理逻辑都能搞定,诸如查查数据库,访问一下memcache,读写一下本地文件等,都不在话下。恰好我正在研究一个第三方http服务的调用api,就产生了将其移植到lua上的想法。

    其中涉及到了构造https请求的需求。我先采用ngx.socket.tcp()自己构造了一个http格式的请求,测试结果为可用,然后考虑能不能找到封装好了的httpclient。放狗搜索了一番后,只找到了这个库:lua-resty-http (https://github.com/liseen/lua-resty-http)。无奈下载下来后发现其不支持https,遂fork并修改了一番(https://github.com/kyriosli/lua-resty-http),加上了https支持。

    到此貌似整个移植过程就完了?NO,nginx本身就是玩http的,为啥我们还要自己构造http请求,解析请求结果,管理连接复用呢?想到这一层,我又把眼光转到了nginx自带的api上

    原理

    ngx_lua本身不提供httpclient功能,但提供了ngx.location.capture接口。这个接口可以发送一个子请求(subrequest),并获取子请求的响应结果。子请求可以调用lua,或者返回文件,甚至通过proxy_pass访问另一个地址。

    关键就在这里!如果我们使用proxy_pass机制结合subrequest,不就能实现http接口请求了吗?

    且慢!

    一般来说,httpclient要求能够指定method,scheme,域名,端口,请求头,以及数据。但默认的subrequest将继承所有当前请求的信息。好在ngx.location.capture接口支持指定method和data。那么只要搞定scheme,域名,端口,请求头就可以了。

    实现

    使用过nginx的proxy_pass的同学都知道,proxy_pass支持从变量中获取值(如常见的proxy_pass $scheme://xxx$request_uri;)。而rewrite能够利用捕获组设置变量,所以,我们可以在nginx的配置文件中加入这么一条location规则:

    location /proxy/ {
         internal;
        rewrite ^/proxy/(https?)/([^/]+)/(d+)/(.*)     /$4 break;
        proxy_pass      $1://$2:$3;
    }
    • 第一行,指定规则为internal规则,防止外部请求命中此规则
    • 第二行,使用rewrite将地址中的scheme,host,port匹配出来;使用break终止匹配,不再检查其它location
    • 第三行,使用proxy_pass将scheme,host,port拼装成服务器地址

    这样,我们要请求如https://www.example.com:443/foo/bar时,只需要在lua代码中构造一个子请求,地址为/proxy/https/www.example.com/443/foo/bar即可。

    接下来就是header问题,通过ngx.req.set_header可以在lua中添加需要的请求头。

    由于header的继承性,我们需要考虑来自客户端的哪些header不能被传递到远端接口中:

    • Host: 因为proxy_pass默认会重写host,所以无需处理(在常规的proxy_pass中,可能需要使用proxy_set_header命令修正Host头)
    • Content-Type: 如果来自客户端的请求类型为POST,需要将该头去掉
    • Content-Length: 同上
    • Accept-Encoding: 绝大多数时候,浏览器接受gzip, deflate格式的压缩,如果远端接口启用了压缩,此头将导致返回的数据格式被压缩过,从而lua无法正确处理。因此需要去掉此头
    • Cookie: 涉及到隐私问题,所以这个头也需要去掉

     通过ngx.req.clear_header可以将需要去掉的头去掉。注意为了不影响后续处理,所有被去掉/改写的头应该被恢复,所有被添加的头应该被去掉。

    下面是一个简单的示例代码:

    -- POST https://www.example.com:443/foo/bar?hello=world
    
    ngx.req.set_header("Content-Type", "application/json;charset=utf8");
    ngx.req.set_header("Accept", "application/json");
    
    local res = ngx.location.capture('/proxy/https/www.example.com/443/foo/bar', {
        method = ngx.HTTP_POST,
        body = body,
        args = {hello = 'world'}
    });
  • 相关阅读:
    【矩阵乘法优化dp】[Codeforces 621E] Wet Shark and Blocks
    【2016常州一中夏令营Day7】
    【2016常州一中夏令营Day6】
    【2016常州一中夏令营Day5】
    【2016常州一中夏令营Day4】
    【2016常州一中夏令营Day3】
    【2016常州一中夏令营Day2】
    Aiopr的中文意思
    Bloom filter
    redis4.0.2集群搭建
  • 原文地址:https://www.cnblogs.com/kyrios/p/ngx_lua-httpclient-using-capture-and-proxy.html
Copyright © 2020-2023  润新知