# LuaWeb **Repository Path**: hadlooop/LuaWeb ## Basic Information - **Project Name**: LuaWeb - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-23 - **Last Updated**: 2026-10-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LuaWeb for Nginx LuaWeb 是面向现有 `ngx_http_lua_module` 的轻量 Web 扩展。它不要求 OpenResty,也不修改 Nginx 模块;直接使用服务器上已经加载的 Lua 模块封装 路由、中间件、请求解析、JSON 和统一响应。 项目包名为 `lua_web`,建议仓库名为 `lua-web-nginx`。 ## 与 Nginx 配置的关系 目标 Nginx 需要已经加载: ```nginx load_module modules/ndk_http_module.so; load_module modules/ngx_http_lua_module.so; ``` 因此新增部分只需放到 `http {}` 中,或通过已有的: ```nginx include /etc/nginx/conf.d/*.conf; ``` 加载本扩展。不需要重新编译 Nginx。 ## 安装 安装扩展和示例应用: ```sh sudo make install ``` 默认安装位置: ```text /etc/nginx/lua/lua_web.lua /etc/nginx/lua/lua_web/ /etc/nginx/lua/lua_web_app/ ``` 安装完整站点示例: ```sh sudo make install-site ``` 默认会写入: ```text /etc/nginx/conf.d/lua-web.conf ``` 可以通过 `SERVER_CONFIG` 指定其他文件: ```sh sudo make install-site SERVER_CONFIG=/etc/nginx/conf.d/example.com.conf ``` `install-site` 会覆盖目标文件。执行前先备份并确认: ```sh sudo cp /etc/nginx/conf.d/lua-web.conf \ /etc/nginx/conf.d/lua-web.conf.bak sudo make install-site sudo nginx -t sudo nginx -s reload ``` `/` 返回 UTF-8 文本 `你好`,完整示例接口为: ```sh curl http://example.com/ curl http://example.com/api/health curl 'http://example.com/api/hello?name=simon' curl -X POST http://example.com/api/echo \ -H 'Content-Type: application/json' \ -d '{"ok":true}' ``` 如果只想安装 Lua 包,不动域名配置: ```sh sudo make install ``` 如果只想在 `8080` 端口启动独立演示: ```sh sudo make install-demo ``` ## 集成方式 站点配置的核心只有两段。先加载 Lua 包并注册业务路由: ```nginx lua_package_path "/etc/nginx/lua/?.lua;/etc/nginx/lua/?/init.lua;;"; init_by_lua_block { local web = require("lua_web") web.configure({ base_path = "/", error_details = false }) require("lua_web_app.bootstrap")(web) } ``` 再把现有 `location /` 的处理交给扩展: ```nginx location / { default_type text/plain; content_by_lua_block { require("lua_web").run() } } ``` 示例应用中的 `/api/users/42` 匹配: ```lua web.get("/api/users/:id", function(ctx) return ctx:json({ id = ctx:param("id") }) end) ``` ## 业务逻辑写在哪里 `nginx.conf` 只负责定义入口,`init_by_lua_block` 只负责注册中间件和路由。 真正的请求逻辑应放在应用目录中,不要在 `content_by_lua_block` 里直接写业务。 简单业务可以直接写在 handler 中: ```text examples/app/handlers/order.lua ``` 然后在 `examples/app/bootstrap.lua` 中注册: ```lua web.get("/api/orders/:id", require("lua_web_app.handlers.order")) web.post("/api/orders", require("lua_web_app.handlers.order_create")) ``` handler 只负责 HTTP 输入、调用业务服务和输出响应: ```lua local order_service = require("lua_web_app.services.order") return function(ctx) local id = ctx:param("id") local order, err = order_service.get(id) if order == nil then return ctx:fail(404, err or "order not found") end return ctx:json(order) end ``` 业务变复杂后,建议使用以下结构: ```text examples/app/ ├── bootstrap.lua ├── handlers/ │ ├── index.lua │ ├── order.lua │ └── user.lua ├── middleware/ │ ├── auth.lua │ └── cors.lua ├── services/ │ ├── order_service.lua │ └── user_service.lua ├── clients/ │ └── upstream_client.lua ├── repositories/ │ └── order_repository.lua └── lib/ ├── validator.lua └── response.lua ``` 各层的职责: | 目录 | 职责 | | --- | --- | | `handlers/` | 读取请求、校验参数、调用 service、生成响应 | | `middleware/` | 认证、CORS、日志、限流和其他公共流程 | | `services/` | 核心业务规则和流程编排 | | `clients/` | 调用上游 HTTP 服务 | | `repositories/` | 数据库、缓存和其他持久化访问 | | `lib/` | 与业务无关的通用工具 | 中间件和路由都应在启动阶段注册: ```lua local web = require("lua_web") web.use(require("lua_web_app.middleware.auth")) web.use(require("lua_web_app.middleware.cors")) web.get("/api/orders/:id", require("lua_web_app.handlers.order")) ``` 部署时,本地文件与服务器路径的映射关系为: ```text examples/app/handlers/order.lua -> /etc/nginx/lua/lua_web_app/handlers/order.lua ``` Lua 引用路径为: ```lua require("lua_web_app.handlers.order") ``` 安装规则会递归复制整个 `examples/app/`,因此 `services/`、`middleware/`、`clients/`、`repositories/` 和 `lib/` 都会 一起部署。 每个 Nginx worker 都有独立的 Lua 内存。全局变量可以作为单 worker 缓存, 但不能作为跨 worker 的共享状态、数据库或分布式锁。 ## 数据库 数据库模块位于 `lua_web.db`,当前实现 MySQL 非阻塞访问,驱动使用 `lua-resty-mysql`。在 CentOS/RHEL/GetPageSpeed 环境安装: ```sh sudo dnf install -y lua5.1-resty-mysql lua5.1-resty-string ``` 先创建私有配置文件: ```sh cd /etc/nginx/lua/lua_web_app cp database.example.lua database.lua chmod 600 database.lua ``` `database.lua` 已被 `.gitignore` 忽略,不会进入公开仓库。配置内容: ```lua return { host = "127.0.0.1", port = 3306, database = "luaweb", user = "luaweb", password = "change-me", charset = "utf8mb4", pool_timeout = 10000, pool_size = 20, read_timeout = 5000, } ``` 也可以使用 `database.example.lua` 中的环境变量方式。若通过 systemd 运行, 使用以下命令配置环境变量: ```sh sudo systemctl edit nginx ``` 示例: ```ini [Service] Environment=LUAWEB_DB_HOST=127.0.0.1 Environment=LUAWEB_DB_PORT=3306 Environment=LUAWEB_DB_NAME=luaweb Environment=LUAWEB_DB_USER=luaweb Environment=LUAWEB_DB_PASSWORD=change-me ``` 查询和写入: ```lua local db = require("lua_web.db") local rows, err, errcode, sqlstate = db.query( "SELECT id, name FROM users WHERE id = ? LIMIT 1", {user_id} ) local result, err, errcode, sqlstate = db.execute( "INSERT INTO users (name) VALUES (?)", {"alice"} ) ``` 事务: ```lua local result, err = db.transaction(function(tx) local ok, insert_err = tx:execute( "INSERT INTO accounts (name) VALUES (?)", {"alice"} ) if not ok then return nil, insert_err end local updated, update_err = tx:execute( "UPDATE counters SET value = value + 1 WHERE name = ?", {"users"} ) if not updated then return nil, update_err end return true end) ``` 参数必须通过 `?` 绑定。字符串、数字、布尔值和 `NULL` 会自动转义; 只有可信 SQL 片段才能使用: ```lua db.raw("NOW()") ``` 数据库健康检查接口: ```text GET /api/db/health ``` 未配置数据库时该接口返回 `503`,不会影响其他接口和网站运行。 ## API ### 注册路由 ```lua web.get(path, handler) web.post(path, handler) web.put(path, handler) web.patch(path, handler) web.delete(path, handler) web.head(path, handler) web.options(path, handler) web.any(path, handler) web.route(method, path, handler) ``` 路径支持 `:name` 参数和 `*` 通配符: ```lua web.get("/api/users/:id", handler) web.get("/assets/*", handler) ``` ### 中间件 ```lua web.use(function(ctx, next) local started = ngx.now() local result = next() ctx:set_header("X-Response-Time", ngx.now() - started) return result end) ``` 中间件按注册顺序执行,调用 `next()` 进入下一个中间件或最终路由。同一层 `next()` 只能调用一次。 ### 日志 初始化时可以统一配置日志级别和前缀: ```lua web.configure({ base_path = "/", log_level = "info", log_prefix = "lua_web", }) ``` 支持的级别按严重程度排列: ```text STDERR, EMERG, ALERT, CRIT, ERR, WARN, NOTICE, INFO, DEBUG ``` `log_level` 表示允许输出的最低级别。例如设置为 `warn` 时,会输出 `WARN`、`ERR`、`CRIT` 等日志,并过滤 `NOTICE`、`INFO` 和 `DEBUG`。 模块日志还会经过 Nginx 自身的 `error_log` 级别过滤。如果 Nginx 配置为 `error_log /var/log/nginx/error.log warn;`,则需要查看 `INFO` 或 `NOTICE` 日志时,应同时把 Nginx 的 `error_log` 调整为 `info`。查看 `DEBUG` 日志时,Nginx 和 LuaWeb 两边都需要设置为 `debug`。 handler 中可以使用请求级日志: ```lua ctx:log(ngx.INFO, "order created", order_id) ctx:log(ngx.ERR, "upstream failed", err) ``` 也可以直接获取带请求上下文的 logger: ```lua local request_log = require("lua_web.log").with_context(ctx) request_log.info("request completed") request_log.warn("slow response", 850) request_log.error("request failed", err) ``` 请求日志会自动包含可用的请求 ID、HTTP 方法和路径: ```text lua_web [INFO] request_id=req-123 method=GET path=/api/health request completed ``` 请求 ID 优先读取 `ngx.var.request_id`,其次读取请求头 `X-Request-Id`。没有请求 ID 时会自动省略该字段。 ### Nginx 请求日志 示例 Nginx 配置显式开启了 `access_log`: ```nginx server { access_log /var/log/nginx/lua-web.access.log combined buffer=16k flush=1s; } ``` 这会记录客户端地址、请求行、状态码、响应字节数、Referer 和 User-Agent。 如果主配置已经定义了 `main` 日志格式,可以改成: ```nginx access_log /var/log/nginx/lua-web.access.log main buffer=16k flush=1s; ``` 示例中间件还会通过 LuaWeb 记录请求开始和完成日志。它们写入 Nginx `error_log`,并自动带上请求 ID、方法、路径和响应状态。 ### 请求 `ctx` 提供: | 接口 | 说明 | | --- | --- | | `ctx.method` | HTTP 方法 | | `ctx.path` | 去掉 `base_path` 后的路径 | | `ctx.raw_path` | 原始 Nginx URI | | `ctx.args` | 查询参数表 | | `ctx.headers` | 小写请求头表 | | `ctx.params` | 路由参数 | | `ctx.state` | 请求级共享表 | | `ctx:query(name)` | 查询参数 | | `ctx:header(name)` | 请求头,name 不区分大小写 | | `ctx:param(name)` | 路由参数 | | `ctx:body()` | 原始请求体 | | `ctx:json_body()` | 解析 JSON 请求体 | ### 响应 ```lua ctx:json(data, status) ctx:text(text, status) ctx:html(html, status) ctx:send(body, status) ctx:redirect(location, status) ctx:set_header(name, value) ctx:fail(status, message, details) ``` handler 也可以返回响应表: ```lua return { status = 201, headers = { ["X-Trace-Id"] = "abc" }, body = "created" } ``` 未匹配的路由返回: ```json {"error":{"status":404,"message":"not found"}} ``` 脚本异常记录到 Nginx `error_log`,客户端默认只收到 500 和通用错误信息。 开发时可把 `error_details` 设为 `true`。 ## 配置 ```lua web.configure({ base_path = "/", error_details = false, log_level = "info", log_prefix = "lua_web", database = { host = "127.0.0.1", database = "luaweb", user = "luaweb", password = "change-me", }, max_args = 100, max_headers = 100, }) ``` 如果把整个应用挂在 `/api/`,可设置 `base_path = "/api"`,路由中就不用 重复写 `/api` 前缀。 ## 测试 ```sh make test ``` 测试需要 `lua`、`luajit` 或 `resty`。完整 Nginx 行为需要在目标服务器执行 `nginx -t` 后用 `curl` 验证。 ## 同步到服务器 同步脚本不保存服务器地址。使用时通过环境变量指定: ```sh SERVER=root@example.com ./scripts/sync.sh ``` 默认远端目录为 `/opt/lua-web-nginx`: ```sh SERVER=root@example.com ./scripts/sync.sh ``` 部署流程是: 1. 通过 `tar` 和 SSH 同步源码到服务器暂存目录。 2. 安装 Lua 库和示例应用。 3. 执行 `nginx -t`。 4. 校验成功后执行 `nginx -s reload`。 默认不会修改任何 Nginx 配置文件。需要同时部署站点模板时,显式指定 `SITE_CONFIG`: ```sh SERVER=root@example.com \ SITE_CONFIG=/etc/nginx/conf.d/example.com.conf \ ./scripts/sync.sh ``` 此时脚本会先备份目标配置,再执行 `make install-site`;`nginx -t` 失败会 自动恢复备份。 只同步源码,不安装配置: ```sh SERVER=root@example.com ./scripts/sync.sh sync ``` 可通过环境变量覆盖目标: ```sh SERVER=root@example.com \ REMOTE_DIR=/srv/lua-web-nginx \ SITE_CONFIG=/etc/nginx/conf.d/example.com.conf \ ./scripts/sync.sh ``` ## 开源协议 LuaWeb 使用 [MIT License](./LICENSE)。参与开发前请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md),版本变化记录在 [CHANGELOG.md](./CHANGELOG.md)。