背景
Orange 属于流量网关(Api Geteway),项目托管在Github(https://github.com/orlabs/orange)上,目前活跃度较弱(2年未更新)。通常将`orange`和另一个流行的网关项目`Kong`进行比较,其实`orange`大部分组件都是参考`Kong`实现的,但是活跃度远不及`Kong`。
orange和Kong本质上都是OpenResty 应用,Lua语言编写,Lua在葡萄牙语里代表美丽的月亮。Openresty和Lua语言的关系类似JVM虚拟机和Java语言的关系。OpenResty可以理解为一个集成了很多模块的定制版nginx。
本文以orange 0.7.0版本介绍项目的整体架构和原理,最后通过案例介绍如何研发实现一个orange插件。
第一部分 知识准备
1.1 Openresty介绍
Nginx组件已经被广泛应用在web应用架构中,例如:负载均衡、反向代理、代理缓存以及流量限流等场景。Nginx开发则需要C/C++语言,还需要了解底层接口,学习门槛较高。2009年,淘宝的章亦春和王晓哲一起设计了第二代的 OpenResty。在王晓哲的提议下,选择了小而美的脚本语言 lua 进行开发。这就是ngx_lua 模块,并且将Nginx核心、LuaJIT、ngx_lua模块、许多有用的Lua库和常用的第三方Nginx模块组合在一起成为OpenResty。
Lua脚本语言,不需要编译就可以执行,更加灵活。而且 OpenResty 还把 Lua 自身的协程与 Nginx 的事件机制完美结合在一起,优雅地实现了许多其他语言所没有的同步非阻塞编程范式,能够轻松开发出高性能的 Web 应用。
我们知道Nginx的配置文件是文件存储,修改后需要reload。而Lua 有代码热加载特性,不需要重启进程,就能够从磁盘、Redis 或者任何其他地方加载数据,随时替换内存里的代码片段。这就带来了“动态配置”,让 OpenResty 能够永不停机,在微秒、毫秒级别实现配置和业务逻辑的实时更新,比起 Nginx 秒级的重启是一个极大的进步。
OpenResty 还选用了LuaJIT作为 Lua 语言的运行时(Runtime),提升效率。LuaJIT 是一个高效的 Lua 虚拟机,支持 JIT(Just In Time)技术,可以把 Lua 代码即时编译成“本地机器码”,这样就消除了脚本语言解释运行的劣势,让Lua 脚本跑得和原生 C 代码一样快。
1.2 Openresty阶段式处理
Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log。
基于Nginx底层的阶段处理,OpenResty 也使用“流水线”来处理 HTTP 请求。Nginx 的处理流水线是由一个个 C 模块组成的,只能在静态文件里配置,开发困难,配置麻烦(相对而言)。而 OpenResty 的处理流水线则是由一个个的 Lua 脚本组成的,不仅可以从磁盘上加载,也可以从Redis、MySQL 里加载,而且编写、调试的过程非常方便快捷。
Nginx 把一个请求分成了很多阶段,这样第三方模块就可以根据自己行为,挂载到不同阶段进行处理达到目的。OpenResty 也应用了同样的特性。所不同的是,OpenResty 挂载的是我们编写的 Lua 代码。OpenResty 的阶段,比起 Nginx,OpenResty 的阶段更注重对 HTTP 请求响应报文的加工和处理。

OpenResty 里有几个阶段与 Nginx 是相同的,比如 rewrite、access、content、filter,这些都是标准的 HTTP 处理。
在这几个阶段里可以用“xxx_by_lua”指令嵌入 Lua 代码,执行重定向跳转、访问控制、产生响应、负载均衡、过滤报文等功能。因为 Lua 的脚本语言特性,不用考虑内存分配、资源回收释放等底层的细节问题,可以专注于编写非常复杂的业务逻辑,比 C 模块的开发效率高很多,即易于扩展又易于维护。
第二部分
版本是一个重构版本, 着重为了解决之前版本在有大量规则配置时性能损耗的问题。 基本的设计思路是将原来的规则细分成两层, 第一层叫做selector, 用于将流量进行第一步划分, 在进入某个selector后才按照之前的设计进行规则匹配, 匹配到后进行相关处理。
https://lengrongfu.github.io/2019/05/21/orange-%E5%8E%9F%E7%90%86/
https://book.aikaiyuan.com/openresty/understanding-orange.html
https://zhuanlan.zhihu.com/p/67481992
网关优化项目
https://github.com/starjiang/xorange
orange运行后根据nginx.conf文件中location配置来进行顺序匹配流量。
1 | location / { |

第一部分 orange 中设计概念
Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。按照它们执行时的先后顺序,依次是 rewrite 阶段、access 阶段以及 content 阶段(后面我们还有机会见到其他更多的处理阶段)。
orange 缓存
在nginx.conf文件中:
1 | lua_code_cache on; |
这些配置是插件缓存数据。
orange中数据持久化
orange中插件基本构成
选择器(selector)
规则(rule)
第二部分 orange的启动过程
2.1
1 | init_by_lua_block { |
第三部分 orange中的插件
第四部分 如何开发一个orange插件
通常一个插件的代码分为两个部分:后端和前端。
需求分析
业务逻辑介绍
插件对过滤后的目标流量访问的接口进行验签认证(SignatureAuth)。
- 插件支持多用户,在实际线上环境,对于一个借口,通常是多用户需要访问该接口。就需要我们针对不同用户分配密钥(accessKey);
- 插件支持用户将认证字段放在header还是query中;
- 插件支持用户自由添加字段;
客户端包装认证参数
- 传入参数:accessKey,accessSecret
- 生成参数
- paramStr:method=akauth&client=$UUID&rand=Math.rand()
- timeStr:String.valueOf(System.currentTimeMillis()/1000)
- sginStr:accessKey+timeStr+paramStr
- 签名计算:signature=base64(HmacSHA1.init(accessSecret).doFinal(signStr))
- 传递参数:accessKey,timeStr,paramStr,signature
参数放在header中。新增三个字段,分别是:timestamp、sign、appName
其中timestamp 作为判断密钥的有效期。
服务端转发认证参数
- 接收参数:accessKey,timeStr,paramStr,signature
- 请求 认证服务接口
后端
后端代码位于:orange/pligins中,在源码中创建新增插件名称命名的子目录,例如:signature_auth_header。
每个插件的子目录中约定必须有两个文件(api.lua、handler.lua):
1 | signature_auth_header/ |
其中
下面的函数可以从流量请求中提取header表。
1 | local headers = ngx.req.get_headers() |
前端
插件前后端使用经典MVC模式。前端代码文件在dashboad中,目录结构如下:
1 | routes |
在views中主页中增加插件:
1 | dashboard\views\common\left_nav.html |
代码:
1 | <li id="nav-signature-auth-header"> |
最后提交git的清单如下:
1 |
参考文献及资料
[1] Orange官网,链接:http://orange.sumory.com/
[2] Orange网关官网docker,链接:https://hub.docker.com/r/syhily/orange
[3] agentzh 的 Nginx 教程(版本 2020.03.19),链接:http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
[4] OpenResty最佳实践,链接:https://moonbingbing.gitbooks.io/openresty-best-practices/content/
