OpenResty简介及学习笔记

摘要

OpenResty系列文章之一。

前些日子做的一些研究,期间收获了一些对web后台应用的理解,nginx运行机理的部分理解。

本篇包括简介、部分模块的介绍。

简介

OpenResty简介及学习笔记_nginx

OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器。它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。

OpenResty简介及学习笔记_nginx_02

通过众多进行良好设计的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员可以使用Lua 编程语言对 Nginx 核心以及现有的各种Nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。

OpenResty 致力于将你的服务器端应用完全运行于 Nginx 服务器中,充分利用 Nginx 的事件模型来进行非阻塞 I/O 通信。不仅仅是和 HTTP 客户端间的网络通信是非阻塞的,与MySQL、PostgreSQL、Memcached、以及 Redis 等众多远方后端之间的网络通信也是非阻塞的。

因为 OpenResty 软件包的维护者也是其中打包的许多 Nginx 模块的作者,所以 OpenResty 可以确保所包含的所有组件可以可靠地协同工作。

OpenResty简介及学习笔记_lua_03

作者简介:

agentzh,本名章亦春,现任 CloudFare 系统工程师,主要是 Nginx 和 OpenResty 开发,是一名快乐的程序员,现定居美国旧金山。曾经在北京的时候供职于 Yahoo!中国以及淘宝(阿里巴巴)。

教程:​​agentzh 的 Nginx 教程​

其他内容可以参看: ​​OpenResty简介​​​,​​OpenResty 作者章亦春访谈实录​

一、OpenResty综述





​OpenResty®​​通过 Lua 扩展 NGINX 实现的可伸缩的 Web 平台

先扔三个网站:


首先,​​OpenResty官网!​

还有Github:​​OpenResty​

为数不多的书籍:​​OpenResty最佳实践​​(Lua语言入门也可以全靠它)

下图给出的是Lua Nginx Module中各指令的执行顺序

OpenResty简介及学习笔记_redis_04


  • set_by_lua: 流程分支处理判断变量初始化
  • rewrite_by_lua: 转发、重定向、缓存等功能(例如特定请求代理到外网)
  • access_by_lua: IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙)
  • content_by_lua: 内容生成
  • header_filter_by_lua: 应答 HTTP 过滤处理(例如添加头部信息)
  • body_filter_by_lua: 应答 BODY 过滤处理(例如完成应答内容统一成大写)
  • log_by_lua: 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)

实际上我们只使用其中一个阶段 content_by_lua,也可以完成所有的处理。但这样做,会让我们的代码比较臃肿,越到后期越发难以维护。把我们的逻辑放在不同阶段,分工明确,代码独立,后期发力可以有很多有意思的玩法。

二、指令说明:

*_by_lua < lua-script-str >

无后缀的指令,后加字符串型的lua程序。

如:


  • ​location / {​
  • ​default_type text/html;​
  • ​content_by_lua '​
  • ​ngx.say("<p>hello, world</p>")​
  • ​';​
  • ​}​

nginx

*_by_lua_block {lua_script}

有block后缀,可以直接跟lua程序段。

如:


  • ​location / {​
  • ​content_by_lua_block{​
  • ​local test = 'Hello,world'​
  • ​ngx.say(test)​
  • ​}​
  • ​}​

*_by_lua_file < path-to-lua-script-file >

file后缀跟lua文件路径

如:


  • ​location ~ ^/api/([-_a-zA-Z0-9/]+) {​
  • ​# 准入阶段完成参数验证​
  • ​access_by_lua_file lua/access_check.lua;​

  • ​#内容生成阶段​
  • ​content_by_lua_file lua/$1.lua;​

  • ​}​

nginx

三、登陆验证

在​​access_by_lua*​​中集中进行一些权限认证,防止恶意ip、非法行为进入到服务器中。


  • ​location / {​
  • ​access_by_lua_block{​
  • ​ngx.exit(ngx.HTTP_FORBIDDEN)​
  • ​}​
  • ​content_by_lua_block{​
  • ​--内容生产阶段​
  • ​}​
  • ​}​

IP防火墙

OpenResty最佳实践提到:​​禁止某些终端访问​

上面文档下方推荐第三方包:Github,​​Lua-resty-iputils​

操作头信息检测

通过​​access_by_lua*​​​可以对请求进行过滤,比如可以在这里检查有没有带​​authorization​​​头部信息,若没有带可以用​​ngx.exit()​​直接退出。

登陆缓存

Github: ​​lua-resty-jwt模块进行jwt校验​

结合​​lua-resty-jwt​​​和​​lua-resty-redis​​甚至可以将服务器登陆缓存移植到nginx上。

过滤参数

还未尝试。

​防止 SQL 注入​

