## 概述
OpenResty 为开发者提供了一系列强大的API,这些API使得Lua脚本能够与Nginx紧密交互,从而高效地执行多种Web服务器任务。在处理Web服务器的核心工作流程中,主要包括三个环节:接收请求、处理请求以及输出响应。在接收请求时,我们能够获取到请求参数、请求头部以及请求体等关键信息。处理请求则涉及执行特定的Lua代码逻辑。至于输出响应,则需要设定响应状态码、自定义响应头部以及构造响应内容体。
在Web开发的典型流程中,接收请求、处理请求并输出响应是三个核心环节。OpenResty以其独特的方式优化了这些环节的处理过程:
1. **接收请求**:OpenResty允许Lua脚本直接访问到请求的各个组成部分,包括但不限于请求参数(无论是URL中的查询参数还是POST请求体中的字段)、请求头信息以及完整的请求体内容。这种直接访问能力让开发者能够轻松解析并理解客户端的请求意图,为后续的处理逻辑提供坚实的数据基础。
2. **处理请求**:一旦请求被接收并解析,OpenResty便通过其提供的Lua API调用相应的Lua代码来处理这些请求。得益于Lua语言的轻量级和高效性,以及OpenResty对Nginx内部机制的深度集成,这一处理过程既快速又灵活。开发者可以编写复杂的业务逻辑,调用外部服务,执行数据库操作等,以满足各种业务需求。
3. **输出响应**:在处理完请求后,OpenResty同样支持通过Lua脚本灵活地构建并输出响应。这包括设置响应状态码(如200 OK、404 Not Found等),添加或修改响应头信息(如Content-Type、Set-Cookie等),以及发送响应体内容。通过精细控制响应的各个方面,开发者能够确保客户端接收到准确、清晰且符合预期的响应。
## 接收请求
`openresty.tinywan.com.conf`配置文件
```
server {
listen 80;
server_name openresty.tinywan.com;
location ~ /lua_request/(\d+)/(\d+) {
default_type "text/html";
lua_code_cache off;
# 设置nginx变量
set $a $1;
set $b $host;
# nginx内容处理
content_by_lua_file conf/lua/request_test.lua;
# 内容体处理完成后调用
echo_after_body "[x] 内容体处理完成后调用 ngx.var.b : $b";
}
}
```
`request_test.lua`文件代码
```lua
--[[--------------------------------------------------------
* | Copyright (C) Shaobo Wan (Tinywan)
* | Origin: 开源技术小栈
* |--------------------------------------------------------
--]]
--接受Nginx变量 ngx.var 访问Nginx变量,例如客户端IP地址、请求URI等。
local var = ngx.var
ngx.say("[x] ngx.var.a : ", var.a)
ngx.say("[x] ngx.var.b : ", var.b)
ngx.say("[x] ngx.var[2] : ", var[2])
ngx.var.b = "Tinywan Openresty";
ngx.say("\r\n")
--请求头
local headers = ngx.req.get_headers()
ngx.say("[x] headers begin")
ngx.say("[x] Host : ", headers["Host"])
ngx.say("[x] user-agent1 : ", headers["user-agent"])
ngx.say("[x] user-agent2 : ", headers.user_agent)
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","))
else
ngx.say(k, " : ", v)
end
end
ngx.say("[x] headers end")
ngx.say("\r\n")
--get请求uri参数
ngx.say("[x] uri args begin")
local uri_args = ngx.req.get_uri_args()
for k, v in pairs(uri_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "))
else
ngx.say(k, ": ", v)
end
end
ngx.say("[x] uri args end")
ngx.say("\r\n")
--post请求参数
ngx.req.read_body()
ngx.say("[x] post args begin")
local post_args = ngx.req.get_post_args()
for k, v in pairs(post_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "))
else
ngx.say(k, ": ", v)
end
end
ngx.say("[x] post args end")
ngx.say("\r\n")
--请求的http协议版本
ngx.say("[x] ngx.req.http_version : ", ngx.req.http_version())
--请求方法
ngx.say("[x] ngx.req.get_method : ", ngx.req.get_method())
--原始的请求头内容
ngx.say("[x] ngx.req.raw_header : ", ngx.req.raw_header())
--请求的body内容体
ngx.say("[x] ngx.req.get_body_data() : ", ngx.req.get_body_data())
```
通过curl脚本测试请求打印结果
```
$ curl -i -H "Content-Type:application/json" -X POST -d '{"name":"ShaoBoWan","age":24}' http://openresty.tinywan.com/lua_request/2024/12/?name=Tinywan&schoole=Ggoogle
[1] 1264
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 00:52:36 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
[x] ngx.var.a : 2024
[x] ngx.var.b : openresty.tinywan.com
[x] ngx.var[2] : 12
[x] headers begin
[x] Host : openresty.tinywan.com
[x] user-agent1 : curl/7.70.0
[x] user-agent2 : curl/7.70.0
host : openresty.tinywan.com
content-type : application/json
user-agent : curl/7.70.0
accept : */*
content-length : 29
[x] headers end
[x] uri args begin
name: Tinywan
[x] uri args end
[x] post args begin
{"name":"ShaoBoWan","age":24}: true
[x] post args end
[x] ngx.req.http_version : 1.1
[x] ngx.req.get_method : POST
[x] ngx.req.raw_header : POST /lua_request/2024/12/?name=Tinywan HTTP/1.1
Host: openresty.tinywan.com
User-Agent: curl/7.70.0
Accept: */*
Content-Type:application/json
Content-Length: 29
[x] ngx.req.get_body_data() : {"name":"ShaoBoWan","age":24}
[x] 内容体处理完成后调用 ngx.var.b : Tinywan Openresty
[1]+ Done curl -i -H "Content-Type:application/json" -X POST -d '{"name":"ShaoBoWan","age":24}' http://openresty.tinywan.com/lua\_request/2024/12/?name=Tinywan
```
![](https://img.kancloud.cn/08/d4/08d4708ec2d15c93d2b1f111abac3ef9_1624x956.png)
* **ngx.var** : nginx变量,如果要赋值如`ngx.var.b = 2`,此变量必须提前声明;另外对于``nginx location中使用正则捕获的捕获组可以使用`ngx.var[捕获组数字]`获取;
* **ngx.req.get_headers**:获取请求头,默认只获取前100,如果想要获取所以可以调用`ngx.req.get_headers(0)`;获取带中划线的请求头时请使用如`headers.user_agent`这种方式;如果一个请求头有多个值,则返回的是lua `table`;
* **ngx.req.get_uri_args**:获取url请求参数,其用法和`get_headers`类似;
* **ngx.req.get_post_args**:获取post请求内容体,其用法和`get_headers`类似,但是必须提前调用ngx.req.read_body()来读取body体(也可以选择在nginx配置文件使用`lua_need_request_body on`;开启读取body体,但是官方不推荐);
* **ngx.req.raw_header**:未解析的请求头字符串;
* **ngx.req.get_body_data**:为解析的请求`body`体内容字符串。
## 处理请求
`openresty.tinywan.com.conf`配置文件
```
location /lua_response_02 {
default_type "text/html";
lua_code_cache off;
content_by_lua_file conf/lua/response_test_02.lua;
}
```
`response_test_02.lua`脚本代码
```
ngx.redirect("https://www.tinywan.com", 302)
```
通过curl脚本测试请求打印结果
```
$ curl -i http://openresty.tinywan.com/lua_response_02
HTTP/1.1 302 Moved Temporarily
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01:13:26 GMT
Content-Type: text/html
Content-Length: 151
Connection: keep-alive
Location: https://www.tinywan.com
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>openresty/1.17.8.2</center>
</body>
</html>
```
* `ngx.status=状态码`,设置响应的状态码;
* `ngx.resp.get_headers()`获取设置的响应状态码;
* `ngx.send_headers()`发送响应状态码,当调用`ngx.say/ngx.print`时自动发送响应状态码;可以通过`ngx.headers_sent=true`判断是否发送了响应状态码。
`openresty.tinywan.com.conf`配置文件
```
location /lua_response_03 {
default_type "text/html";
lua_code_cache off;
content_by_lua_file conf/lua/response_test_03.lua;
}
```
`response_test_03.lua`脚本代码
```
--[[---------------------------------------------------------
* | Copyright (C) Shaobo Wan (Tinywan)
* | Origin: 开源技术小栈
* |-----------------------------------------------------------
--]]
--未经解码的请求uri
local request_uri = ngx.var.request_uri;
ngx.say("[x] request_uri : ", request_uri);
--解码
ngx.say("[x] decode request_uri : ", ngx.unescape_uri(request_uri));
--MD5
ngx.say("[x] ngx.md5 : ", ngx.md5("123"))
--http time
ngx.say("[x] ngx.http_time : ", ngx.http_time(ngx.time()))
```
通过curl脚本测试请求打印结果
```
$ curl -i http://openresty.tinywan.com/lua_response_03
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01:38:43 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
[x] request_uri : /lua_response_03
[x] decode request_uri : /lua_response_03
[x] ngx.md5 : 202cb962ac59075b964b07152d234b70
[x] ngx.http_time : Tue, 16 Jul 2024 01:38:43 GMT
```
如果访问出现`500 Internal Server Error` 请通过nginx错误日志排查,下面错误表示缺少一个结束符`;`
```
[error] 7#7: *2 failed to load external Lua file
"/usr/local/openresty/nginx/conf/lua/response_test_03.lua":
/usr/local/openresty/nginx/conf/lua/response_test_03.lua:13: unfinished string near '") ',
client: 172.18.0.1,
server: openresty.tinywan.com,
request: "GET /lua_response_03 HTTP/1.1",
host: "openresty.tinywan.com"
```
## 输出响应
`openresty.tinywan.com.conf`配置文件
```
server {
listen 80;
server_name openresty.tinywan.com;
location /lua_response_01 {
default_type "text/html";
lua_code_cache off;
content_by_lua_file conf/lua/response_test_01.lua;
}
}
```
`response_test_01.lua`脚本代码
```lua
--[[---------------------------------------------------------
* | Copyright (C) Shaobo Wan (Tinywan)
* | Origin: 开源技术小栈
* |-----------------------------------------------------------
--]]
--写响应头
ngx.header.age = "24"
--多个响应头可以使用table
ngx.header.name = {"Tinywan", "ShaoBoWan"}
--输出响应
ngx.say("[x] age", "name")
ngx.print("[x] age", "name")
--200状态码退出
return ngx.exit(200)
```
通过curl脚本测试请求打印结果
```
$ curl -i http://openresty.tinywan.com/lua_response_01
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01:09:51 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
age: 24
name: Tinywan
name: ShaoBoWan
[x] agename
[x] agename
```
* **ngx.header**:输出响应头;
* **ngx.print**:输出响应内容体;
* **ngx.say**:通`ngx.print`,但是会最后输出一个换行符;
* **ngx.exit**:指定状态码退出。
## Nginx全局内存
Nginx是一个Master进程多个Worker进程的工作方式,因此我们可能需要在多个Worker进程中共享数据。对于全局内存的配置,Nginx提供了`lua_shared_dict`指令,允许在Nginx的http部分分配内存大小,定义一块共享内存空间,所有worker进程都可见 6。这种共享内存机制类似于Java中的Ehcache进程内本地缓存,允许在多个Worker进程间共享数据 6。例如,可以使用以下语法分配10MB的共享内存:
```
http {
# 共享全局变量,在所有worker间共享
lua_shared_dict shared_resty_data 1m;
...
server {
listen 80;
server_name openresty.tinywan.com;
location /lua_shared_dict {
default_type "text/html";
lua_code_cache off;
content_by_lua_file conf/lua/lua_shared_dict_test.lua;
}
}
}
```
在使用共享内存时,可以通过Lua代码进行操作,例如获取、设置、删除共享内存中的键值对 6。例如,使用以下Lua代码可以获取和设置共享内存中的值。
`lua_shared_dict_test.lua` 脚本文件
```
--1、获取全局共享内存变量
local resty_shared_data = ngx.shared.shared_resty_data
--2、获取字典值
local i = resty_shared_data:get("i")
if not i then
i = 1
--3、惰性赋值
resty_shared_data:set("i", i)
ngx.say("[x] lazy set i ", i)
end
--4、递增
i = resty_shared_data:incr("i", 1)
ngx.say("[x] i = ", i)
```
此外,还有`get_stale`、`safe_set`、`add`、`safe_add`、`replace`等方法,用于处理共享内存中的数据,包括处理过期键和避免内存不足时的强制删除操作。
Nginx全局变量是存储在服务器进程内存中的数据,用于在配置和运行时提供各种信息,可以分为常量变量、内置变量和自定义变量 5。全局变量的使用可以提高配置的灵活性,简化管理任务,并提供对服务器运行状况的深入了解。
请参考[http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)。
- 设计模式系列
- 工厂方法模式
- 序言
- Windows程序注册为服务的工具WinSW
- 基础
- 安装
- 开发规范
- 目录结构
- 配置
- 快速入门
- 架构
- 请求流程
- 架构总览
- URL访问
- 容器和依赖注入
- 中间件
- 事件
- 代码层结构
- 四个层次
- 路由
- 控制器
- 请求
- 响应
- 数据库
- MySQL实时同步数据到ES解决方案
- 阿里云DTS数据MySQL同步至Elasticsearch实战
- PHP中的MySQL连接池
- PHP异步非阻塞MySQL客户端连接池
- 模型
- 视图
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 错误和日志
- 异常处理
- 日志处理
- 调试
- 验证
- 验证器
- 验证规则
- 扩展库
- 附录
- Spring框架知识体系详解
- Maven
- Maven和Composer
- 构建Maven项目
- 实操课程
- 01.初识SpringBoot
- 第1章 Java Web发展史与学习Java的方法
- 第2章 环境与常见问题踩坑
- 第3章 springboot的路由与控制器
- 02.Java编程思想深度理论知识
- 第1章 Java编程思想总体
- 第2章 英雄联盟的小案例理解Java中最为抽象的概念
- 第3章 彻底理解IOC、DI与DIP
- 03.Spring与SpringBoot理论篇
- 第1章 Spring与SpringBoot导学
- 第2章 Spring IOC的核心机制:实例化与注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的条件注解与配置
- 第1章 conditonal 条件注解
- 第2章 SpringBoot自动装配解析
- 05.Java异常深度剖析
- 第1章 Java异常分类剖析与自定义异常
- 第2章 自动配置Url前缀
- 06.参数校验机制与LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 参数校验机制以及自定义校验
- 07.项目分层设计与JPA技术
- 第1章 项目分层原则与层与层的松耦合原则
- 第2章 数据库设计、实体关系与查询方案探讨
- 第3章 JPA的关联关系与规则查询
- 08.ORM的概念与思维
- 第1章 ORM的概念与思维
- 第2章 Banner等相关业务
- 第3章 再谈数据库设计技巧与VO层对象的技巧
- 09.JPA的多种查询规则
- 第1章 DozerBeanMapper的使用
- 第2章 详解SKU的规格设计
- 第3章 通用泛型Converter
- 10.令牌与权限
- 第1章 通用泛型类与java泛型的思考
- 常见问题
- 微服务
- demo
- PHP中Self、Static和parent的区别
- Swoole-Cli
- 为什么要使用现代化PHP框架?
- 公众号
- 一键部署微信公众号Markdown编辑器(支持适配和主题设计)
- Autodesigner 2.0发布
- Luya 一个现代化PHP开发框架
- PHPZip - 创建、读取和管理 ZIP 文件的简单库
- 吊打Golang的PHP界天花板webman压测对比
- 简洁而强大的 YAML 解析库
- 推荐一个革命性的PHP测试框架:Kahlan
- ServBay下一代Web开发环境
- 基于Websocket和Canvas实现多人协作实时共享白板
- Apipost预执行脚本如何调用外部PHP语言
- 认证和授权的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地开发环境
- 高效接口防抖策略,确保数据安全,避免重复提交的终极解决方案!
- TIOBE 6月榜单:PHP稳步前行,编程语言生态的微妙变化
- Aho-Corasick字符串匹配算法的实现
- Redis键空间通知 Keyspace Notification 事件订阅
- ServBay如何启用并运行Webman项目
- 使用mpdf实现导出pdf文件功能
- Medoo 轻量级PHP数据库框架
- 在PHP中编写和运行单元测试
- 9 PHP运行时基准性能测试
- QR码生成器在PHP中的源代码
- 使用Gogs极易搭建的自助Git服务
- Gitea
- webman如何记录SQL到日志?
- Sentry PHP: 实时监测并处理PHP应用程序中的错误
- Swoole v6 Alpha 版本已发布
- Proxypin
- Rust实现的Redis内存数据库发布
- PHP 8.4.0 Alpha 1 测试版本发布
- 121
- Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板
- 内网穿透 FRP VS Tailscale
- 新一代开源代码托管平台Gitea
- 微服务系列
- Nacos云原生配置中心介绍与使用
- 轻量级的开源高性能事件库libevent
- 国密算法
- 国密算法(商用密码)
- GmSSL 支持国密SM2/SM3/SM4/SM9/SSL 密码工具箱
- GmSSL PHP 使用
- 数据库
- SQLite数据库的Web管理工具
- 阿里巴巴MySQL数据库强制规范
- PHP
- PHP安全测试秘密武器 PHPGGC
- 使用declare(strict_types=1)来获得更健壮的PHP代码
- PHP中的魔术常量
- OSS 直传阿里腾讯示例
- PHP源码编译安装APCu扩展实现数据缓存
- BI性能DuckDB数据管理系统
- 为什么别人可以是架构师!而我却不是?
- 密码还在用 MD5 加盐?不如试试 password_hash
- Elasticsearch 在电商领域的应用与实践
- Cron 定时任务入门
- 如何动态设置定时任务!而不是写死在Linux Crontab
- Elasticsearch的四种查询方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 轻量级开源博客及建站系统
- 现代化PHP原生协程引擎 PRipple
- 使用Zephir编写C扩展将PHP源代码编译加密
- 如何将PHP源代码编译加密,同时保证代码能正常的运行
- 为什么选择Zephir给PHP编写动态扩展库?
- 使用 PHP + XlsWriter实现百万级数据导入导出
- Rust编写PHP扩展
- 阿里云盘开放平台对接进行文件同步
- 如何构建自己的PHP静态可执行文件
- IM后端架构
- RESTful设计方法和规范
- PHP编译器BPC 7.3 发布,成功编译ThinkPHP8
- 高性能的配置管理扩展 Yaconf
- PHP实现雪花算法库 Snowflake
- PHP官方现代化核心加密库Sodium
- pie
- 现代化、精简、非阻塞PHP标准库PSL
- PHP泛型和集合
- 手把手教你正确使用 Composer包管理
- JWT双令牌认证实现无感Token自动续期
- 最先进PHP大模型深度学习库TransformersPHP
- PHP如何启用 FFI 扩展
- PHP超集语言PXP
- 低延迟双向实时事件通信 Socket.IO
- PHP OOP中的继承和多态
- 强大的现代PHP高级调试工具Kint
- PHP基金会
- 基于webman+vue3高质量中后台框架SaiAdmin
- 开源免费的定时任务管理系统:Gocron
- 简单强大OCR工具EasyOCR在PHP中使用
- PHP代码抽象语法树工具PHP AST Viewer
- MySQL数据库管理工具PHPMyAdmin
- Rust编写的一款高性能多人代码编辑器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 发布
- 高并发系列
- 入门介绍及安装
- Lua脚本开发 Hello World
- 执行流程与阶段详解
- Nginx Lua API 接口开发
- Lua模块开发
- OpenResty 高性能的正式原因
- 记一次查找 lua-resty-mysql 库 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 异步非阻塞HTTP客户端库 lua-resty-http
- Nginx 内置绑定变量
- Redis协程网络库 lua-resty-redis
- 动态HTML渲染库 lua-testy-template
- 单独的
- StackBlitz在线开发环境
- AI
- 基础概念
- 12312
- 基础镜像的坑
- 利用phpy实现 PHP 编写 Vision Transformer (ViT) 模型
- 语义化版本 2.0.0