Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangnanscu committed Dec 3, 2024
1 parent ab48c04 commit 0c84abb
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 42 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# lua-resty-router

High performance and productive router for Openresty.
Elegant, performant and productive router for Openresty.

# Router

Expand Down Expand Up @@ -98,3 +98,7 @@ assert(tree:match('/v1','GET') == 'v1')
assert(tree:match('/v2','POST') == 'v2')
assert(tree:match('/v2','GET') == nil)
```

## reference

- [nginx api for lua](https://github.com/openresty/lua-nginx-module?tab=readme-ov-file#nginx-api-for-lua)
91 changes: 91 additions & 0 deletions app.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
local Router = require('resty.router')

local router = Router:new()

-- 测试插件
router:use(function(ctx)
ctx.plugin_executed = true
ctx:yield()
end)

-- 测试事件
router:on('success', function(ctx)
ngx.log(ngx.INFO, "请求成功")
end)

router:on('error', function(ctx)
ngx.log(ngx.INFO, "请求失败")
end)

-- 1. 测试静态路径
router:get("/hello", function()
return "Hello World"
end)

-- 2. 测试JSON响应
router:get("/json", function()
return { message = "success", code = 0 }
end)

-- 3. 测试动态路径参数
router:get("/users/#id", function(ctx)
return {
id = ctx.params.id,
type = "number"
}
end)

router:get("/users/:name", function(ctx)
return {
name = ctx.params.name,
type = "string"
}
end)

-- 4. 测试正则路径
router:get("/version/<ver>\\d+\\.\\d+", function(ctx)
return {
version = ctx.params.ver
}
end)

-- 5. 测试通配符
router:get("/files/*path", function(ctx)
return {
path = ctx.params.path
}
end)

-- 6. 测试多个HTTP方法
router:post("/users", function(ctx)
return { method = "POST" }
end)

router:put("/users/#id", function(ctx)
return { method = "PUT", id = ctx.params.id }
end)

-- 7. 测试错误处理
router:get("/error", function()
error("测试错误")
end)

-- 8. 测试状态码
router:get("/404", function()
return nil, "Not Found", 404
end)

-- 9. 测试HTML响应
router:get("/html", function()
return "<h1>Hello HTML</h1>"
end)

-- 10. 测试函数返回
router:get("/func", function()
return function()
ngx.say("function called")
return true
end
end)

return router
31 changes: 31 additions & 0 deletions conf/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
worker_processes auto;
pid logs/nginx.pid;

error_log logs/error.log;
events {
worker_connections 1024;
}

http {
default_type application/json;
access_log logs/access.log;

lua_package_path './lib/?.lua;;';
lua_code_cache on;
uwsgi_temp_path /tmp;
fastcgi_temp_path /tmp;
client_body_temp_path /tmp;
proxy_temp_path /tmp;
scgi_temp_path /tmp;
server {
listen 8080;
server_name localhost;
charset utf-8;

location / {
content_by_lua_block {
require("app"):run()
}
}
}
}
2 changes: 1 addition & 1 deletion dist.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = lua-resty-router
abstract = High performance and productive router for Openresty
abstract = Elegant, performant and productive router for Openresty
author = Nan Xiang(@xiangnanscu)
is_original = yes
license = mit
Expand Down
114 changes: 79 additions & 35 deletions lib/resty/router.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local encode = require "cjson.safe".encode
local json = require "cjson.safe"
local bit = bit
local gmatch = string.gmatch
local ipairs = ipairs
Expand All @@ -15,6 +15,15 @@ local ngx_var = ngx.var
local string_format = string.format
local xpcall = xpcall
local spawn = ngx.thread.spawn
local ngx_req = ngx.req
local decode = json.decode
local encode = json.encode
local get_post_args = ngx.req.get_post_args
local read_body = ngx.req.read_body
local get_body_data = ngx.req.get_body_data
local assert = assert
local rawget = rawget
local setmetatable = setmetatable

local method_bitmask = {
GET = 1, -- 2^0
Expand Down Expand Up @@ -351,100 +360,131 @@ function Router:emit(event_key, ...)
end
end

local Response = {
header = ngx_header,
get_headers = ngx_req.get_headers,
}
Response.__index = Response

local Context = {
yield = coroutine.yield
}
---@private
function Context.__index(self, key)
if Context[key] ~= nil then
return Context[key]
elseif ngx_req[key] ~= nil then
return ngx_req[key]
elseif key == 'response' then
return setmetatable({}, Response)
elseif key == 'query' then
self.query = ngx_req.get_uri_args()
return rawget(self, 'query')
else
return nil
end
end

---@return table
function Router:create_context()
return setmetatable({ yield = coroutine.yield }, ngx.req)
return setmetatable({}, Context)
end

---@param path string
---@param method string
---@return boolean
---@overload fun(string, string): boolean
function Router:dispatch(path, method)
local handler, params_or_code = self:match(path, method)
if handler == nil then
return self:fail('match route failed', {}, params_or_code)
end

-- before plugins
local ctx = self:create_context()
if params_or_code then
ctx.params = params_or_code
end

for _, plugin in ipairs(self.plugins) do
local co = coroutine.create(function()
plugin(ctx)
end)
local ok, err = resume(co)
if not ok then
return self:fail(err, ctx, 500)
return self:fail(ctx, err)
end
if coroutine.status(co) == "suspended" then
ctx[#ctx + 1] = co
end
end

-- match route
local handler, params_or_code = self:match(path, method)
if handler == nil then
return self:fail(ctx, 'match route failed', params_or_code)
end
if params_or_code then
ctx.params = params_or_code
end
if type(handler) == 'string' then
return self:ok(handler, ctx)
return self:ok(ctx, handler)
else
---@diagnostic disable-next-line: param-type-mismatch
local ok, result, err = xpcall(handler, trace_back, ctx)
local ok, result, err_or_okcode, errcode = xpcall(handler, trace_back, ctx)
if not ok then
return self:fail(result, ctx, 500)
return self:fail(ctx, result)
elseif result == nil then
return self:fail(err, ctx, 500)
if err_or_okcode ~= nil then
return self:fail(ctx, err_or_okcode, errcode)
elseif rawget(ctx, 'response') then
local code = ctx.response.status or 200
return self:finish(ctx, code, ngx.exit, code)
else
return self:fail(ctx, 'no response')
end
else
local resp_type = type(result)
if resp_type == 'table' or resp_type == 'boolean' or resp_type == 'number' then
local json, encode_err = encode(result)
if not json then
return self:fail(encode_err, ctx)
local response_json, encode_err = encode(result)
if not response_json then
return self:fail(ctx, encode_err)
else
ngx_header.content_type = 'application/json; charset=utf-8'
return self:ok(json, ctx)
return self:ok(ctx, response_json, err_or_okcode)
end
elseif resp_type == 'string' then
if byte(result) == 60 then -- 60 is ASCII value of '<'
ngx_header.content_type = 'text/html; charset=utf-8'
else
ngx_header.content_type = 'text/plain; charset=utf-8'
end
return self:ok(result, ctx)
return self:ok(ctx, result, err_or_okcode)
elseif type(result) == 'function' then
ok, result, err = xpcall(result, trace_back)
ok, result, err_or_okcode, errcode = xpcall(result, trace_back, ctx)
if not ok then
return self:fail(result, ctx, 500)
return self:fail(ctx, result)
elseif result == nil then
return self:fail(err, ctx, 500)
return self:finish(ctx, 500, ngx.exit, 500)
else
return self:finish(200, ctx)
return self:finish(ctx, 200, ngx.exit, 200)
end
else
return self:fail('invalid response type: ' .. resp_type)
return self:fail(ctx, 'invalid response type: ' .. resp_type)
end
end
end
end

function Router:ok(body, ctx, code)
function Router:ok(ctx, body, code)
ngx_print(body)
code = code or 200
return self:finish(code, ctx, ngx.exit, code)
return self:finish(ctx, code, ngx.exit, code)
end

function Router:fail(err, ctx, code)
function Router:fail(ctx, err, code)
ngx_print(err)
code = code or 500
return self:finish(code, ctx, ngx.exit, code)
return self:finish(ctx, code, ngx.exit, code)
end

function Router:redirect(ctx, uri, code)
code = code or 302
return self:finish(code, ctx, ngx.redirect, uri, code)
return self:finish(ctx, code, ngx.redirect, uri, code)
end

function Router:exec(uri, args, ctx)
return self:finish(200, ctx, ngx.exec, uri, args)
function Router:exec(ctx, uri, args)
return self:finish(ctx, ngx.OK, ngx.exec, uri, args)
end

local function get_event_key(events, code)
Expand All @@ -467,7 +507,7 @@ local function get_event_key(events, code)
end
end

function Router:finish(code, ctx, ngx_func, ...)
function Router:finish(ctx, code, ngx_func, ...)
local events = self.events
if events[code] then
self:emit(code, ctx)
Expand All @@ -485,4 +525,8 @@ function Router:finish(code, ctx, ngx_func, ...)
return ngx_func(...)
end

function Router:run()
return self:dispatch(ngx_var.document_uri, ngx_var.request_method)
end

return Router
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
"pull": "uname -s | grep -q Darwin && yarn _pull || while true; do timeout 10 yarn _pull && break; done",
"resty": "/usr/local/openresty/bin/resty -I template -I resty_modules/lualib -I resty_modules/site/lualib --main-conf 'env NODE_ENV;' --http-conf 'lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;'",
"busted": "yarn resty -I spec template/bin/busted.lua -o TAP",
"lab": "pnpm build && ./outfile.cjs",
"sync": "cp lib/resty/router.lua template/lib/resty/router.lua",
"lab": "yarn sync && pnpm build && ./outfile.cjs",
"format": "prettier --write .",
"build": "zx ./scripts/build.mjs",
"snapshot": "zx ./scripts/snapshot.mjs",
"pretest": "run-s build snapshot",
"pretest2": "run-s build snapshot",
"prepublishOnly": "pnpm build",
"prepublishOnly2": "zx ./scripts/prepublish.mjs",
"test": "resty test_spec.lua"
"nginx": "nginx -p . -c conf/nginx.conf",
"test": "yarn nginx; ./test.sh ; yarn nginx -s stop"
},
"author": "",
"license": "ISC",
Expand Down
10 changes: 10 additions & 0 deletions template/app.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local Router = require('resty.router')

local router = Router:new()

router:get('/', function(ctx)
ctx.response.status = 200
ctx.response.body = 'Hello, World!'
end)

return router
2 changes: 1 addition & 1 deletion template/conf/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ http {
include mime.types;
default_type application/json;

access_log logs/access.log main;
access_log logs/access.log;

sendfile on;
tcp_nopush on;
Expand Down
Loading

0 comments on commit 0c84abb

Please sign in to comment.