四、输出过滤

通常是​​header_filter_by_lua*​​​与​​body_filter_by_lua*​​配合实现。

【文档来源官方】:

注意下列API函数现在还不能在​​set_by_lua*​​​、​​header_filter_by_lua*​​​、​​body_filter_by_lua*​​​、​​log_by_lua*​​四个环境中使用:


  • 输出API 函数 (e.g., ngx.say and ngx.send_headers)
  • 控制API 函数 (e.g., ngx.redirect and ngx.exec)
  • 子请求API 函数 (e.g., ngx.location.capture and ngx.location.capture_multi)
  • Cosocket API 函数 (e.g., ngx.socket.tcp and ngx.req.socket).

header_filter_by_lua*

使用Lua代码在lua块中定义输出头信息。

example1:


  • ​location / {​
  • ​proxy_pass http://mybackend;​
  • ​header_filter_by_lua 'ngx.header.Foo = "blah"';​
  • ​}​

nginx

example2:


  • ​header_filter_by_lua_block {​
  • ​ngx.header["content-length"] = nil​
  • ​}​

body_filter_by_lua*

参考:

​谈谈 OpenResty 中的 body_filter_by_lua*​

输入数据块​​chunks​​​要经过ngx.arg​​1​​​ (Lua 字符串)的过滤,"eof"标志指示响应体数据流的末端通过ngx.arg​​2​​ (Lua bollean值)显示。

这个情景下,"eof"标志就是主请求的​​last_buf​​​或子请求的 ​​last_in_chain​​。"eof" == "true" 表示此次nginx请求的响应结束了。

由于响应体可能会分多块发送,​​body_filter_by_lua*​​可能会被多次调用。详细机理参考上面文献有提到。


  • ​location /t {​
  • ​echo hello world;​
  • ​echo hiya globe;​

  • ​body_filter_by_lua '​
  • ​......​
  • ​';​
  • ​}​

nginx

比如上面,​​body_filter_by_lua*​​​首次调用时ngx.arg​​1​​​ 的值只是​​hello world​​​,不包括下面的​​hiya globe​​。

我验证了一下:


  • ​location = /test_bf {​
  • ​echo hello world;​
  • ​echo this;​
  • ​echo is;​
  • ​echo world;​
  • ​header_filter_by_lua_block {​
  • ​ngx.header.content_length = nil​
  • ​}​
  • ​body_filter_by_lua_block {​
  • ​if string.match(ngx.arg[1], "this") then​
  • ​ngx.arg[2] = true​
  • ​ngx.arg[1] = "end"​
  • ​return​
  • ​end​
  • ​}​
  • ​}​

上述location发送四块数据,匹配到this便设置"eof"不再发送下去了。

  • ​$ curl​

结果:


  • ​$ hello world​
  • ​$ end​

要点:

① ​​ngx.arg[2] = true​​设置新的"eof"标志使截断响应,设置"eof"依旧是有效的响应。

② ​​ngx.arg[1] = "end"​​​修改​​ngx.arg[1]​​ 的值,即为修改输出响应。而当需要替换一些信息,仅需一行代码即可实现。

  • ​ngx.arg[1] = ngx.re.gsub(ngx.arg[1], "o", "*")​

上文也提到。上述即为将所有 ' o ' 字符替换为 ' * ' 字符

若该​​location​​​作为其他​​location​​的子请求,而作为子请求不想被过滤数据。需要通过​​ngx.is_subrequest​​判断。


  • ​body_filter_by_lua_block {​
  • ​if ngx.arg[1] and not ngx.is_subrequest then​
  • ​......​
  • ​end​
  • ​}​

nginx

还有一点,如果在​​body_filter_by_lua*​​​中的Lua代码会修改响应体的长度,这就需要去把​​Content-Length​​的响应头去除掉。我之前测试与前端联调时,就是发现body过滤必须要字符数相同才会显示,很头疼不知道什么原因。其实只要去掉这个头就行了。


  • ​location = / {​
  • ​proxy_pass ......​
  • ​header_filter_by_lua_block {​
  • ​ngx.header.content_length = nil​
  • ​}​
  • ​body_filter_by_lua_block {​
  • ​ngx.arg[1] = ngx.re.gsub(ngx.arg[1], "o", "**")​
  • ​}​
  • ​}​

五、Redis

官方github:​​lua-resty-redis​

OpenResty最佳实践:​​访问有授权验证的 Redis​

官方包封装了一些​​Redis​​的方法,使用起来还是挺舒适的。

-- EOF --

作者 ​​毛毛​​ ​​发表于 2017-11-05 15:11:55 ,并被添加「 ​​OpenResty​​ ​​」标签 ,最后修改于 2017-11-05 15:47:30