需求

某系统,需在国外建立完全独立的一套业务。且国内已有用户还需继续维护。
由于带宽、响应时间等原因,通过db主从的方式全球同步数据存在一系列问题。

问题

  • 放在前端的nginx不能判断用户归属国家,判断逻辑太重,nginx不适合做这个
  • nginx后端的backend有自己的逻辑判断用户归属,用返回的status code来标识

解决方案

  • nginx + lua + subrequest
  • 要在content阶段完成
  • 需在rewrite阶段做一点处理
  • GET和POST要区分对待
  • POST的body和GET的args、cookie等处理
  • nginx根据后端的不同返回status code,进行逻辑判断,是直接返回给client还是重新proxy到其他国家

为什么是content阶段

前期找了很多方案,目标就是在nginx upstream到后端的时候,将后端的响应拦截下来。然后根据响应的status code加以区分对待。
上面响应二字说明了问题。
引用网上找来的关于content阶段的说明,nginx 在content阶段生成产生响应,并返回给client。
所以猜想着在这个处理阶段,拦截下来后端的status code。然后过完我们自己的逻辑之后再次进行proxy或者返回给client。(没错,就是,只因水平太菜^_^)

为什么GET和POST要区分对待

GET所有args都在URI中体现。而POST有body的概念,默认做转发等逻辑时是不带这个body的。
这里有一个指令需要注意,ngx.req.read_body
官方解释是Reads the client request body synchronously without blocking the Nginx event loop.
我理解为需要做一个的动作,才会将client POST的body取到。
当然如果使用lua_need_request_body或者类似module也可以实现。
读取body要使用的函数是ngx.req.get_post_args
官方document的解释是Returns a Lua table holding all the current request POST query arguments
这里还有一句叫做Call ngx.req.read_body to read the request body first or turn on the lua_need_request_body directive to avoid errors
对应了上面使用ngx.req.read_body的原因。

subrequest

关于subrequest直接看官方doc解释
Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or any other nginx C modules like ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle, and even ngx_lua itself and etc etc etc.
几个关键字,non-blocking, internal, to other locations, ngx_proxy,这些都符合我们的目标。原文下面还有一段话,Everything works internally, efficiently, on the C level.
subrequest的返回值是一个lua的table
Returns a Lua table with three slots (res.status, res.header, res.body, and res.truncated).
只看字面意思就知道了。注意这里res.header也是一个table,所以处理的时候需要loop
处理起来就差不多像下面的样子

1
2
args = ngx.req.get_body_data()
res = ngx.location.capture("/fooPost" .. ngx.var.request_uri,{ method = ngx.HTTP_POST , body = args })

注意request_uriuri的区别,不细说了。
剩下的就是判断后端返回status code、各种if判断了。

至于GET请求,直接搞request_uri就可以了。

1
2
args = ngx.req.get_body_data()
res = ngx.location.capture("/fooPost" .. ngx.var.request_uri)

args什么的,直接都带过去了。

GET和POST共同要处理就是header,上面说了是一个table,使用循环处理一下就可以了。
lua里面的for语法和其他脚本语言没什么区别

1
2
3
for k, v in pairs(res.header) do
    ngx.header[k] = v
end

这里还有一个问题,如何将响应的body送回给client/backend。
我在官方doc中没找到对应的说明,但从各种例子看,直接使用ngx.print即可

1
ngx.print(res.body)

为什么rewrite阶段也需要介入

上文我们看到,subrequest是打到/fooPost这个location的。
为什么需要另起一个location,答案是避免死循环,例如在/启用content_by_lua的逻辑,极可能引起死循环。这部分需要看下nginx如何处理多个location。
那我原来的request_uri是/login,打到backend变成了/fooPost/login,肯定就要使用rewrite了。

1
rewrite ^/fooPost(.*) $1 break;

说到这里就差不多了。总之罗里罗嗦的说了一堆,连着思路带着实现都扔了出来。
看官自己理解吧^_^

-EOF-