Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,9 @@ CMakeCache.txt
cmake_install.cmake
_codeql_build_dir/
_codeql_detected_source_root

# agent
.claude/
.superpowers/
specs/
docs/superpowers/
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,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")
Expand Down Expand Up @@ -180,6 +182,17 @@ if(WITH_IO_URING)
set(LIBS ${LIBS} uring)
endif()

if(WITH_LUA)
add_definitions(-DWITH_LUA)
find_package(Lua)
if(LUA_FOUND)
include_directories(${LUA_INCLUDE_DIR})
set(LIBS ${LIBS} ${LUA_LIBRARIES})
else()
set(LIBS ${LIBS} lua)
endif()
Comment thread
Copilot marked this conversation as resolved.
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)
Expand Down Expand Up @@ -238,6 +251,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/HttpScriptHandler.h http/server/HttpLuaHandler.h)
endif()
set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http/server)
endif()
if(WITH_HTTP_CLIENT)
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/HttpScriptHandler.h http/server/HttpLuaHandler.h
endif
endif

ifeq ($(WITH_HTTP_CLIENT), yes)
Expand Down Expand Up @@ -315,6 +318,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
Expand Down
6 changes: 6 additions & 0 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# LIBS="opencv_core opencv_highgui"
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-include config.mk
-include Makefile.vars

# VARIABLES
TARGET ?= test
Expand Down Expand Up @@ -160,6 +161,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))
Expand Down
7 changes: 7 additions & 0 deletions Makefile.vars
Original file line number Diff line number Diff line change
Expand Up @@ -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 && $(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)

BASE_HEADERS = base/hplatform.h\
\
base/hdef.h\
Expand Down
2 changes: 2 additions & 0 deletions config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -36,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
Expand Down
5 changes: 3 additions & 2 deletions config.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
CONFIG_DATE=20260728
1 change: 1 addition & 0 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- mqtt client
- redis client
- async DNS
- http lua handler

## Plan

Expand Down
140 changes: 140 additions & 0 deletions docs/cn/HttpLuaHandler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Http Lua Handler

`HttpScriptHandler` 允许 `HttpService` 调用脚本里的 `handle(ctx)` 方法处理 HTTP 请求。当前支持 `.lua` 脚本,适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。

该功能是可选模块,默认不编译。

## 编译

需要 Lua 5.3 或更新版本的开发库。

Makefile:

```bash
make libhv WITH_LUA=yes
make http_server_test WITH_LUA=yes
make unittest WITH_LUA=yes
```

如果系统没有 `pkg-config`,或者 Lua 安装在自定义路径,可以显式指定:

```bash
make libhv WITH_LUA=yes \
LUA_CFLAGS="-I/usr/local/include/lua" \
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 "HttpScriptHandler.h"

using namespace hv;

int main() {
HttpService router;
router.GET("/hello", HttpScriptHandler("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
```

如果需要明确指定 Lua 引擎,也可以直接使用 `HttpLuaHandler("scripts/hello.lua")`。推荐用户代码优先使用 `HttpScriptHandler`,这样后续增加 JS/Python 等脚本引擎时不用改路由注册代码。

## 目录映射

`HttpService::Script(path, script_dir)` 可以把 URL 前缀映射到脚本目录,内部同样使用 `HttpScriptHandler`:

```cpp
router.Script("/script/", "scripts");
```

访问 `/script/user?id=42` 时会调用 `scripts/user.lua`。访问 `/script/` 时会调用 `scripts/index.lua`。当前目录映射只自动补 `.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` 等模块。

## 热更新

`HttpScriptHandler` 当前会把 `.lua` 文件转给 `HttpLuaHandler`。`HttpLuaHandler` 会记录脚本文件的 `mtime`。每次请求前,如果文件被修改,会重新加载脚本。

重新加载失败时:

- 如果已有旧版本脚本,继续使用旧版本。
- 如果首次加载失败,返回 `500`。

这能避免线上脚本语法错误直接打断已有服务。

## 示例

```bash
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"
```
1 change: 1 addition & 0 deletions docs/cn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions examples/http_server_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/*
Expand Down Expand Up @@ -88,6 +92,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();
Expand Down
23 changes: 23 additions & 0 deletions examples/scripts/hello.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
2 changes: 2 additions & 0 deletions hconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@
#cmakedefine WITH_KCP 1
#cmakedefine WITH_IO_URING 1

#cmakedefine WITH_LUA 1

#endif // HV_CONFIG_H_
Loading
Loading