如何发起新 HTTP 请求

OpenResty 最主要的应用场景之一是 API Server,有别于传统 Nginx 的代理转发应用场景,API Server 中心内部有各种复杂的交易流程和判断逻辑,学会高效的与其他 HTTP Server 调用是必备基础。本文将介绍 OpenResty 中两个最常见 HTTP 接口调用方法。

我们先来模拟一个接口场景,一个公共服务专门用来对外提供加了“盐” md5 计算,业务系统调用这个公共服务完成业务逻辑,用来判断请求本身是否合法。

利用 proxy_pass

参考下面示例,利用 proxy_pass 完成 HTTP 接口访问的成熟配置+调用方法。

  1. http {
  2. upstream md5_server{
  3. server 127.0.0.1:81; # ①
  4. keepalive 20; # ②
  5. }
  6. server {
  7. listen 80;
  8. location /test {
  9. content_by_lua_block {
  10. ngx.req.read_body()
  11. local args, err = ngx.req.get_uri_args()
  12. --
  13. local res = ngx.location.capture('/spe_md5',
  14. {
  15. method = ngx.HTTP_POST,
  16. body = args.data
  17. }
  18. )
  19. if 200 ~= res.status then
  20. ngx.exit(res.status)
  21. end
  22. if args.key == res.body then
  23. ngx.say("valid request")
  24. else
  25. ngx.say("invalid request")
  26. end
  27. }
  28. }
  29. location /spe_md5 {
  30. proxy_pass http://md5_server; -- ④
  31. #For HTTP, the proxy_http_version directive should be set to “1.1” and the “Connection”
  32. #header field should be cleared.(from:http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive)
  33. proxy_http_version 1.1;
  34. proxy_set_header Connection "";
  35. }
  36. }
  37. server {
  38. listen 81; --
  39. location /spe_md5 {
  40. content_by_lua_block {
  41. ngx.req.read_body()
  42. local data = ngx.req.get_body_data()
  43. ngx.print(ngx.md5(data .. "*&^%$#$^&kjtrKUYG"))
  44. }
  45. }
  46. }
  47. }

重点说明:
① 上游访问地址清单(可以按需配置不同的权重规则);
② 上游访问长连接,是否开启长连接,对整体性能影响比较大(大家可以实测一下);
③ 接口访问通过 ngx.location.capture 的子查询方式发起;
④ 由于 ngx.location.capture 方式只能是 nginx 自身的子查询,需要借助 proxy_pass 发出 HTTP 连接信号;
⑤ 公共 API 输出服务;

这里大家可以看到,借用 nginx 周边成熟组件力量,为了发起一个 HTTP 请求,我们需要绕好几个弯子,甚至还有可能踩到坑(upstream 中长连接的细节处理),显然没有足够优雅,所以我们继续看下一章节。

利用 cosocket

立马开始我们的新篇章,给大家展示优雅的解决方式。

  1. http {
  2. server {
  3. listen 80;
  4. location /test {
  5. content_by_lua_block {
  6. ngx.req.read_body()
  7. local args, err = ngx.req.get_uri_args()
  8. local http = require "resty.http" --
  9. local httpc = http.new()
  10. local res, err = httpc:request_uri( --
  11. "http://127.0.0.1:81/spe_md5",
  12. {
  13. method = "POST",
  14. body = args.data,
  15. }
  16. )
  17. if 200 ~= res.status then
  18. ngx.exit(res.status)
  19. end
  20. if args.key == res.body then
  21. ngx.say("valid request")
  22. else
  23. ngx.say("invalid request")
  24. end
  25. }
  26. }
  27. }
  28. server {
  29. listen 81;
  30. location /spe_md5 {
  31. content_by_lua_block {
  32. ngx.req.read_body()
  33. local data = ngx.req.get_body_data()
  34. ngx.print(ngx.md5(data .. "*&^%$#$^&kjtrKUYG"))
  35. }
  36. }
  37. }
  38. }

重点解释:
① 引用 resty.http 库资源,它来自 github https://github.com/pintsized/lua-resty-http
② 参考 resty-http 官方 wiki 说明,我们可以知道 request_uri 函数完成了连接池、HTTP 请求等一系列动作。

题外话,为什么这么简单的方法我们还要求助外部开源组件呢?其实我也觉得这个功能太基础了,真的应该集成到 OpenResty 官方包里面,只不过目前官方默认包里还没有。

如果你的内部请求比较少,使用 ngx.location.capture+proxy_pass 的方式还没什么问题。但如果你的请求数量比较多,或者需要频繁的修改上游地址,那么 resty.http就更适合你。

另外 ngx.thread.*resty.http 相互结合也是很不错的玩法,推荐大家有时间研究一下。