uhttp lua

uhttp lua

uhttp-mod-lua

  • 安裝 uhttpd-mod-lua
  • 透過 uci 設定
  • 重啟 uhttpd
安裝 uhttpd-mod-lua
1
2
3
4
5
6
7
opkg update
opkg install uhttpd-mod-lua
uci set uhttpd.main.lua_prefix=/lua
uci set uhttpd.main.lua_handler=/root/test.lua
uci commit uhttpd
/etc/init.d/uhttpd restart
wget -qO- http://127.0.0.1/lua/

這份 lua file,比較要注意的地方是

  • 回呼函數名稱一定要是 handle_request
  • 未定義但是可以用的變數 uhttpd
test.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function handle_request(env)
uhttpd.send("Status: 200 OK\r\n")
uhttpd.send("Content-Type: text/plain\r\n\r\n")

--[[
env 底下的 key,以及對應型態
REQUEST_METHOD (String)
REQUEST_URI (String)
SCRIPT_NAME (String)
QUERY_STRING (String)
PATH_INFO (String)
HTTP_VERSION (Number)
SERVER_PROTOCOL (String)
REMOTE_ADDR (String)
REMOTE_PORT (Int)
SERVER_ADDR (String)
SERVER_PORT (Int)
CONTENT_LENGTH (Number)
CONTENT_TYPE (String)
headers (Table)
--]]


uhttpd.send("REQUEST_METHOD".." "..tostring(env.REQUEST_METHOD).."\n")
uhttpd.send("REQUEST_URI".." "..tostring(env.REQUEST_URI).."\n")
uhttpd.send("SCRIPT_NAME".." "..tostring(env.SCRIPT_NAME).."\n")
uhttpd.send("QUERY_STRING".." "..tostring(env.QUERY_STRING).."\n")
uhttpd.send("PATH_INFO".." "..tostring(env.PATH_INFO).."\n")
uhttpd.send("SERVER_PROTOCOL".." "..tostring(env.SERVER_PROTOCOL).."\n")
uhttpd.send("REMOTE_ADDR".." "..tostring(env.REMOTE_ADDR).."\n")
uhttpd.send("SERVER_ADDR".." "..tostring(env.SERVER_ADDR).."\n")
uhttpd.send("CONTENT_TYPE".." "..tostring(env.CONTENT_TYPE).."\n")

uhttpd.send("HTTP_VERSION".." "..tostring(env.HTTP_VERSION).."\n")
uhttpd.send("REMOTE_PORT".." "..tostring(env.REMOTE_PORT).."\n")
uhttpd.send("SERVER_PORT".." "..tostring(env.SERVER_PORT).."\n")
uhttpd.send("CONTENT_LENGTH".." "..tostring(env.CONTENT_LENGTH).."\n")

uhttpd.send("Headers start\n")
uhttpd.send("-------------\n")
for k,v in pairs(env.headers) do
uhttpd.send(k.." "..v.."\n")
end
uhttpd.send("-------------\n")

local len,data = uhttpd.recv(1024)
if len > 0 then
uhttpd.send("length:"..tostring(len).."\n")
uhttpd.send("data:"..data.."\n")
end

uhttpd.send("Hello world.\n")

end

uhttpd-lua.h

再回來看 uhttpd-lua.h, 提供給 lua 當回乎函數 handle_request 似乎有點著落

uhttpd-lua.hlink
1
2
3
4
5
6
7
8
9
#define UH_LUA_CALLBACK         "handle_request"

lua_State * uh_lua_init();

void uh_lua_request(
struct client *cl, struct http_request *req, lua_State *L
)
;


void uh_lua_close(lua_State *L);

uhttpd-lua.c

從這邊可以看到 lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd");,提供 lua 全域變數 uhttpd (table),並提供以下欄位

  • recv (Int -> (Int,String))
  • send (String -> Int)
  • sendc (String -> Int)
  • urldecode (String -> String)

c 的動作 :

  • luaL_loadfile(L, handler) 把 handler 當成 function 載入,並把 function 放入堆疊 (handler=/root/test.lua)
  • lua_pcall(L, 0, 0, 0) 執行堆疊上第一個,0,0,0 分別是 輸入參數個數,return 個數,錯誤處理 function
  • lua_getglobal(L, UH_LUA_CALLBACK),將 handle_request 放入堆疊
  • lua_isfunction(L, -1),確認堆疊最上層是 function
c 調用 lua function
1
2
3
4
5
6
7
8
-- lua_pcall(L, 2, 3, 0)
-- | y | 3
-- | x | 2
-- | function xy | 1

function xy (x,y)
return x,y,x+y
end
uhttpd-lua.clink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
lua_State * uh_lua_init(const char *handler) {
lua_State *L = lua_open();
const char *err_str = NULL;

/* Load standard libaries */
luaL_openlibs(L);

/* build uhttpd api table */
lua_newtable(L);

/* register global send and receive functions */
lua_pushcfunction(L, uh_lua_recv);
lua_setfield(L, -2, "recv");

lua_pushcfunction(L, uh_lua_send);
lua_setfield(L, -2, "send");

lua_pushcfunction(L, uh_lua_sendc);
lua_setfield(L, -2, "sendc");

lua_pushcfunction(L, uh_lua_urldecode);
lua_setfield(L, -2, "urldecode");

/* _G.uhttpd = { ... } */
lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd");
// ...
switch( luaL_loadfile(L, handler) ) {
default:
/* compile Lua handler */
switch( lua_pcall(L, 0, 0, 0) ) {
default:
/* test handler function */
lua_getglobal(L, UH_LUA_CALLBACK);

if( ! lua_isfunction(L, -1) ){
fprintf(stderr,
"Lua handler provides no " UH_LUA_CALLBACK "(), unable to continue\n");
exit(1);
}

lua_pop(L, 1);
break;
}
break;
}
return L;
}

uhttpd.c

  • 解析 arg
  • 透過 -c 讀取 config 檔案(或預設位置 /etc/httpd.conf)
  • 讀取 so 檔案,(dlopen("uhttpd_lua.so", RTLD_LAZY | RTLD_GLOBAL))
  • 執行 uhttpd-lua.c 的 lua_init
uhttpd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void uh_config_parse(struct config *conf)
{

// ...
const char *path = conf->file ? conf->file : "/etc/httpd.conf";
// ...
}

int main (int argc, char **argv)
{

// ...
/* init Lua runtime if handler is specified */
if( conf.lua_handler )
{
/* default lua prefix */
if( ! conf.lua_prefix )
conf.lua_prefix = "/lua";

conf.lua_state = conf.lua_init(conf.lua_handler);
}
// ...
}

uhttpd

/etc/init.d/uhttpd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# lrwxrwxrwx    1 root     root            16 Nov 20  2015 S50uhttpd -> ../init.d/uhttpd
start_service() {
config_load uhttpd
config_foreach start_instance uhttpd
}

start_instance() {
local cfg="$1"
# ...
append_arg "$cfg" config "-c"
}

append_arg() {
local cfg="$1"
local var="$2"
local opt="$3"
local def="$4"
local val

config_get val "$cfg" "$var"
[ -n "$val" -o -n "$def" ] && procd_append_param command "$opt" "${val:-$def}"
}

開機流程