用OpenResty实战案例5分钟掌握Lua核心语法
从修改Nginx响应头开始理解Lua基础
第一次在OpenResty中写Lua脚本时,我盯着content_by_lua_block配置发呆了半小时——直到发现原来用5行代码就能动态修改响应头。这种"在解决问题中学习"的方式,比死记硬背语法高效得多。
假设我们需要给所有API响应添加版本号头信息。在nginx.conf中加入:
location /api { content_by_lua_block { ngx.header["X-API-Version"] = "1.0.0" ngx.say("Hello, OpenResty!") } }这个简单例子已经包含了Lua的三大基础要素:
- 变量:
"1.0.0"是字符串类型值 - 函数调用:
ngx.header和ngx.say是OpenResty提供的函数 - 表(Table):
ngx.header本质是个Lua table
用请求验证案例串联核心语法
让我们做个更实用的参数校验场景:检查必传参数是否存在。这个案例将覆盖90%的日常语法需求:
location /check { content_by_lua_block { -- 获取查询参数(table类型) local args = ngx.req.get_uri_args() -- 变量与逻辑判断 if not args["user_id"] then ngx.status = ngx.HTTP_BAD_REQUEST ngx.say("Missing parameter: user_id") ngx.exit(ngx.HTTP_BAD_REQUEST) end -- 字符串拼接 local msg = "Hello, " .. (args["user_name"] or "anonymous") -- 函数定义与调用 local function log_request() ngx.log(ngx.INFO, "Request checked for user:", args["user_id"]) end log_request() ngx.say(msg) } }关键语法解析
变量与作用域:
local声明局部变量,避免污染全局空间- 变量默认全局(慎用)
Table操作:
local config = { timeout = 5000, -- 数字键值 ["retry"] = 3 -- 字符串键 } print(config["timeout"]) -- 两种访问方式 print(config.retry) -- 更简洁的语法控制结构对比:
类型 语法示例 特点 if-else if a>b then ... end支持多重条件 for循环 for i=1,10 do ... end数字范围迭代 while循环 while n<100 do n=n*2 end条件满足时持续执行
高效处理JSON数据
实际开发中处理JSON数据占70%的工作量。看这个解析请求体的例子:
location /parse { content_by_lua_block { -- 读取请求体 ngx.req.read_body() local body = ngx.req.get_body_data() -- 使用cjson解析 local cjson = require "cjson.safe" local data, err = cjson.decode(body) -- 错误处理 if not data then ngx.status = ngx.HTTP_BAD_REQUEST ngx.say("Invalid JSON: ", err) return end -- 表遍历 local response = {} for k, v in pairs(data) do if type(v) == "string" then response[k] = v:upper() -- 字符串方法调用 end end ngx.say(cjson.encode(response)) } }性能提示:
- 在init阶段加载模块:
init_by_lua_block { cjson = require "cjson" } - 避免在循环内反复
require - 使用
pairs遍历表,ipairs仅遍历数组部分
必须掌握的元表技巧
元表(Metatable)是Lua最强大的特性之一。我曾用它实现过配置合并功能:
local default_config = { timeout = 30, retry = 3 } local user_config = { timeout = 50 } setmetatable(user_config, { __index = default_config -- 访问缺失键时查找默认配置 }) print(user_config.retry) -- 输出3常用元方法:
| 元方法 | 触发场景 | 典型应用 |
|---|---|---|
| __index | 访问不存在的键 | 实现继承、默认值 |
| __newindex | 给不存在的键赋值 | 只读表、赋值验证 |
| __call | 把表当函数调用 | 实现工厂模式 |
| __tostring | 调用tostring(table)时 | 自定义表输出格式 |
错误处理最佳实践
Lua没有try-catch,但可以用pcall/xpcall:
local function risky_operation() if math.random() > 0.5 then error("Something went wrong") end return "success" end local ok, res = pcall(risky_operation) if not ok then ngx.log(ngx.ERR, "Operation failed: ", res) ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR return end ngx.say(res)错误处理原则:
- 总检查
cjson.decode等可能失败的调用 - 用
ngx.log分级记录日志(ERR/WARN/INFO) - 及时
ngx.exit终止错误请求
性能优化关键点
在OpenResty中写Lua要注意:
变量局部化:
-- 不好 for i=1,100 do sum = sum + i -- 全局变量 end -- 好 local sum = 0 for i=1,100 do sum = sum + i end避免字符串拼接:
-- 低效 local s = "" for i=1,100 do s = s .. tostring(i) end -- 高效 local t = {} for i=1,100 do t[#t+1] = tostring(i) end local s = table.concat(t)重用模块实例:
-- init阶段 init_by_lua_block { json = require "cjson.safe" redis = require "resty.redis" }
实际项目代码结构
一个典型的OpenResty项目目录:
/api ├── init.lua -- 初始化模块 ├── utils.lua -- 工具函数 ├── model │ ├── user.lua -- 用户模型 │ └── product.lua -- 产品模型 └── controller ├── auth.lua -- 认证逻辑 └── api_router.lua -- 路由分发示例模块写法:
-- utils.lua local _M = {} -- 模块表 function _M.trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end function _M.random_string(len) -- 生成随机字符串实现 end return _M调用时:
local utils = require "api.utils" utils.trim(" hello ")调试技巧
日志输出:
ngx.log(ngx.INFO, "Current args: ", require("table").concat(args, ","))打印表内容:
local inspect = require "inspect" print(inspect(some_table))断点调试: 使用OpenResty的
resty -e 'script.lua'测试独立脚本
常见坑与解决方案
数组索引从1开始:
local t = {"a","b","c"} print(t[0]) -- nil print(t[1]) -- "a"nil会终止数组:
local t = {1, nil, 3} print(#t) -- 可能是1或3,不确定!真假判断:
-- 只有false和nil为假,其他都为真 if 0 then print("0 is true!") end
掌握这些核心语法后,配合OpenResty的API文档,你已经能处理90%的网关开发需求。记住:Lua的简洁性既是优点也是挑战——代码越简单,越需要清晰的逻辑。