From b30b3d25d3a84dd4bb188b0fe81e9b19deeb743a Mon Sep 17 00:00:00 2001 From: ithewei Date: Mon, 27 Jul 2026 22:34:43 +0800 Subject: [PATCH 01/12] feat(http): add lua script handler --- CMakeLists.txt | 31 +++ Makefile | 18 ++ Makefile.in | 9 + config.ini | 1 + docs/PLAN.md | 1 + docs/cn/HttpLuaHandler.md | 138 ++++++++++ docs/cn/README.md | 1 + examples/CMakeLists.txt | 6 + examples/http_lua_server.cpp | 26 ++ examples/scripts/hello.lua | 8 + hconfig.h.in | 1 + http/server/HttpLuaHandler.cpp | 419 +++++++++++++++++++++++++++++ http/server/HttpLuaHandler.h | 59 ++++ http/server/HttpService.cpp | 77 ++++++ http/server/HttpService.h | 5 + scripts/unittest.sh | 3 + unittest/CMakeLists.txt | 8 + unittest/http_lua_handler_test.cpp | 105 ++++++++ 18 files changed, 916 insertions(+) create mode 100644 docs/cn/HttpLuaHandler.md create mode 100644 examples/http_lua_server.cpp create mode 100644 examples/scripts/hello.lua create mode 100644 http/server/HttpLuaHandler.cpp create mode 100644 http/server/HttpLuaHandler.h create mode 100644 unittest/http_lua_handler_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ffbe683fe..7b46cea26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ option(WITH_HTTP_SERVER "compile http/server" ON) option(WITH_HTTP_CLIENT "compile http/client" ON) option(WITH_MQTT "compile mqtt" OFF) option(WITH_REDIS "compile redis" OFF) +option(WITH_LUA "with Lua handler for http/server" OFF) option(ENABLE_UDS "Unix Domain Socket" OFF) option(USE_MULTIMAP "MultiMap" OFF) @@ -180,6 +181,33 @@ if(WITH_IO_URING) set(LIBS ${LIBS} uring) endif() +if(WITH_LUA) + add_definitions(-DWITH_LUA) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + foreach(LUA_MODULE lua5.4 lua5.3 lua) + if(NOT LUA_FOUND) + pkg_check_modules(LUA QUIET ${LUA_MODULE}) + endif() + endforeach() + endif() + if(LUA_FOUND) + include_directories(${LUA_INCLUDE_DIRS}) + set(LIBS ${LIBS} ${LUA_LIBRARIES}) + link_directories(${LUA_LIBRARY_DIRS}) + else() + find_path(LUA_INCLUDE_DIR lua.h + PATH_SUFFIXES lua lua5.4 lua5.3) + find_library(LUA_LIBRARY + NAMES lua lua5.4 lua5.3) + if(NOT LUA_INCLUDE_DIR OR NOT LUA_LIBRARY) + message(FATAL_ERROR "WITH_LUA=ON requires Lua development headers and library") + endif() + include_directories(${LUA_INCLUDE_DIR}) + set(LIBS ${LIBS} ${LUA_LIBRARY}) + endif() +endif() + if(WIN32 OR MINGW) add_definitions(-DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS -D_WIN32_WINNT=0x0600) set(LIBS ${LIBS} secur32 crypt32 winmm iphlpapi ws2_32) @@ -238,6 +266,9 @@ if(WITH_EVPP) endif() if(WITH_HTTP_SERVER) set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP_SERVER_HEADERS}) + if(WITH_LUA) + set(LIBHV_HEADERS ${LIBHV_HEADERS} http/server/HttpLuaHandler.h) + endif() set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http/server) endif() if(WITH_HTTP_CLIENT) diff --git a/Makefile b/Makefile index 2eafb2a15..fa86c6603 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,9 @@ endif ifeq ($(WITH_HTTP_SERVER), yes) LIBHV_HEADERS += $(HTTP_SERVER_HEADERS) LIBHV_SRCDIRS += http/server +ifeq ($(WITH_LUA), yes) +LIBHV_HEADERS += http/server/HttpLuaHandler.h +endif endif ifeq ($(WITH_HTTP_CLIENT), yes) @@ -84,6 +87,9 @@ ifeq ($(WITH_HTTP), yes) EXAMPLES += wrk ifeq ($(WITH_HTTP_SERVER), yes) EXAMPLES += http_server_test websocket_server_test +ifeq ($(WITH_LUA), yes) +EXAMPLES += http_lua_server +endif endif ifeq ($(WITH_HTTP_CLIENT), yes) EXAMPLES += curl wget consul http_client_test websocket_client_test @@ -112,6 +118,9 @@ prepare: libhv: $(MKDIR) lib +ifeq ($(WITH_LUA), yes) + $(RM) http/server/HttpLuaHandler.o +endif ifeq ($(BUILD_SHARED), yes) ifeq ($(BUILD_STATIC), yes) $(MAKEF) TARGET=$@ TARGET_TYPE="SHARED|STATIC" SRCDIRS="$(LIBHV_SRCDIRS)" @@ -233,6 +242,9 @@ http_client_test: prepare websocket_server_test: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/server" SRCS="examples/websocket_server_test.cpp" +http_lua_server: prepare + $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/server" SRCS="examples/http_lua_server.cpp" WITH_LUA=yes + websocket_client_test: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/client" SRCS="examples/websocket_client_test.cpp" @@ -315,6 +327,12 @@ unittest: prepare ifeq ($(WITH_EVPP), yes) $(MAKE) libhv $(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -o bin/tcpclient_dns_test unittest/tcpclient_dns_test.cpp -Llib -lhv -pthread +ifeq ($(WITH_LUA), yes) + $(MAKE) libhv + $(CXX) -g -Wall -O0 -std=c++11 -DWITH_LUA $(LUA_CFLAGS) -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -Ihttp -Ihttp/server -o bin/http_lua_handler_test unittest/http_lua_handler_test.cpp -Llib -lhv -pthread $(LUA_LIBS) +else + $(RM) bin/http_lua_handler_test +endif ifeq ($(WITH_REDIS), yes) $(MAKE) libhv $(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Ievent -Icpputil -Iredis -o bin/redis_protocol_test unittest/redis_protocol_test.cpp redis/RedisMessage.cpp diff --git a/Makefile.in b/Makefile.in index 1b807ed38..875ad555c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,6 +29,10 @@ TARGET ?= test BUILD_TYPE ?= RELEASE # TARGET_TYPE=EXECUTABLE,SHARED,STATIC,SHARED|STATIC TARGET_TYPE ?= EXECUTABLE +PKG_CONFIG ?= pkg-config +LUA_PKG_CONFIG ?= $(shell if command -v $(PKG_CONFIG) >/dev/null 2>&1; then $(PKG_CONFIG) --exists lua5.4 && echo lua5.4 || $(PKG_CONFIG) --exists lua5.3 && echo lua5.3 || $(PKG_CONFIG) --exists lua && echo lua; fi) +LUA_CFLAGS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --cflags $(LUA_PKG_CONFIG); fi) +LUA_LIBS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --libs $(LUA_PKG_CONFIG); else echo -llua; fi) # COMMANDS ifdef CROSS_COMPILE @@ -160,6 +164,11 @@ ifeq ($(USE_MULTIMAP), yes) CPPFLAGS += -DUSE_MULTIMAP endif +ifeq ($(WITH_LUA), yes) + CPPFLAGS += -DWITH_LUA $(LUA_CFLAGS) + LDFLAGS += $(LUA_LIBS) +endif + CPPFLAGS += $(addprefix -D, $(DEFINES)) CPPFLAGS += $(addprefix -I, $(INCDIRS)) CPPFLAGS += $(addprefix -I, $(SRCDIRS)) diff --git a/config.ini b/config.ini index 466d3b936..19f451713 100644 --- a/config.ini +++ b/config.ini @@ -18,6 +18,7 @@ WITH_HTTP_SERVER=yes WITH_HTTP_CLIENT=yes WITH_MQTT=no WITH_REDIS=no +WITH_LUA=no # features # base/hsocket.h: Unix Domain Socket diff --git a/docs/PLAN.md b/docs/PLAN.md index 97004d020..badb081fb 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -10,6 +10,7 @@ - mqtt client - redis client - async DNS +- http lua handler ## Plan diff --git a/docs/cn/HttpLuaHandler.md b/docs/cn/HttpLuaHandler.md new file mode 100644 index 000000000..10f44c284 --- /dev/null +++ b/docs/cn/HttpLuaHandler.md @@ -0,0 +1,138 @@ +# Http Lua Handler + +`HttpLuaHandler` 允许 `HttpService` 调用 Lua 脚本里的 `handle(ctx)` 方法处理 HTTP 请求。它适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。 + +该功能是可选模块,默认不编译。 + +## 编译 + +需要 Lua 5.3 或 5.4 开发库。 + +Makefile: + +```bash +make libhv WITH_LUA=yes +make http_lua_server WITH_LUA=yes +make unittest WITH_LUA=yes +``` + +如果系统没有 `pkg-config`,或者 Lua 安装在自定义路径,可以显式指定: + +```bash +make libhv WITH_LUA=yes \ + LUA_CFLAGS="-I/usr/local/include/lua5.4" \ + LUA_LIBS="-L/usr/local/lib -llua" +``` + +CMake: + +```bash +cmake -S . -B build -DWITH_LUA=ON -DBUILD_EXAMPLES=ON -DBUILD_UNITTEST=ON +cmake --build build +``` + +## 基本用法 + +C++: + +```cpp +#include "HttpServer.h" +#include "HttpLuaHandler.h" + +using namespace hv; + +int main() { + HttpService router; + router.GET("/hello", LuaHandler("scripts/hello.lua")); + + HttpServer server; + server.port = 8080; + server.service = &router; + server.run(); + return 0; +} +``` + +Lua: + +```lua +function handle(ctx) + local id = ctx:query("id", "") + return ctx:json({ + ok = true, + id = id, + path = ctx:path() + }) +end +``` + +## 目录映射 + +`HttpService::Script(path, script_dir)` 可以把 URL 前缀映射到脚本目录: + +```cpp +router.Script("/script/", "scripts"); +``` + +访问 `/script/user?id=42` 时会调用 `scripts/user.lua`。访问 `/script/` 时会调用 `scripts/index.lua`。 + +目录映射默认支持 `GET`、`POST`、`PUT`、`DELETE`、`PATCH`。路径中包含 `..` 路径段时返回 `403`。 + +## ctx API + +首版只暴露 HTTP handler 必需的最小 API: + +```lua +ctx:method() -- GET/POST/... +ctx:path() -- URL path +ctx:param(name, default) -- query/restful params +ctx:query(name, default) -- alias of param +ctx:header(name, default) +ctx:body() + +ctx:status(code) +ctx:set_header(name, value) +ctx:text(str) +ctx:json(table) +``` + +`handle(ctx)` 可以直接调用 `ctx:text` / `ctx:json` 返回,也可以返回字符串、数字状态码或 Lua table: + +```lua +function handle(ctx) + ctx:status(201) + ctx:set_header("X-From", "lua") + return ctx:text("created") +end +``` + +## hv API + +首版只提供少量宿主能力: + +```lua +hv.log(...) +hv.now() +``` + +暂不暴露 `hv.event_loop`、TCP/HTTP client、Redis 等能力,避免脚本直接操作底层事件循环。后续可以按业务需要增加受控的 `hv.redis`、`hv.http` 等模块。 + +## 热更新 + +`LuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。 + +重新加载失败时: + +- 如果已有旧版本脚本,继续使用旧版本。 +- 如果首次加载失败,返回 `500`。 + +这能避免线上脚本语法错误直接打断已有服务。 + +## 示例 + +```bash +make http_lua_server WITH_LUA=yes +bin/http_lua_server 8080 examples/scripts +curl "http://127.0.0.1:8080/hello?id=42" +curl "http://127.0.0.1:8080/script/hello?id=42" +``` diff --git a/docs/cn/README.md b/docs/cn/README.md index 7f4c8d3a2..db22adb57 100644 --- a/docs/cn/README.md +++ b/docs/cn/README.md @@ -14,6 +14,7 @@ - [class UdpServer: UDP服务端类](UdpServer.md) - [class UdpClient: UDP客户端类](UdpClient.md) - [class HttpServer: HTTP服务端类](HttpServer.md) +- [Http Lua Handler: HTTP Lua脚本处理器](HttpLuaHandler.md) - [class HttpClient: HTTP客户端类](HttpClient.md) - [class WebSocketServer: WebSocket服务端类](WebSocketServer.md) - [class WebSocketClient: WebSocket客户端类](WebSocketClient.md) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 324030d3f..d74627093 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -143,6 +143,12 @@ if(WITH_HTTP_SERVER) target_link_libraries(websocket_server_test ${HV_LIBRARIES}) list(APPEND EXAMPLES http_server_test websocket_server_test) + + if(WITH_LUA) + add_executable(http_lua_server http_lua_server.cpp) + target_link_libraries(http_lua_server ${HV_LIBRARIES}) + list(APPEND EXAMPLES http_lua_server) + endif() endif() if(WITH_HTTP_CLIENT) diff --git a/examples/http_lua_server.cpp b/examples/http_lua_server.cpp new file mode 100644 index 000000000..a0e14f6fb --- /dev/null +++ b/examples/http_lua_server.cpp @@ -0,0 +1,26 @@ +#include "HttpServer.h" +#include "HttpLuaHandler.h" + +using namespace hv; + +int main(int argc, char** argv) { + int port = 8080; + const char* script_dir = "examples/scripts"; + if (argc > 1) { + port = atoi(argv[1]); + } + if (argc > 2) { + script_dir = argv[2]; + } + + HttpService router; + router.GET("/hello", LuaHandler("examples/scripts/hello.lua")); + router.Script("/script/", script_dir); + + HttpServer server; + server.port = port; + server.service = &router; + server.setThreadNum(4); + server.run(); + return 0; +} diff --git a/examples/scripts/hello.lua b/examples/scripts/hello.lua new file mode 100644 index 000000000..068da5657 --- /dev/null +++ b/examples/scripts/hello.lua @@ -0,0 +1,8 @@ +function handle(ctx) + hv.log("lua request", ctx:method(), ctx:path()) + return ctx:json({ + ok = true, + path = ctx:path(), + id = ctx:query("id", "") + }) +end diff --git a/hconfig.h.in b/hconfig.h.in index 4880666a8..39b55ad68 100644 --- a/hconfig.h.in +++ b/hconfig.h.in @@ -99,5 +99,6 @@ #cmakedefine WITH_WEPOLL 1 #cmakedefine WITH_KCP 1 #cmakedefine WITH_IO_URING 1 +#cmakedefine WITH_LUA 1 #endif // HV_CONFIG_H_ diff --git a/http/server/HttpLuaHandler.cpp b/http/server/HttpLuaHandler.cpp new file mode 100644 index 000000000..998df7321 --- /dev/null +++ b/http/server/HttpLuaHandler.cpp @@ -0,0 +1,419 @@ +#include "HttpLuaHandler.h" + +#ifdef WITH_LUA + +#include +#include + +extern "C" { +#include +#include +#include +} + +#include "hfile.h" +#include "hlog.h" +#include "hpath.h" +#include "hstring.h" +#include "htime.h" + +namespace hv { + +namespace { + +static const char* LUA_CTX_META = "hv.HttpContext"; + +struct LuaHttpContext { + HttpContextPtr ctx; +}; + +static LuaHttpContext* lua_check_ctx(lua_State* L) { + return (LuaHttpContext*)luaL_checkudata(L, 1, LUA_CTX_META); +} + +static std::string lua_opt_string_arg(lua_State* L, int index, const char* defvalue = "") { + if (lua_isnoneornil(L, index)) { + return defvalue; + } + size_t len = 0; + const char* s = luaL_checklstring(L, index, &len); + return std::string(s, len); +} + +static int lua_ctx_method(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + lua_pushstring(L, http_method_str(holder->ctx->request->method)); + return 1; +} + +static int lua_ctx_path(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + lua_pushlstring(L, holder->ctx->request->path.data(), holder->ctx->request->path.size()); + return 1; +} + +static int lua_ctx_param(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + std::string key = lua_opt_string_arg(L, 2); + std::string defvalue = lua_opt_string_arg(L, 3); + lua_pushstring(L, holder->ctx->request->GetParam(key.c_str(), defvalue).c_str()); + return 1; +} + +static int lua_ctx_query(lua_State* L) { + return lua_ctx_param(L); +} + +static int lua_ctx_header(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + std::string key = lua_opt_string_arg(L, 2); + std::string defvalue = lua_opt_string_arg(L, 3); + lua_pushstring(L, holder->ctx->request->GetHeader(key.c_str(), defvalue).c_str()); + return 1; +} + +static int lua_ctx_body(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + const std::string& body = holder->ctx->request->body; + lua_pushlstring(L, body.data(), body.size()); + return 1; +} + +static int lua_ctx_status(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + int status = (int)luaL_checkinteger(L, 2); + holder->ctx->response->status_code = (http_status)status; + lua_pushinteger(L, status); + return 1; +} + +static int lua_ctx_set_header(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + std::string key = lua_opt_string_arg(L, 2); + std::string value = lua_opt_string_arg(L, 3); + holder->ctx->response->SetHeader(key.c_str(), value); + if (stricmp(key.c_str(), "Content-Type") == 0) { + holder->ctx->response->SetContentType(value.c_str()); + } + return 0; +} + +static int lua_ctx_text(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + std::string text = lua_opt_string_arg(L, 2); + holder->ctx->response->String(text); + lua_pushinteger(L, holder->ctx->response->status_code); + return 1; +} + +static Json lua_to_json(lua_State* L, int index); + +static Json lua_table_to_json(lua_State* L, int index) { + index = lua_absindex(L, index); + bool is_array = true; + lua_Integer max_index = 0; + size_t count = 0; + + lua_pushnil(L); + while (lua_next(L, index) != 0) { + ++count; + if (lua_type(L, -2) == LUA_TNUMBER && lua_isinteger(L, -2)) { + lua_Integer k = lua_tointeger(L, -2); + if (k <= 0) { + is_array = false; + } else if (k > max_index) { + max_index = k; + } + } else { + is_array = false; + } + lua_pop(L, 1); + } + + if (is_array && (lua_Integer)count == max_index) { + Json j = Json::array(); + for (lua_Integer i = 1; i <= max_index; ++i) { + lua_geti(L, index, i); + j.push_back(lua_to_json(L, -1)); + lua_pop(L, 1); + } + return j; + } + + Json j = Json::object(); + lua_pushnil(L); + while (lua_next(L, index) != 0) { + std::string key; + if (lua_type(L, -2) == LUA_TSTRING) { + size_t len = 0; + const char* s = lua_tolstring(L, -2, &len); + key.assign(s, len); + } else if (lua_type(L, -2) == LUA_TNUMBER) { + key = hv::to_string((int64_t)lua_tointeger(L, -2)); + } + if (!key.empty()) { + j[key] = lua_to_json(L, -1); + } + lua_pop(L, 1); + } + return j; +} + +static Json lua_to_json(lua_State* L, int index) { + switch (lua_type(L, index)) { + case LUA_TNIL: + return nullptr; + case LUA_TBOOLEAN: + return lua_toboolean(L, index) != 0; + case LUA_TNUMBER: + if (lua_isinteger(L, index)) { + return (int64_t)lua_tointeger(L, index); + } + return lua_tonumber(L, index); + case LUA_TSTRING: { + size_t len = 0; + const char* s = lua_tolstring(L, index, &len); + return std::string(s, len); + } + case LUA_TTABLE: + return lua_table_to_json(L, index); + default: + return nullptr; + } +} + +static int lua_ctx_json(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + Json j = lua_to_json(L, 2); + holder->ctx->response->Json(j); + lua_pushinteger(L, holder->ctx->response->status_code); + return 1; +} + +static int lua_ctx_gc(lua_State* L) { + LuaHttpContext* holder = lua_check_ctx(L); + holder->ctx.~shared_ptr(); + return 0; +} + +static void lua_push_ctx(lua_State* L, const HttpContextPtr& ctx) { + void* storage = lua_newuserdata(L, sizeof(LuaHttpContext)); + new (storage) LuaHttpContext(); + ((LuaHttpContext*)storage)->ctx = ctx; + luaL_getmetatable(L, LUA_CTX_META); + lua_setmetatable(L, -2); +} + +static int lua_hv_log(lua_State* L) { + int n = lua_gettop(L); + std::string line; + for (int i = 1; i <= n; ++i) { + size_t len = 0; + const char* s = luaL_tolstring(L, i, &len); + if (i > 1) line += "\t"; + line.append(s, len); + lua_pop(L, 1); + } + hlogi("[lua] %s", line.c_str()); + return 0; +} + +static int lua_hv_now(lua_State* L) { + lua_pushinteger(L, (lua_Integer)time(NULL)); + return 1; +} + +static void register_ctx(lua_State* L) { + if (luaL_newmetatable(L, LUA_CTX_META)) { + lua_pushcfunction(L, lua_ctx_gc); + lua_setfield(L, -2, "__gc"); + + lua_newtable(L); + lua_pushcfunction(L, lua_ctx_method); + lua_setfield(L, -2, "method"); + lua_pushcfunction(L, lua_ctx_path); + lua_setfield(L, -2, "path"); + lua_pushcfunction(L, lua_ctx_param); + lua_setfield(L, -2, "param"); + lua_pushcfunction(L, lua_ctx_query); + lua_setfield(L, -2, "query"); + lua_pushcfunction(L, lua_ctx_header); + lua_setfield(L, -2, "header"); + lua_pushcfunction(L, lua_ctx_body); + lua_setfield(L, -2, "body"); + lua_pushcfunction(L, lua_ctx_status); + lua_setfield(L, -2, "status"); + lua_pushcfunction(L, lua_ctx_set_header); + lua_setfield(L, -2, "set_header"); + lua_pushcfunction(L, lua_ctx_text); + lua_setfield(L, -2, "text"); + lua_pushcfunction(L, lua_ctx_json); + lua_setfield(L, -2, "json"); + lua_setfield(L, -2, "__index"); + } + lua_pop(L, 1); +} + +static void register_hv(lua_State* L) { + lua_newtable(L); + lua_pushcfunction(L, lua_hv_log); + lua_setfield(L, -2, "log"); + lua_pushcfunction(L, lua_hv_now); + lua_setfield(L, -2, "now"); + lua_setglobal(L, "hv"); +} + +static time_t file_mtime(const std::string& filepath) { + struct stat st; + if (stat(filepath.c_str(), &st) != 0) { + return 0; + } + return st.st_mtime; +} + +} // namespace + +LuaHandler::LuaHandler(const char* filepath, const LuaHandlerOptions& options) + : filepath_(filepath ? filepath : "") + , options_(options) + , L_(NULL) + , mtime_(0) { +} + +LuaHandler::LuaHandler(const LuaHandler& rhs) + : filepath_(rhs.filepath_) + , options_(rhs.options_) + , L_(NULL) + , mtime_(0) { +} + +LuaHandler& LuaHandler::operator=(const LuaHandler& rhs) { + if (this == &rhs) return *this; + std::lock_guard lock(mutex_); + closeLocked(); + filepath_ = rhs.filepath_; + options_ = rhs.options_; + mtime_ = 0; + last_error_.clear(); + return *this; +} + +LuaHandler::~LuaHandler() { + std::lock_guard lock(mutex_); + closeLocked(); +} + +std::string LuaHandler::lastError() const { + std::lock_guard lock(mutex_); + return last_error_; +} + +void LuaHandler::setErrorLocked(const std::string& error) { + last_error_ = error; +} + +void LuaHandler::closeLocked() { + if (L_) { + lua_close(L_); + L_ = NULL; + } +} + +bool LuaHandler::loadLocked(time_t mtime) { + lua_State* L = luaL_newstate(); + if (L == NULL) { + setErrorLocked("luaL_newstate failed"); + return false; + } + + luaL_openlibs(L); + register_ctx(L); + register_hv(L); + + if (luaL_loadfile(L, filepath_.c_str()) != LUA_OK || lua_pcall(L, 0, 0, 0) != LUA_OK) { + std::string error = lua_tostring(L, -1) ? lua_tostring(L, -1) : "load script failed"; + lua_close(L); + setErrorLocked(error); + hloge("load lua script %s failed: %s", filepath_.c_str(), error.c_str()); + return false; + } + + lua_getglobal(L, "handle"); + if (!lua_isfunction(L, -1)) { + lua_close(L); + setErrorLocked("global handle(ctx) is not a function"); + hloge("load lua script %s failed: handle(ctx) not found", filepath_.c_str()); + return false; + } + lua_pop(L, 1); + + closeLocked(); + L_ = L; + mtime_ = mtime; + last_error_.clear(); + return true; +} + +bool LuaHandler::reloadIfNeeded() { + std::lock_guard lock(mutex_); + time_t mtime = file_mtime(filepath_); + if (mtime == 0) { + setErrorLocked(strerror(errno)); + return L_ != NULL; + } + if (L_ != NULL && (!options_.reload_on_change || mtime == mtime_)) { + return true; + } + return loadLocked(mtime) || L_ != NULL; +} + +int LuaHandler::callLocked(const HttpContextPtr& ctx) { + lua_getglobal(L_, "handle"); + lua_push_ctx(L_, ctx); + if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { + std::string error = lua_tostring(L_, -1) ? lua_tostring(L_, -1) : "call handle failed"; + lua_pop(L_, 1); + setErrorLocked(error); + hloge("call lua script %s failed: %s", filepath_.c_str(), error.c_str()); + ctx->response->status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; + ctx->response->String(error); + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + + int status = ctx->response->status_code; + if (lua_isinteger(L_, -1)) { + status = (int)lua_tointeger(L_, -1); + if (ctx->response->status_code == HTTP_STATUS_OK) { + ctx->response->status_code = (http_status)status; + } + } else if (lua_isstring(L_, -1)) { + size_t len = 0; + const char* s = lua_tolstring(L_, -1, &len); + ctx->response->String(std::string(s, len)); + status = ctx->response->status_code; + } else if (lua_istable(L_, -1)) { + Json j = lua_to_json(L_, -1); + ctx->response->Json(j); + status = ctx->response->status_code; + } + lua_pop(L_, 1); + return status; +} + +int LuaHandler::operator()(const HttpContextPtr& ctx) { + if (!ctx || !ctx->response || !ctx->request) { + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + if (!reloadIfNeeded()) { + std::lock_guard lock(mutex_); + ctx->response->status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; + ctx->response->String(last_error_); + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + std::lock_guard lock(mutex_); + return callLocked(ctx); +} + +} // namespace hv + +#endif // WITH_LUA diff --git a/http/server/HttpLuaHandler.h b/http/server/HttpLuaHandler.h new file mode 100644 index 000000000..a9a51acf7 --- /dev/null +++ b/http/server/HttpLuaHandler.h @@ -0,0 +1,59 @@ +#ifndef HV_HTTP_LUA_HANDLER_H_ +#define HV_HTTP_LUA_HANDLER_H_ + +#include +#include +#include +#include + +#include "hexport.h" +#include "HttpService.h" + +struct lua_State; + +namespace hv { + +struct HV_EXPORT LuaHandlerOptions { + bool reload_on_change; + + LuaHandlerOptions() { + reload_on_change = true; + } +}; + +class HV_EXPORT LuaHandler { +public: + LuaHandler(const char* filepath, const LuaHandlerOptions& options = LuaHandlerOptions()); + LuaHandler(const LuaHandler& rhs); + LuaHandler& operator=(const LuaHandler& rhs); + ~LuaHandler(); + + int operator()(const HttpContextPtr& ctx); + + const std::string& filepath() const { + return filepath_; + } + + std::string lastError() const; + +private: + bool reloadIfNeeded(); + bool loadLocked(time_t mtime); + void closeLocked(); + int callLocked(const HttpContextPtr& ctx); + void setErrorLocked(const std::string& error); + +private: + std::string filepath_; + LuaHandlerOptions options_; + lua_State* L_; + time_t mtime_; + std::string last_error_; + mutable std::mutex mutex_; +}; + +typedef std::shared_ptr LuaHandlerPtr; + +} // namespace hv + +#endif // HV_HTTP_LUA_HANDLER_H_ diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index 896113682..e2f00361c 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -1,6 +1,11 @@ #include "HttpService.h" #include "HttpMiddleware.h" #include "HttpRouter.h" +#ifdef WITH_LUA +#include "HttpLuaHandler.h" +#include "hpath.h" +#include "hstring.h" +#endif namespace hv { @@ -29,6 +34,78 @@ void HttpService::AddRoute(const char* path, http_method method, const http_hand method_handlers->push_back(http_method_handler(method, handler)); } +#ifdef WITH_LUA +static bool http_lua_script_path_is_safe(const std::string& path) { + if (path.empty()) return true; + size_t start = 0; + while (start <= path.size()) { + size_t end = path.find_first_of("/\\", start); + std::string segment = end == std::string::npos ? path.substr(start) : path.substr(start, end - start); + if (segment == "..") { + return false; + } + if (end == std::string::npos) break; + start = end + 1; + } + return true; +} + +void HttpService::Script(const char* path, const char* script_dir) { + std::string route_path(path ? path : ""); + if (route_path.empty()) return; + if (route_path.back() != '*') { + if (route_path.back() != '/') route_path += '/'; + route_path += '*'; + } + + std::string route_prefix = route_path.substr(0, route_path.size() - 1); + std::string root(script_dir ? script_dir : ""); + while (!root.empty() && (root.back() == '/' || root.back() == '\\')) { + root.pop_back(); + } + + std::shared_ptr > scripts = + std::make_shared >(); + std::shared_ptr scripts_mutex = std::make_shared(); + http_handler handler([route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) { + std::string path = ctx->path(); + if (path.compare(0, route_prefix.size(), route_prefix) != 0) { + return HTTP_STATUS_NOT_FOUND; + } + std::string name = path.substr(route_prefix.size()); + if (name.empty()) { + name = "index"; + } + if (!http_lua_script_path_is_safe(name)) { + return HTTP_STATUS_FORBIDDEN; + } + std::string script = HPath::join(root, name); + if (HPath::suffixname(script).empty()) { + script += ".lua"; + } + + LuaHandlerPtr lua_handler; + { + std::lock_guard lock(*scripts_mutex); + auto iter = scripts->find(script); + if (iter == scripts->end()) { + lua_handler = std::make_shared(script.c_str()); + (*scripts)[script] = lua_handler; + } else { + lua_handler = iter->second; + } + } + return (*lua_handler)(ctx); + }); + + AddRoute(route_path.c_str(), HTTP_GET, handler); + AddRoute(route_path.c_str(), HTTP_POST, handler); + AddRoute(route_path.c_str(), HTTP_PUT, handler); + AddRoute(route_path.c_str(), HTTP_DELETE, handler); + AddRoute(route_path.c_str(), HTTP_PATCH, handler); +} +#endif + bool HttpService::HasRoutes() const { return router && !router->Empty(); } diff --git a/http/server/HttpService.h b/http/server/HttpService.h index 7b9686bc5..d1742543f 100644 --- a/http/server/HttpService.h +++ b/http/server/HttpService.h @@ -109,6 +109,8 @@ typedef std::unordered_map http_path_ha namespace hv { +class LuaHandler; + template class HttpRouter; @@ -191,6 +193,9 @@ struct HV_EXPORT HttpService { // router interface void AddRoute(const char* path, http_method method, const http_handler& handler); +#ifdef WITH_LUA + void Script(const char* path, const char* script_dir); +#endif // @param[in] full_path: {base_url}/path?query // @param[out] params: RESTful API /:field/ => params["field"] // @retval 0 OK, else HTTP_STATUS_NOT_FOUND, HTTP_STATUS_METHOD_NOT_ALLOWED diff --git a/scripts/unittest.sh b/scripts/unittest.sh index f32edee13..2a795efa1 100755 --- a/scripts/unittest.sh +++ b/scripts/unittest.sh @@ -32,6 +32,9 @@ bin/socketpair_test # bin/objectpool_test bin/sizeof_test bin/http_router_test +if [ -x bin/http_lua_handler_test ]; then + bin/http_lua_handler_test +fi if [ -x bin/hdns_test ]; then bin/hdns_test fi diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index c92a3e1fb..9109d8420 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -90,6 +90,13 @@ target_include_directories(sendmail PRIVATE .. ../base ../protocol ../util) add_executable(http_router_test http_router_test.cpp) target_include_directories(http_router_test PRIVATE ../http/server) +if(WITH_LUA) +add_executable(http_lua_handler_test http_lua_handler_test.cpp) +target_include_directories(http_lua_handler_test PRIVATE .. ../base ../ssl ../event ../cpputil ../evpp ../http ../http/server) +target_link_libraries(http_lua_handler_test ${HV_LIBRARIES}) +set(HTTP_LUA_UNITTEST_TARGETS http_lua_handler_test) +endif() + # ------event: async dns------ add_executable(hdns_test hdns_test.c) target_include_directories(hdns_test PRIVATE .. ../base ../ssl ../event) @@ -155,6 +162,7 @@ add_custom_target(unittest DEPENDS ftp sendmail http_router_test + ${HTTP_LUA_UNITTEST_TARGETS} hdns_test hdns_benchmark ${REDIS_UNITTEST_TARGETS} diff --git a/unittest/http_lua_handler_test.cpp b/unittest/http_lua_handler_test.cpp new file mode 100644 index 000000000..f4a6fd022 --- /dev/null +++ b/unittest/http_lua_handler_test.cpp @@ -0,0 +1,105 @@ +#include "HttpLuaHandler.h" + +#include +#include +#include + +#include "hbase.h" +#include "hfile.h" +#include "hpath.h" +#include "HttpService.h" +#include "HttpContext.h" + +static std::string write_script(const char* name, const char* content) { + std::string dir = "tmp/http_lua_handler_test"; + const char* slash = strrchr(name, '/'); + if (slash) { + dir = HPath::join(dir, std::string(name, slash - name)); + } + hv_mkdir_p(dir.c_str()); + std::string path = HPath::join("tmp/http_lua_handler_test", name); + HFile file; + int ret = file.open(path.c_str(), "wb"); + assert(ret == 0); + file.write(content, strlen(content)); + file.close(); + return path; +} + +static HttpContextPtr make_ctx(const char* method, const char* path) { + HttpContextPtr ctx = std::make_shared(); + ctx->request = std::make_shared(); + ctx->response = std::make_shared(); + ctx->request->method = http_method_enum(method); + ctx->request->path = path; + ctx->request->query_params["id"] = "42"; + ctx->request->headers["X-Test"] = "header-value"; + ctx->request->body = "request-body"; + return ctx; +} + +static void test_text_response() { + std::string script = write_script("text.lua", + "function handle(ctx)\n" + " ctx:status(201)\n" + " ctx:set_header('X-Lua', ctx:query('id'))\n" + " return ctx:text(ctx:method() .. ' ' .. ctx:path() .. ' ' .. ctx:header('X-Test'))\n" + "end\n"); + + hv::LuaHandler handler(script.c_str()); + HttpContextPtr ctx = make_ctx("GET", "/hello"); + int status = handler(ctx); + assert(status == 201); + assert(ctx->response->status_code == 201); + assert(ctx->response->body == "GET /hello header-value"); + assert(ctx->response->GetHeader("X-Lua") == "42"); + assert(ctx->response->ContentType() == TEXT_PLAIN); +} + +static void test_json_response() { + std::string script = write_script("json.lua", + "function handle(ctx)\n" + " return ctx:json({ok=true, id=ctx:query('id')})\n" + "end\n"); + + hv::LuaHandler handler(script.c_str()); + HttpContextPtr ctx = make_ctx("POST", "/json"); + int status = handler(ctx); + assert(status == 200); + assert(ctx->response->ContentType() == APPLICATION_JSON); + assert(ctx->response->body.find("\"ok\": true") != std::string::npos); + assert(ctx->response->body.find("\"id\": \"42\"") != std::string::npos); +} + +static void test_script_dir_mapping() { + write_script("api/user.lua", + "function handle(ctx)\n" + " return ctx:text('script:' .. ctx:query('id'))\n" + "end\n"); + + hv::HttpService service; + service.Script("/api/", "tmp/http_lua_handler_test/api"); + + http_handler* handler = NULL; + std::map params; + int ret = service.GetRoute("/api/user?id=42", HTTP_GET, &handler, params); + assert(ret == 0); + assert(handler != NULL); + assert(handler->ctx_handler != NULL); + + HttpContextPtr ctx = make_ctx("GET", "/api/user"); + ctx->service = &service; + ctx->request->query_params = params; + ctx->request->query_params["id"] = "42"; + int status = handler->ctx_handler(ctx); + assert(status == 200); + assert(ctx->response->body == "script:42"); +} + +int main() { + test_text_response(); + test_json_response(); + test_script_dir_mapping(); + printf("ALL http_lua_handler_test PASSED\n"); + return 0; +} From 0df28bcc00cd2d81ae1a0c3a35a3b9da023a0cad Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 10:34:35 +0800 Subject: [PATCH 02/12] fix(lua): detect Homebrew Lua 5.5 --- CMakeLists.txt | 8 +++++--- Makefile.in | 5 +---- Makefile.vars | 7 +++++++ docs/cn/HttpLuaHandler.md | 4 ++-- http/server/HttpService.cpp | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b46cea26..d6716ed7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,7 +185,7 @@ if(WITH_LUA) add_definitions(-DWITH_LUA) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) - foreach(LUA_MODULE lua5.4 lua5.3 lua) + foreach(LUA_MODULE lua5.5 lua-5.5 lua5.4 lua5.3 lua) if(NOT LUA_FOUND) pkg_check_modules(LUA QUIET ${LUA_MODULE}) endif() @@ -197,9 +197,11 @@ if(WITH_LUA) link_directories(${LUA_LIBRARY_DIRS}) else() find_path(LUA_INCLUDE_DIR lua.h - PATH_SUFFIXES lua lua5.4 lua5.3) + PATHS /opt/homebrew/opt/lua/include /usr/local/opt/lua/include + PATH_SUFFIXES lua lua5.5 lua5.4 lua5.3) find_library(LUA_LIBRARY - NAMES lua lua5.4 lua5.3) + NAMES lua lua5.5 lua5.4 lua5.3 + PATHS /opt/homebrew/opt/lua/lib /usr/local/opt/lua/lib) if(NOT LUA_INCLUDE_DIR OR NOT LUA_LIBRARY) message(FATAL_ERROR "WITH_LUA=ON requires Lua development headers and library") endif() diff --git a/Makefile.in b/Makefile.in index 875ad555c..6c9b4392b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,6 +22,7 @@ # LIBS="opencv_core opencv_highgui" #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -include config.mk +-include Makefile.vars # VARIABLES TARGET ?= test @@ -29,10 +30,6 @@ TARGET ?= test BUILD_TYPE ?= RELEASE # TARGET_TYPE=EXECUTABLE,SHARED,STATIC,SHARED|STATIC TARGET_TYPE ?= EXECUTABLE -PKG_CONFIG ?= pkg-config -LUA_PKG_CONFIG ?= $(shell if command -v $(PKG_CONFIG) >/dev/null 2>&1; then $(PKG_CONFIG) --exists lua5.4 && echo lua5.4 || $(PKG_CONFIG) --exists lua5.3 && echo lua5.3 || $(PKG_CONFIG) --exists lua && echo lua; fi) -LUA_CFLAGS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --cflags $(LUA_PKG_CONFIG); fi) -LUA_LIBS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --libs $(LUA_PKG_CONFIG); else echo -llua; fi) # COMMANDS ifdef CROSS_COMPILE diff --git a/Makefile.vars b/Makefile.vars index e3cb3121e..41eb5bdb4 100644 --- a/Makefile.vars +++ b/Makefile.vars @@ -7,6 +7,13 @@ PREFIX ?= /usr/local INSTALL_INCDIR ?= $(PREFIX)/include/hv INSTALL_LIBDIR ?= $(PREFIX)/lib +PKG_CONFIG ?= pkg-config +LUA_PKG_CONFIG ?= $(shell if command -v $(PKG_CONFIG) >/dev/null 2>&1; then $(PKG_CONFIG) --exists lua5.5 && echo lua5.5 || $(PKG_CONFIG) --exists lua-5.5 && echo lua-5.5 || $(PKG_CONFIG) --exists lua5.4 && echo lua5.4 || $(PKG_CONFIG) --exists lua5.3 && echo lua5.3 || $(PKG_CONFIG) --exists lua && echo lua; fi) +LUA_PREFIX ?= $(shell for dir in /opt/homebrew/opt/lua /usr/local/opt/lua /usr; do if [ -f "$$dir/include/lua/lua.h" ] || [ -f "$$dir/include/lua5.5/lua.h" ] || [ -f "$$dir/include/lua5.4/lua.h" ] || [ -f "$$dir/include/lua5.3/lua.h" ] || [ -f "$$dir/include/lua.h" ]; then echo $$dir; break; fi; done) +LUA_INCLUDE_DIR ?= $(shell if [ -n "$(LUA_PREFIX)" ]; then for dir in "$(LUA_PREFIX)/include/lua" "$(LUA_PREFIX)/include/lua5.5" "$(LUA_PREFIX)/include/lua5.4" "$(LUA_PREFIX)/include/lua5.3" "$(LUA_PREFIX)/include"; do if [ -f "$$dir/lua.h" ]; then echo $$dir; break; fi; done; fi) +LUA_CFLAGS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --cflags $(LUA_PKG_CONFIG); elif [ -n "$(LUA_INCLUDE_DIR)" ]; then echo -I$(LUA_INCLUDE_DIR); fi) +LUA_LIBS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --libs $(LUA_PKG_CONFIG); elif [ -n "$(LUA_PREFIX)" ]; then echo -L$(LUA_PREFIX)/lib -llua; else echo -llua; fi) + BASE_HEADERS = base/hplatform.h\ \ base/hdef.h\ diff --git a/docs/cn/HttpLuaHandler.md b/docs/cn/HttpLuaHandler.md index 10f44c284..949617904 100644 --- a/docs/cn/HttpLuaHandler.md +++ b/docs/cn/HttpLuaHandler.md @@ -6,7 +6,7 @@ ## 编译 -需要 Lua 5.3 或 5.4 开发库。 +需要 Lua 5.3 或更新版本的开发库。 Makefile: @@ -20,7 +20,7 @@ make unittest WITH_LUA=yes ```bash make libhv WITH_LUA=yes \ - LUA_CFLAGS="-I/usr/local/include/lua5.4" \ + LUA_CFLAGS="-I/usr/local/include/lua" \ LUA_LIBS="-L/usr/local/lib -llua" ``` diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index e2f00361c..c13326bcf 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -67,7 +67,7 @@ void HttpService::Script(const char* path, const char* script_dir) { std::shared_ptr > scripts = std::make_shared >(); std::shared_ptr scripts_mutex = std::make_shared(); - http_handler handler([route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) { + http_handler handler([route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) -> int { std::string path = ctx->path(); if (path.compare(0, route_prefix.size(), route_prefix) != 0) { return HTTP_STATUS_NOT_FOUND; From 74ea46b57dba48a2059e5d36e1ee1998d7b25771 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 12:36:42 +0800 Subject: [PATCH 03/12] refactor(http): add generic script handler entry --- CMakeLists.txt | 2 +- Makefile | 4 +- docs/cn/HttpLuaHandler.md | 14 ++++--- examples/http_lua_server.cpp | 4 +- http/server/HttpScriptHandler.cpp | 63 ++++++++++++++++++++++++++++++ http/server/HttpScriptHandler.h | 44 +++++++++++++++++++++ http/server/HttpService.cpp | 16 ++++---- unittest/http_lua_handler_test.cpp | 17 ++++++-- 8 files changed, 142 insertions(+), 22 deletions(-) create mode 100644 http/server/HttpScriptHandler.cpp create mode 100644 http/server/HttpScriptHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d6716ed7b..6b549e2eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,7 +269,7 @@ if(WITH_EVPP) if(WITH_HTTP_SERVER) set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP_SERVER_HEADERS}) if(WITH_LUA) - set(LIBHV_HEADERS ${LIBHV_HEADERS} http/server/HttpLuaHandler.h) + set(LIBHV_HEADERS ${LIBHV_HEADERS} http/server/HttpScriptHandler.h http/server/HttpLuaHandler.h) endif() set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http/server) endif() diff --git a/Makefile b/Makefile index fa86c6603..1bc221b0d 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ ifeq ($(WITH_HTTP_SERVER), yes) LIBHV_HEADERS += $(HTTP_SERVER_HEADERS) LIBHV_SRCDIRS += http/server ifeq ($(WITH_LUA), yes) -LIBHV_HEADERS += http/server/HttpLuaHandler.h +LIBHV_HEADERS += http/server/HttpScriptHandler.h http/server/HttpLuaHandler.h endif endif @@ -119,7 +119,7 @@ prepare: libhv: $(MKDIR) lib ifeq ($(WITH_LUA), yes) - $(RM) http/server/HttpLuaHandler.o + $(RM) http/server/HttpLuaHandler.o http/server/HttpScriptHandler.o endif ifeq ($(BUILD_SHARED), yes) ifeq ($(BUILD_STATIC), yes) diff --git a/docs/cn/HttpLuaHandler.md b/docs/cn/HttpLuaHandler.md index 949617904..321d40935 100644 --- a/docs/cn/HttpLuaHandler.md +++ b/docs/cn/HttpLuaHandler.md @@ -1,6 +1,6 @@ # Http Lua Handler -`HttpLuaHandler` 允许 `HttpService` 调用 Lua 脚本里的 `handle(ctx)` 方法处理 HTTP 请求。它适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。 +`HttpScriptHandler` 允许 `HttpService` 调用脚本里的 `handle(ctx)` 方法处理 HTTP 请求。当前支持 `.lua` 脚本,适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。 该功能是可选模块,默认不编译。 @@ -37,13 +37,13 @@ C++: ```cpp #include "HttpServer.h" -#include "HttpLuaHandler.h" +#include "HttpScriptHandler.h" using namespace hv; int main() { HttpService router; - router.GET("/hello", LuaHandler("scripts/hello.lua")); + router.GET("/hello", HttpScriptHandler("scripts/hello.lua")); HttpServer server; server.port = 8080; @@ -66,15 +66,17 @@ function handle(ctx) end ``` +如果需要明确指定 Lua 引擎,也可以直接使用 `LuaHandler("scripts/hello.lua")`。推荐用户代码优先使用 `HttpScriptHandler`,这样后续增加 JS/Python 等脚本引擎时不用改路由注册代码。 + ## 目录映射 -`HttpService::Script(path, script_dir)` 可以把 URL 前缀映射到脚本目录: +`HttpService::Script(path, script_dir)` 可以把 URL 前缀映射到脚本目录,内部同样使用 `HttpScriptHandler`: ```cpp router.Script("/script/", "scripts"); ``` -访问 `/script/user?id=42` 时会调用 `scripts/user.lua`。访问 `/script/` 时会调用 `scripts/index.lua`。 +访问 `/script/user?id=42` 时会调用 `scripts/user.lua`。访问 `/script/` 时会调用 `scripts/index.lua`。当前目录映射只自动补 `.lua` 后缀。 目录映射默认支持 `GET`、`POST`、`PUT`、`DELETE`、`PATCH`。路径中包含 `..` 路径段时返回 `403`。 @@ -119,7 +121,7 @@ hv.now() ## 热更新 -`LuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。 +`HttpScriptHandler` 当前会把 `.lua` 文件转给 `LuaHandler`。`LuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。 重新加载失败时: diff --git a/examples/http_lua_server.cpp b/examples/http_lua_server.cpp index a0e14f6fb..aef7b1916 100644 --- a/examples/http_lua_server.cpp +++ b/examples/http_lua_server.cpp @@ -1,5 +1,5 @@ #include "HttpServer.h" -#include "HttpLuaHandler.h" +#include "HttpScriptHandler.h" using namespace hv; @@ -14,7 +14,7 @@ int main(int argc, char** argv) { } HttpService router; - router.GET("/hello", LuaHandler("examples/scripts/hello.lua")); + router.GET("/hello", HttpScriptHandler("examples/scripts/hello.lua")); router.Script("/script/", script_dir); HttpServer server; diff --git a/http/server/HttpScriptHandler.cpp b/http/server/HttpScriptHandler.cpp new file mode 100644 index 000000000..da29bf292 --- /dev/null +++ b/http/server/HttpScriptHandler.cpp @@ -0,0 +1,63 @@ +#include "HttpScriptHandler.h" + +#ifdef WITH_LUA + +#include "hbase.h" +#include "hstring.h" +#include "HttpLuaHandler.h" + +namespace hv { + +struct HttpScriptHandler::State { + LuaHandlerPtr lua_handler; +}; + +namespace { + +static bool filepath_has_suffix(const std::string& filepath, const char* suffix) { + std::string ext = hv_suffixname(filepath.c_str()); + return stricmp(ext.c_str(), suffix) == 0; +} + +} // namespace + +HttpScriptHandler::HttpScriptHandler(const char* filepath, const HttpScriptHandlerOptions& options) + : filepath_(filepath ? filepath : "") + , options_(options) + , state_(std::make_shared()) { +} + +HttpScriptHandler::HttpScriptHandler(const HttpScriptHandler& rhs) + : filepath_(rhs.filepath_) + , options_(rhs.options_) + , state_(std::make_shared()) { +} + +HttpScriptHandler& HttpScriptHandler::operator=(const HttpScriptHandler& rhs) { + if (this == &rhs) return *this; + filepath_ = rhs.filepath_; + options_ = rhs.options_; + state_ = std::make_shared(); + return *this; +} + +int HttpScriptHandler::operator()(const HttpContextPtr& ctx) { + if (filepath_has_suffix(filepath_, "lua")) { + if (!state_->lua_handler) { + LuaHandlerOptions lua_options; + lua_options.reload_on_change = options_.reload_on_change; + state_->lua_handler = std::make_shared(filepath_.c_str(), lua_options); + } + return (*state_->lua_handler)(ctx); + } + + if (ctx && ctx->response) { + ctx->response->status_code = HTTP_STATUS_NOT_IMPLEMENTED; + ctx->response->String("unsupported script type"); + } + return HTTP_STATUS_NOT_IMPLEMENTED; +} + +} // namespace hv + +#endif // WITH_LUA diff --git a/http/server/HttpScriptHandler.h b/http/server/HttpScriptHandler.h new file mode 100644 index 000000000..60b247c5c --- /dev/null +++ b/http/server/HttpScriptHandler.h @@ -0,0 +1,44 @@ +#ifndef HV_HTTP_SCRIPT_HANDLER_H_ +#define HV_HTTP_SCRIPT_HANDLER_H_ + +#include +#include + +#include "hexport.h" +#include "HttpContext.h" + +namespace hv { + +struct HV_EXPORT HttpScriptHandlerOptions { + bool reload_on_change; + + HttpScriptHandlerOptions() { + reload_on_change = true; + } +}; + +class HV_EXPORT HttpScriptHandler { +public: + HttpScriptHandler(const char* filepath, const HttpScriptHandlerOptions& options = HttpScriptHandlerOptions()); + HttpScriptHandler(const HttpScriptHandler& rhs); + HttpScriptHandler& operator=(const HttpScriptHandler& rhs); + + int operator()(const HttpContextPtr& ctx); + + const std::string& filepath() const { + return filepath_; + } + +private: + struct State; + + std::string filepath_; + HttpScriptHandlerOptions options_; + std::shared_ptr state_; +}; + +typedef std::shared_ptr HttpScriptHandlerPtr; + +} // namespace hv + +#endif // HV_HTTP_SCRIPT_HANDLER_H_ diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index c13326bcf..39f6b6d3b 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -2,7 +2,7 @@ #include "HttpMiddleware.h" #include "HttpRouter.h" #ifdef WITH_LUA -#include "HttpLuaHandler.h" +#include "HttpScriptHandler.h" #include "hpath.h" #include "hstring.h" #endif @@ -64,8 +64,8 @@ void HttpService::Script(const char* path, const char* script_dir) { root.pop_back(); } - std::shared_ptr > scripts = - std::make_shared >(); + std::shared_ptr > scripts = + std::make_shared >(); std::shared_ptr scripts_mutex = std::make_shared(); http_handler handler([route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) -> int { std::string path = ctx->path(); @@ -84,18 +84,18 @@ void HttpService::Script(const char* path, const char* script_dir) { script += ".lua"; } - LuaHandlerPtr lua_handler; + HttpScriptHandlerPtr script_handler; { std::lock_guard lock(*scripts_mutex); auto iter = scripts->find(script); if (iter == scripts->end()) { - lua_handler = std::make_shared(script.c_str()); - (*scripts)[script] = lua_handler; + script_handler = std::make_shared(script.c_str()); + (*scripts)[script] = script_handler; } else { - lua_handler = iter->second; + script_handler = iter->second; } } - return (*lua_handler)(ctx); + return (*script_handler)(ctx); }); AddRoute(route_path.c_str(), HTTP_GET, handler); diff --git a/unittest/http_lua_handler_test.cpp b/unittest/http_lua_handler_test.cpp index f4a6fd022..b761f5ee9 100644 --- a/unittest/http_lua_handler_test.cpp +++ b/unittest/http_lua_handler_test.cpp @@ -1,4 +1,4 @@ -#include "HttpLuaHandler.h" +#include "HttpScriptHandler.h" #include #include @@ -46,7 +46,7 @@ static void test_text_response() { " return ctx:text(ctx:method() .. ' ' .. ctx:path() .. ' ' .. ctx:header('X-Test'))\n" "end\n"); - hv::LuaHandler handler(script.c_str()); + hv::HttpScriptHandler handler(script.c_str()); HttpContextPtr ctx = make_ctx("GET", "/hello"); int status = handler(ctx); assert(status == 201); @@ -62,7 +62,7 @@ static void test_json_response() { " return ctx:json({ok=true, id=ctx:query('id')})\n" "end\n"); - hv::LuaHandler handler(script.c_str()); + hv::HttpScriptHandler handler(script.c_str()); HttpContextPtr ctx = make_ctx("POST", "/json"); int status = handler(ctx); assert(status == 200); @@ -96,10 +96,21 @@ static void test_script_dir_mapping() { assert(ctx->response->body == "script:42"); } +static void test_unknown_script_suffix() { + std::string script = write_script("unknown.py", "def handle(ctx): pass\n"); + + hv::HttpScriptHandler handler(script.c_str()); + HttpContextPtr ctx = make_ctx("GET", "/unknown"); + int status = handler(ctx); + assert(status == HTTP_STATUS_NOT_IMPLEMENTED); + assert(ctx->response->status_code == HTTP_STATUS_NOT_IMPLEMENTED); +} + int main() { test_text_response(); test_json_response(); test_script_dir_mapping(); + test_unknown_script_suffix(); printf("ALL http_lua_handler_test PASSED\n"); return 0; } From 4b6fc5968979241c1bf6ecbd40d280e629887534 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 16:12:57 +0800 Subject: [PATCH 04/12] refactor(examples): fold lua routes into http_server_test --- Makefile | 6 ------ docs/cn/HttpLuaHandler.md | 8 ++++---- examples/CMakeLists.txt | 6 ------ examples/http_lua_server.cpp | 26 -------------------------- examples/http_server_test.cpp | 13 +++++++++++++ 5 files changed, 17 insertions(+), 42 deletions(-) delete mode 100644 examples/http_lua_server.cpp diff --git a/Makefile b/Makefile index 1bc221b0d..ce19d974b 100644 --- a/Makefile +++ b/Makefile @@ -87,9 +87,6 @@ ifeq ($(WITH_HTTP), yes) EXAMPLES += wrk ifeq ($(WITH_HTTP_SERVER), yes) EXAMPLES += http_server_test websocket_server_test -ifeq ($(WITH_LUA), yes) -EXAMPLES += http_lua_server -endif endif ifeq ($(WITH_HTTP_CLIENT), yes) EXAMPLES += curl wget consul http_client_test websocket_client_test @@ -242,9 +239,6 @@ http_client_test: prepare websocket_server_test: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/server" SRCS="examples/websocket_server_test.cpp" -http_lua_server: prepare - $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/server" SRCS="examples/http_lua_server.cpp" WITH_LUA=yes - websocket_client_test: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) util cpputil evpp http http/client" SRCS="examples/websocket_client_test.cpp" diff --git a/docs/cn/HttpLuaHandler.md b/docs/cn/HttpLuaHandler.md index 321d40935..47797415c 100644 --- a/docs/cn/HttpLuaHandler.md +++ b/docs/cn/HttpLuaHandler.md @@ -12,7 +12,7 @@ Makefile: ```bash make libhv WITH_LUA=yes -make http_lua_server WITH_LUA=yes +make http_server_test WITH_LUA=yes make unittest WITH_LUA=yes ``` @@ -133,8 +133,8 @@ hv.now() ## 示例 ```bash -make http_lua_server WITH_LUA=yes -bin/http_lua_server 8080 examples/scripts -curl "http://127.0.0.1:8080/hello?id=42" +make http_server_test WITH_LUA=yes +bin/http_server_test 8080 +curl "http://127.0.0.1:8080/lua/hello?id=42" curl "http://127.0.0.1:8080/script/hello?id=42" ``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d74627093..324030d3f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -143,12 +143,6 @@ if(WITH_HTTP_SERVER) target_link_libraries(websocket_server_test ${HV_LIBRARIES}) list(APPEND EXAMPLES http_server_test websocket_server_test) - - if(WITH_LUA) - add_executable(http_lua_server http_lua_server.cpp) - target_link_libraries(http_lua_server ${HV_LIBRARIES}) - list(APPEND EXAMPLES http_lua_server) - endif() endif() if(WITH_HTTP_CLIENT) diff --git a/examples/http_lua_server.cpp b/examples/http_lua_server.cpp deleted file mode 100644 index aef7b1916..000000000 --- a/examples/http_lua_server.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "HttpServer.h" -#include "HttpScriptHandler.h" - -using namespace hv; - -int main(int argc, char** argv) { - int port = 8080; - const char* script_dir = "examples/scripts"; - if (argc > 1) { - port = atoi(argv[1]); - } - if (argc > 2) { - script_dir = argv[2]; - } - - HttpService router; - router.GET("/hello", HttpScriptHandler("examples/scripts/hello.lua")); - router.Script("/script/", script_dir); - - HttpServer server; - server.port = port; - server.service = &router; - server.setThreadNum(4); - server.run(); - return 0; -} diff --git a/examples/http_server_test.cpp b/examples/http_server_test.cpp index 0adb3aee4..9f851fc8a 100644 --- a/examples/http_server_test.cpp +++ b/examples/http_server_test.cpp @@ -8,6 +8,10 @@ #include "hthread.h" // import hv_gettid #include "hasync.h" // import hv::async +#ifdef WITH_LUA +#include "HttpScriptHandler.h" +#endif + using namespace hv; /* @@ -21,6 +25,8 @@ using namespace hv; * curl -v https://127.0.0.1:8443/ping --insecure * bin/curl -v http://127.0.0.1:8080/ping * bin/curl -v https://127.0.0.1:8443/ping + * curl -v "http://127.0.0.1:8080/lua/hello?id=42" # WITH_LUA=yes + * curl -v "http://127.0.0.1:8080/script/hello?id=42" # WITH_LUA=yes * */ #define TEST_HTTPS 0 @@ -88,6 +94,13 @@ int main(int argc, char** argv) { return ctx->send(resp.dump(2)); }); +#ifdef WITH_LUA + // curl -v "http://ip:port/lua/hello?id=42" + router.GET("/lua/hello", HttpScriptHandler("examples/scripts/hello.lua")); + // curl -v "http://ip:port/script/hello?id=42" + router.Script("/script/", "examples/scripts"); +#endif + // curl -v http://ip:port/async router.GET("/async", [](const HttpRequestPtr& req, const HttpResponseWriterPtr& writer) { writer->Begin(); From 32540323426146e6ae74bbe64dc51f0100c6c584 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 16:45:43 +0800 Subject: [PATCH 05/12] refactor(http): simplify script path traversal check --- http/server/HttpService.cpp | 131 +++++++++++++---------------- http/server/HttpService.h | 9 +- unittest/http_lua_handler_test.cpp | 18 ++++ 3 files changed, 81 insertions(+), 77 deletions(-) diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index 39f6b6d3b..25b4f4c21 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -5,6 +5,7 @@ #include "HttpScriptHandler.h" #include "hpath.h" #include "hstring.h" +#include #endif namespace hv { @@ -34,78 +35,6 @@ void HttpService::AddRoute(const char* path, http_method method, const http_hand method_handlers->push_back(http_method_handler(method, handler)); } -#ifdef WITH_LUA -static bool http_lua_script_path_is_safe(const std::string& path) { - if (path.empty()) return true; - size_t start = 0; - while (start <= path.size()) { - size_t end = path.find_first_of("/\\", start); - std::string segment = end == std::string::npos ? path.substr(start) : path.substr(start, end - start); - if (segment == "..") { - return false; - } - if (end == std::string::npos) break; - start = end + 1; - } - return true; -} - -void HttpService::Script(const char* path, const char* script_dir) { - std::string route_path(path ? path : ""); - if (route_path.empty()) return; - if (route_path.back() != '*') { - if (route_path.back() != '/') route_path += '/'; - route_path += '*'; - } - - std::string route_prefix = route_path.substr(0, route_path.size() - 1); - std::string root(script_dir ? script_dir : ""); - while (!root.empty() && (root.back() == '/' || root.back() == '\\')) { - root.pop_back(); - } - - std::shared_ptr > scripts = - std::make_shared >(); - std::shared_ptr scripts_mutex = std::make_shared(); - http_handler handler([route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) -> int { - std::string path = ctx->path(); - if (path.compare(0, route_prefix.size(), route_prefix) != 0) { - return HTTP_STATUS_NOT_FOUND; - } - std::string name = path.substr(route_prefix.size()); - if (name.empty()) { - name = "index"; - } - if (!http_lua_script_path_is_safe(name)) { - return HTTP_STATUS_FORBIDDEN; - } - std::string script = HPath::join(root, name); - if (HPath::suffixname(script).empty()) { - script += ".lua"; - } - - HttpScriptHandlerPtr script_handler; - { - std::lock_guard lock(*scripts_mutex); - auto iter = scripts->find(script); - if (iter == scripts->end()) { - script_handler = std::make_shared(script.c_str()); - (*scripts)[script] = script_handler; - } else { - script_handler = iter->second; - } - } - return (*script_handler)(ctx); - }); - - AddRoute(route_path.c_str(), HTTP_GET, handler); - AddRoute(route_path.c_str(), HTTP_POST, handler); - AddRoute(route_path.c_str(), HTTP_PUT, handler); - AddRoute(route_path.c_str(), HTTP_DELETE, handler); - AddRoute(route_path.c_str(), HTTP_PATCH, handler); -} -#endif - bool HttpService::HasRoutes() const { return router && !router->Empty(); } @@ -185,6 +114,64 @@ std::string HttpService::GetStaticFilepath(const char* path) { return filepath; } +#ifdef WITH_LUA +void HttpService::Script(const char* path, const char* script_dir) { + std::string route_path(path ? path : ""); + if (route_path.empty()) return; + if (route_path.back() != '*') { + if (route_path.back() != '/') route_path += '/'; + route_path += '*'; + } + + std::string route_prefix = route_path.substr(0, route_path.size() - 1); + std::string root(script_dir ? script_dir : ""); + while (!root.empty() && (root.back() == '/' || root.back() == '\\')) { + root.pop_back(); + } + + std::shared_ptr > scripts = + std::make_shared >(); + std::shared_ptr scripts_mutex = std::make_shared(); + http_ctx_handler script_handler_func = [route_prefix, root, scripts, scripts_mutex](const HttpContextPtr& ctx) -> int { + std::string path = ctx->path(); + if (path.compare(0, route_prefix.size(), route_prefix) != 0) { + return HTTP_STATUS_NOT_FOUND; + } + std::string name = path.substr(route_prefix.size()); + if (name.empty()) { + name = "index"; + } + if (strstr(path.c_str(), "/..") || strstr(path.c_str(), "\\..")) { + return HTTP_STATUS_FORBIDDEN; + } + std::string script = HPath::join(root, name); + if (HPath::suffixname(script).empty()) { + script += ".lua"; + } + + HttpScriptHandlerPtr script_handler; + { + std::lock_guard lock(*scripts_mutex); + auto iter = scripts->find(script); + if (iter == scripts->end()) { + script_handler = std::make_shared(script.c_str()); + (*scripts)[script] = script_handler; + } else { + script_handler = iter->second; + } + } + return (*script_handler)(ctx); + }; + + AddRoute(route_path.c_str(), HTTP_HEAD, http_handler(script_handler_func)); + AddRoute(route_path.c_str(), HTTP_GET, http_handler(script_handler_func)); + AddRoute(route_path.c_str(), HTTP_POST, http_handler(script_handler_func)); + AddRoute(route_path.c_str(), HTTP_PUT, http_handler(script_handler_func)); + AddRoute(route_path.c_str(), HTTP_DELETE, http_handler(script_handler_func)); + AddRoute(route_path.c_str(), HTTP_PATCH, http_handler(script_handler_func)); +} +#endif + void HttpService::Proxy(const char* path, const char* url) { proxies[path] = url; } diff --git a/http/server/HttpService.h b/http/server/HttpService.h index d1742543f..f52be5e0b 100644 --- a/http/server/HttpService.h +++ b/http/server/HttpService.h @@ -109,8 +109,6 @@ typedef std::unordered_map http_path_ha namespace hv { -class LuaHandler; - template class HttpRouter; @@ -193,9 +191,6 @@ struct HV_EXPORT HttpService { // router interface void AddRoute(const char* path, http_method method, const http_handler& handler); -#ifdef WITH_LUA - void Script(const char* path, const char* script_dir); -#endif // @param[in] full_path: {base_url}/path?query // @param[out] params: RESTful API /:field/ => params["field"] // @retval 0 OK, else HTTP_STATUS_NOT_FOUND, HTTP_STATUS_METHOD_NOT_ALLOWED @@ -210,6 +205,10 @@ struct HV_EXPORT HttpService { // @retval / => /var/www/html/index.html std::string GetStaticFilepath(const char* path); +#ifdef WITH_LUA + void Script(const char* path, const char* script_dir); +#endif + // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS void AllowCORS(); diff --git a/unittest/http_lua_handler_test.cpp b/unittest/http_lua_handler_test.cpp index b761f5ee9..f052a4ccf 100644 --- a/unittest/http_lua_handler_test.cpp +++ b/unittest/http_lua_handler_test.cpp @@ -96,6 +96,23 @@ static void test_script_dir_mapping() { assert(ctx->response->body == "script:42"); } +static void test_script_dir_parent_path_forbidden() { + hv::HttpService service; + service.Script("/api/", "tmp/http_lua_handler_test/api"); + + http_handler* handler = NULL; + std::map params; + int ret = service.GetRoute("/api/foo/..bar", HTTP_GET, &handler, params); + assert(ret == 0); + assert(handler != NULL); + assert(handler->ctx_handler != NULL); + + HttpContextPtr ctx = make_ctx("GET", "/api/foo/..bar"); + ctx->service = &service; + int status = handler->ctx_handler(ctx); + assert(status == HTTP_STATUS_FORBIDDEN); +} + static void test_unknown_script_suffix() { std::string script = write_script("unknown.py", "def handle(ctx): pass\n"); @@ -110,6 +127,7 @@ int main() { test_text_response(); test_json_response(); test_script_dir_mapping(); + test_script_dir_parent_path_forbidden(); test_unknown_script_suffix(); printf("ALL http_lua_handler_test PASSED\n"); return 0; From e66be550da293dc5d98a30439ea88fa719e46037 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 17:01:58 +0800 Subject: [PATCH 06/12] fix(http): make handler copy preserve callbacks --- http/server/HttpService.cpp | 7 +------ http/server/HttpService.h | 8 ++------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index 25b4f4c21..0d7f86e3e 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -163,12 +163,7 @@ void HttpService::Script(const char* path, const char* script_dir) { return (*script_handler)(ctx); }; - AddRoute(route_path.c_str(), HTTP_HEAD, http_handler(script_handler_func)); - AddRoute(route_path.c_str(), HTTP_GET, http_handler(script_handler_func)); - AddRoute(route_path.c_str(), HTTP_POST, http_handler(script_handler_func)); - AddRoute(route_path.c_str(), HTTP_PUT, http_handler(script_handler_func)); - AddRoute(route_path.c_str(), HTTP_DELETE, http_handler(script_handler_func)); - AddRoute(route_path.c_str(), HTTP_PATCH, http_handler(script_handler_func)); + Any(route_path.c_str(), http_handler(script_handler_func)); } #endif diff --git a/http/server/HttpService.h b/http/server/HttpService.h index f52be5e0b..37073d637 100644 --- a/http/server/HttpService.h +++ b/http/server/HttpService.h @@ -56,12 +56,8 @@ struct http_handler { http_handler(http_async_handler fn) : async_handler(std::move(fn)) {} http_handler(http_ctx_handler fn) : ctx_handler(std::move(fn)) {} http_handler(http_state_handler fn) : state_handler(std::move(fn)) {} - http_handler(const http_handler& rhs) - : sync_handler(std::move(const_cast(rhs).sync_handler)) - , async_handler(std::move(const_cast(rhs).async_handler)) - , ctx_handler(std::move(const_cast(rhs).ctx_handler)) - , state_handler(std::move(const_cast(rhs).state_handler)) - {} + http_handler(const http_handler& rhs) = default; + http_handler& operator=(const http_handler& rhs) = default; const http_handler& operator=(http_sync_handler fn) { sync_handler = std::move(fn); From 5e4744ec0f94583d6bb6bcb5c2ce1ab419664170 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 17:10:15 +0800 Subject: [PATCH 07/12] refactor(cmake): simplify lua detection --- CMakeLists.txt | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b549e2eb..758ae7831 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,30 +183,12 @@ endif() if(WITH_LUA) add_definitions(-DWITH_LUA) - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND) - foreach(LUA_MODULE lua5.5 lua-5.5 lua5.4 lua5.3 lua) - if(NOT LUA_FOUND) - pkg_check_modules(LUA QUIET ${LUA_MODULE}) - endif() - endforeach() - endif() - if(LUA_FOUND) - include_directories(${LUA_INCLUDE_DIRS}) + find_package(Lua) + if(Lua_FOUND) + include_directories(${LUA_INCLUDE_DIR}) set(LIBS ${LIBS} ${LUA_LIBRARIES}) - link_directories(${LUA_LIBRARY_DIRS}) else() - find_path(LUA_INCLUDE_DIR lua.h - PATHS /opt/homebrew/opt/lua/include /usr/local/opt/lua/include - PATH_SUFFIXES lua lua5.5 lua5.4 lua5.3) - find_library(LUA_LIBRARY - NAMES lua lua5.5 lua5.4 lua5.3 - PATHS /opt/homebrew/opt/lua/lib /usr/local/opt/lua/lib) - if(NOT LUA_INCLUDE_DIR OR NOT LUA_LIBRARY) - message(FATAL_ERROR "WITH_LUA=ON requires Lua development headers and library") - endif() - include_directories(${LUA_INCLUDE_DIR}) - set(LIBS ${LIBS} ${LUA_LIBRARY}) + set(LIBS ${LIBS} lua) endif() endif() From e4924375f004ed1ebb8fae16178b7483d2a3917a Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 18:05:34 +0800 Subject: [PATCH 08/12] feat(lua): dispatch handlers by http method --- Makefile.vars | 4 ++-- http/server/HttpLuaHandler.cpp | 8 ++++++- unittest/http_lua_handler_test.cpp | 34 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Makefile.vars b/Makefile.vars index 41eb5bdb4..935ba5492 100644 --- a/Makefile.vars +++ b/Makefile.vars @@ -8,8 +8,8 @@ INSTALL_INCDIR ?= $(PREFIX)/include/hv INSTALL_LIBDIR ?= $(PREFIX)/lib PKG_CONFIG ?= pkg-config -LUA_PKG_CONFIG ?= $(shell if command -v $(PKG_CONFIG) >/dev/null 2>&1; then $(PKG_CONFIG) --exists lua5.5 && echo lua5.5 || $(PKG_CONFIG) --exists lua-5.5 && echo lua-5.5 || $(PKG_CONFIG) --exists lua5.4 && echo lua5.4 || $(PKG_CONFIG) --exists lua5.3 && echo lua5.3 || $(PKG_CONFIG) --exists lua && echo lua; fi) -LUA_PREFIX ?= $(shell for dir in /opt/homebrew/opt/lua /usr/local/opt/lua /usr; do if [ -f "$$dir/include/lua/lua.h" ] || [ -f "$$dir/include/lua5.5/lua.h" ] || [ -f "$$dir/include/lua5.4/lua.h" ] || [ -f "$$dir/include/lua5.3/lua.h" ] || [ -f "$$dir/include/lua.h" ]; then echo $$dir; break; fi; done) +LUA_PKG_CONFIG ?= $(shell if command -v $(PKG_CONFIG) >/dev/null 2>&1 && $(PKG_CONFIG) --exists lua; then echo lua; fi) +LUA_PREFIX ?= $(shell for dir in /opt/homebrew/opt/lua /usr/local/opt/lua /usr; do if [ -f "$$dir/include/lua/lua.h" ] || [ -f "$$dir/include/lua.h" ]; then echo $$dir; break; fi; done) LUA_INCLUDE_DIR ?= $(shell if [ -n "$(LUA_PREFIX)" ]; then for dir in "$(LUA_PREFIX)/include/lua" "$(LUA_PREFIX)/include/lua5.5" "$(LUA_PREFIX)/include/lua5.4" "$(LUA_PREFIX)/include/lua5.3" "$(LUA_PREFIX)/include"; do if [ -f "$$dir/lua.h" ]; then echo $$dir; break; fi; done; fi) LUA_CFLAGS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --cflags $(LUA_PKG_CONFIG); elif [ -n "$(LUA_INCLUDE_DIR)" ]; then echo -I$(LUA_INCLUDE_DIR); fi) LUA_LIBS ?= $(shell if [ -n "$(LUA_PKG_CONFIG)" ]; then $(PKG_CONFIG) --libs $(LUA_PKG_CONFIG); elif [ -n "$(LUA_PREFIX)" ]; then echo -L$(LUA_PREFIX)/lib -llua; else echo -llua; fi) diff --git a/http/server/HttpLuaHandler.cpp b/http/server/HttpLuaHandler.cpp index 998df7321..f3d2756b5 100644 --- a/http/server/HttpLuaHandler.cpp +++ b/http/server/HttpLuaHandler.cpp @@ -368,7 +368,13 @@ bool LuaHandler::reloadIfNeeded() { } int LuaHandler::callLocked(const HttpContextPtr& ctx) { - lua_getglobal(L_, "handle"); + std::string handler_name = http_method_str(ctx->request->method); + tolower(handler_name); + lua_getglobal(L_, handler_name.c_str()); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + lua_getglobal(L_, "handle"); + } lua_push_ctx(L_, ctx); if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { std::string error = lua_tostring(L_, -1) ? lua_tostring(L_, -1) : "call handle failed"; diff --git a/unittest/http_lua_handler_test.cpp b/unittest/http_lua_handler_test.cpp index f052a4ccf..2f9919aee 100644 --- a/unittest/http_lua_handler_test.cpp +++ b/unittest/http_lua_handler_test.cpp @@ -71,6 +71,38 @@ static void test_json_response() { assert(ctx->response->body.find("\"id\": \"42\"") != std::string::npos); } +static void test_method_function_preferred() { + std::string script = write_script("method.lua", + "function get(ctx)\n" + " return ctx:text('get:' .. ctx:query('id'))\n" + "end\n" + "function handle(ctx)\n" + " return ctx:text('handle')\n" + "end\n"); + + hv::HttpScriptHandler handler(script.c_str()); + HttpContextPtr ctx = make_ctx("GET", "/method"); + int status = handler(ctx); + assert(status == 200); + assert(ctx->response->body == "get:42"); +} + +static void test_method_function_fallback_to_handle() { + std::string script = write_script("method_fallback.lua", + "function get(ctx)\n" + " return ctx:text('get')\n" + "end\n" + "function handle(ctx)\n" + " return ctx:text('fallback:' .. ctx:method())\n" + "end\n"); + + hv::HttpScriptHandler handler(script.c_str()); + HttpContextPtr ctx = make_ctx("POST", "/method"); + int status = handler(ctx); + assert(status == 200); + assert(ctx->response->body == "fallback:POST"); +} + static void test_script_dir_mapping() { write_script("api/user.lua", "function handle(ctx)\n" @@ -126,6 +158,8 @@ static void test_unknown_script_suffix() { int main() { test_text_response(); test_json_response(); + test_method_function_preferred(); + test_method_function_fallback_to_handle(); test_script_dir_mapping(); test_script_dir_parent_path_forbidden(); test_unknown_script_suffix(); From 1c161b77bedceed5a20204ca58329d5877dccadd Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 18:09:00 +0800 Subject: [PATCH 09/12] test(lua): add method examples to hello script --- examples/scripts/hello.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/scripts/hello.lua b/examples/scripts/hello.lua index 068da5657..a77d429fe 100644 --- a/examples/scripts/hello.lua +++ b/examples/scripts/hello.lua @@ -1,8 +1,23 @@ -function handle(ctx) - hv.log("lua request", ctx:method(), ctx:path()) +function get(ctx) + hv.log("lua get", ctx:path()) return ctx:json({ ok = true, + method = "GET", path = ctx:path(), id = ctx:query("id", "") }) end + +function post(ctx) + hv.log("lua post", ctx:path()) + return ctx:text("POST " .. ctx:body()) +end + +function handle(ctx) + hv.log("lua fallback", ctx:method(), ctx:path()) + return ctx:json({ + ok = true, + method = ctx:method(), + path = ctx:path() + }) +end From c7fdb1abcaf24bc7a13608cc49f3280211b40705 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 19:42:28 +0800 Subject: [PATCH 10/12] refactor(http): rename lua handler type --- docs/cn/HttpLuaHandler.md | 4 ++-- http/server/HttpLuaHandler.cpp | 22 +++++++++++----------- http/server/HttpLuaHandler.h | 28 ++++++++++++++-------------- http/server/HttpScriptHandler.cpp | 6 +++--- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/cn/HttpLuaHandler.md b/docs/cn/HttpLuaHandler.md index 47797415c..c7293c81f 100644 --- a/docs/cn/HttpLuaHandler.md +++ b/docs/cn/HttpLuaHandler.md @@ -66,7 +66,7 @@ function handle(ctx) end ``` -如果需要明确指定 Lua 引擎,也可以直接使用 `LuaHandler("scripts/hello.lua")`。推荐用户代码优先使用 `HttpScriptHandler`,这样后续增加 JS/Python 等脚本引擎时不用改路由注册代码。 +如果需要明确指定 Lua 引擎,也可以直接使用 `HttpLuaHandler("scripts/hello.lua")`。推荐用户代码优先使用 `HttpScriptHandler`,这样后续增加 JS/Python 等脚本引擎时不用改路由注册代码。 ## 目录映射 @@ -121,7 +121,7 @@ hv.now() ## 热更新 -`HttpScriptHandler` 当前会把 `.lua` 文件转给 `LuaHandler`。`LuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。 +`HttpScriptHandler` 当前会把 `.lua` 文件转给 `HttpLuaHandler`。`HttpLuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。 重新加载失败时: diff --git a/http/server/HttpLuaHandler.cpp b/http/server/HttpLuaHandler.cpp index f3d2756b5..c45ec220e 100644 --- a/http/server/HttpLuaHandler.cpp +++ b/http/server/HttpLuaHandler.cpp @@ -273,21 +273,21 @@ static time_t file_mtime(const std::string& filepath) { } // namespace -LuaHandler::LuaHandler(const char* filepath, const LuaHandlerOptions& options) +HttpLuaHandler::HttpLuaHandler(const char* filepath, const HttpLuaHandlerOptions& options) : filepath_(filepath ? filepath : "") , options_(options) , L_(NULL) , mtime_(0) { } -LuaHandler::LuaHandler(const LuaHandler& rhs) +HttpLuaHandler::HttpLuaHandler(const HttpLuaHandler& rhs) : filepath_(rhs.filepath_) , options_(rhs.options_) , L_(NULL) , mtime_(0) { } -LuaHandler& LuaHandler::operator=(const LuaHandler& rhs) { +HttpLuaHandler& HttpLuaHandler::operator=(const HttpLuaHandler& rhs) { if (this == &rhs) return *this; std::lock_guard lock(mutex_); closeLocked(); @@ -298,28 +298,28 @@ LuaHandler& LuaHandler::operator=(const LuaHandler& rhs) { return *this; } -LuaHandler::~LuaHandler() { +HttpLuaHandler::~HttpLuaHandler() { std::lock_guard lock(mutex_); closeLocked(); } -std::string LuaHandler::lastError() const { +std::string HttpLuaHandler::lastError() const { std::lock_guard lock(mutex_); return last_error_; } -void LuaHandler::setErrorLocked(const std::string& error) { +void HttpLuaHandler::setErrorLocked(const std::string& error) { last_error_ = error; } -void LuaHandler::closeLocked() { +void HttpLuaHandler::closeLocked() { if (L_) { lua_close(L_); L_ = NULL; } } -bool LuaHandler::loadLocked(time_t mtime) { +bool HttpLuaHandler::loadLocked(time_t mtime) { lua_State* L = luaL_newstate(); if (L == NULL) { setErrorLocked("luaL_newstate failed"); @@ -354,7 +354,7 @@ bool LuaHandler::loadLocked(time_t mtime) { return true; } -bool LuaHandler::reloadIfNeeded() { +bool HttpLuaHandler::reloadIfNeeded() { std::lock_guard lock(mutex_); time_t mtime = file_mtime(filepath_); if (mtime == 0) { @@ -367,7 +367,7 @@ bool LuaHandler::reloadIfNeeded() { return loadLocked(mtime) || L_ != NULL; } -int LuaHandler::callLocked(const HttpContextPtr& ctx) { +int HttpLuaHandler::callLocked(const HttpContextPtr& ctx) { std::string handler_name = http_method_str(ctx->request->method); tolower(handler_name); lua_getglobal(L_, handler_name.c_str()); @@ -406,7 +406,7 @@ int LuaHandler::callLocked(const HttpContextPtr& ctx) { return status; } -int LuaHandler::operator()(const HttpContextPtr& ctx) { +int HttpLuaHandler::operator()(const HttpContextPtr& ctx) { if (!ctx || !ctx->response || !ctx->request) { return HTTP_STATUS_INTERNAL_SERVER_ERROR; } diff --git a/http/server/HttpLuaHandler.h b/http/server/HttpLuaHandler.h index a9a51acf7..305c96c9c 100644 --- a/http/server/HttpLuaHandler.h +++ b/http/server/HttpLuaHandler.h @@ -13,20 +13,20 @@ struct lua_State; namespace hv { -struct HV_EXPORT LuaHandlerOptions { +struct HV_EXPORT HttpLuaHandlerOptions { bool reload_on_change; - LuaHandlerOptions() { + HttpLuaHandlerOptions() { reload_on_change = true; } }; -class HV_EXPORT LuaHandler { +class HV_EXPORT HttpLuaHandler { public: - LuaHandler(const char* filepath, const LuaHandlerOptions& options = LuaHandlerOptions()); - LuaHandler(const LuaHandler& rhs); - LuaHandler& operator=(const LuaHandler& rhs); - ~LuaHandler(); + HttpLuaHandler(const char* filepath, const HttpLuaHandlerOptions& options = HttpLuaHandlerOptions()); + HttpLuaHandler(const HttpLuaHandler& rhs); + HttpLuaHandler& operator=(const HttpLuaHandler& rhs); + ~HttpLuaHandler(); int operator()(const HttpContextPtr& ctx); @@ -44,15 +44,15 @@ class HV_EXPORT LuaHandler { void setErrorLocked(const std::string& error); private: - std::string filepath_; - LuaHandlerOptions options_; - lua_State* L_; - time_t mtime_; - std::string last_error_; - mutable std::mutex mutex_; + std::string filepath_; + HttpLuaHandlerOptions options_; + lua_State* L_; + time_t mtime_; + std::string last_error_; + mutable std::mutex mutex_; }; -typedef std::shared_ptr LuaHandlerPtr; +typedef std::shared_ptr HttpLuaHandlerPtr; } // namespace hv diff --git a/http/server/HttpScriptHandler.cpp b/http/server/HttpScriptHandler.cpp index da29bf292..73c33be63 100644 --- a/http/server/HttpScriptHandler.cpp +++ b/http/server/HttpScriptHandler.cpp @@ -9,7 +9,7 @@ namespace hv { struct HttpScriptHandler::State { - LuaHandlerPtr lua_handler; + HttpLuaHandlerPtr lua_handler; }; namespace { @@ -44,9 +44,9 @@ HttpScriptHandler& HttpScriptHandler::operator=(const HttpScriptHandler& rhs) { int HttpScriptHandler::operator()(const HttpContextPtr& ctx) { if (filepath_has_suffix(filepath_, "lua")) { if (!state_->lua_handler) { - LuaHandlerOptions lua_options; + HttpLuaHandlerOptions lua_options; lua_options.reload_on_change = options_.reload_on_change; - state_->lua_handler = std::make_shared(filepath_.c_str(), lua_options); + state_->lua_handler = std::make_shared(filepath_.c_str(), lua_options); } return (*state_->lua_handler)(ctx); } From a54c64e5325fb22985e9c9ebd899e0cff23c5331 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 20:32:43 +0800 Subject: [PATCH 11/12] chore: format --- .gitignore | 6 ++++++ CMakeLists.txt | 3 ++- Makefile | 3 --- config.ini | 3 ++- config.mk | 5 +++-- examples/http_server_test.cpp | 2 -- hconfig.h.in | 1 + 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 5822f761f..eb155d1da 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,9 @@ CMakeCache.txt cmake_install.cmake _codeql_build_dir/ _codeql_detected_source_root + +# agent +.claude/ +.superpowers/ +specs/ +docs/superpowers/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 758ae7831..61ed6e797 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ option(WITH_HTTP_SERVER "compile http/server" ON) option(WITH_HTTP_CLIENT "compile http/client" ON) option(WITH_MQTT "compile mqtt" OFF) option(WITH_REDIS "compile redis" OFF) -option(WITH_LUA "with Lua handler for http/server" OFF) option(ENABLE_UDS "Unix Domain Socket" OFF) option(USE_MULTIMAP "MultiMap" OFF) @@ -30,6 +29,8 @@ option(WITH_OPENSSL "with openssl library" OFF) option(WITH_GNUTLS "with gnutls library" OFF) option(WITH_MBEDTLS "with mbedtls library" OFF) +option(WITH_LUA "with lua library" OFF) + option(WITH_KCP "compile event/kcp" OFF) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/Makefile b/Makefile index ce19d974b..6eec0d1d2 100644 --- a/Makefile +++ b/Makefile @@ -115,9 +115,6 @@ prepare: libhv: $(MKDIR) lib -ifeq ($(WITH_LUA), yes) - $(RM) http/server/HttpLuaHandler.o http/server/HttpScriptHandler.o -endif ifeq ($(BUILD_SHARED), yes) ifeq ($(BUILD_STATIC), yes) $(MAKEF) TARGET=$@ TARGET_TYPE="SHARED|STATIC" SRCDIRS="$(LIBHV_SRCDIRS)" diff --git a/config.ini b/config.ini index 19f451713..9b2a0f642 100644 --- a/config.ini +++ b/config.ini @@ -18,7 +18,6 @@ WITH_HTTP_SERVER=yes WITH_HTTP_CLIENT=yes WITH_MQTT=no WITH_REDIS=no -WITH_LUA=no # features # base/hsocket.h: Unix Domain Socket @@ -37,6 +36,8 @@ WITH_NGHTTP2=no WITH_OPENSSL=no WITH_GNUTLS=no WITH_MBEDTLS=no +# for http lua handler +WITH_LUA=no # rudp WITH_KCP=no diff --git a/config.mk b/config.mk index 4283f5a71..403d97628 100644 --- a/config.mk +++ b/config.mk @@ -10,7 +10,7 @@ WITH_HTTP=yes WITH_HTTP_SERVER=yes WITH_HTTP_CLIENT=yes WITH_MQTT=no -WITH_REDIS=yes +WITH_REDIS=no ENABLE_UDS=no ENABLE_WINDUMP=no USE_MULTIMAP=no @@ -19,6 +19,7 @@ WITH_NGHTTP2=no WITH_OPENSSL=no WITH_GNUTLS=no WITH_MBEDTLS=no +WITH_LUA=no WITH_KCP=no WITH_IO_URING=no -CONFIG_DATE=20260711 \ No newline at end of file +CONFIG_DATE=20260728 diff --git a/examples/http_server_test.cpp b/examples/http_server_test.cpp index 9f851fc8a..4782b33a3 100644 --- a/examples/http_server_test.cpp +++ b/examples/http_server_test.cpp @@ -25,8 +25,6 @@ using namespace hv; * curl -v https://127.0.0.1:8443/ping --insecure * bin/curl -v http://127.0.0.1:8080/ping * bin/curl -v https://127.0.0.1:8443/ping - * curl -v "http://127.0.0.1:8080/lua/hello?id=42" # WITH_LUA=yes - * curl -v "http://127.0.0.1:8080/script/hello?id=42" # WITH_LUA=yes * */ #define TEST_HTTPS 0 diff --git a/hconfig.h.in b/hconfig.h.in index 39b55ad68..2bef47660 100644 --- a/hconfig.h.in +++ b/hconfig.h.in @@ -99,6 +99,7 @@ #cmakedefine WITH_WEPOLL 1 #cmakedefine WITH_KCP 1 #cmakedefine WITH_IO_URING 1 + #cmakedefine WITH_LUA 1 #endif // HV_CONFIG_H_ From 3b4bddc1a8d6da4a80feb97c23ed15b3974531f0 Mon Sep 17 00:00:00 2001 From: ithewei Date: Tue, 28 Jul 2026 21:15:06 +0800 Subject: [PATCH 12/12] fix(http): address lua handler review comments --- .github/workflows/CI.yml | 4 ++-- CMakeLists.txt | 2 +- http/server/HttpLuaHandler.cpp | 2 +- http/server/HttpScriptHandler.cpp | 6 ++++-- http/server/HttpService.cpp | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1024b02cd..e28183ee6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,8 +19,8 @@ jobs: - name: build run: | sudo apt update - sudo apt install libssl-dev libnghttp2-dev - ./configure --with-openssl --with-nghttp2 --with-kcp --with-mqtt --with-redis + sudo apt install libssl-dev libnghttp2-dev liblua5.4-dev + ./configure --with-openssl --with-nghttp2 --with-kcp --with-mqtt --with-redis --with-lua make libhv evpp - name: test diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ed6e797..d64326ae6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,7 +185,7 @@ endif() if(WITH_LUA) add_definitions(-DWITH_LUA) find_package(Lua) - if(Lua_FOUND) + if(LUA_FOUND) include_directories(${LUA_INCLUDE_DIR}) set(LIBS ${LIBS} ${LUA_LIBRARIES}) else() diff --git a/http/server/HttpLuaHandler.cpp b/http/server/HttpLuaHandler.cpp index c45ec220e..85a6268d2 100644 --- a/http/server/HttpLuaHandler.cpp +++ b/http/server/HttpLuaHandler.cpp @@ -192,7 +192,7 @@ static int lua_ctx_json(lua_State* L) { static int lua_ctx_gc(lua_State* L) { LuaHttpContext* holder = lua_check_ctx(L); - holder->ctx.~shared_ptr(); + holder->~LuaHttpContext(); return 0; } diff --git a/http/server/HttpScriptHandler.cpp b/http/server/HttpScriptHandler.cpp index 73c33be63..83ee2bae7 100644 --- a/http/server/HttpScriptHandler.cpp +++ b/http/server/HttpScriptHandler.cpp @@ -5,10 +5,12 @@ #include "hbase.h" #include "hstring.h" #include "HttpLuaHandler.h" +#include namespace hv { struct HttpScriptHandler::State { + std::once_flag lua_once; HttpLuaHandlerPtr lua_handler; }; @@ -43,11 +45,11 @@ HttpScriptHandler& HttpScriptHandler::operator=(const HttpScriptHandler& rhs) { int HttpScriptHandler::operator()(const HttpContextPtr& ctx) { if (filepath_has_suffix(filepath_, "lua")) { - if (!state_->lua_handler) { + std::call_once(state_->lua_once, [this]() { HttpLuaHandlerOptions lua_options; lua_options.reload_on_change = options_.reload_on_change; state_->lua_handler = std::make_shared(filepath_.c_str(), lua_options); - } + }); return (*state_->lua_handler)(ctx); } diff --git a/http/server/HttpService.cpp b/http/server/HttpService.cpp index 0d7f86e3e..da7b946c4 100644 --- a/http/server/HttpService.cpp +++ b/http/server/HttpService.cpp @@ -5,6 +5,7 @@ #include "HttpScriptHandler.h" #include "hpath.h" #include "hstring.h" +#include #include #endif