Replace the single LState + coroutine model with an LState pool to eliminate concurrent map read/write issues in gopher-lua. Each request now gets a completely independent LState with its own Global table. Key changes: - Add LStatePool for managing pooled LState instances - Remove shared Engine.L and coroutine-based execution - Simplify coroutine.go: remove yield handling, use direct PCall - Remove ngxRegisterMu lock (no longer needed with isolated LStates) - Update config.go: add LStatePoolInitialSize/MaxSize settings - Update tests to work with the new architecture Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
// Package lua 提供 Lua 脚本嵌入能力
|
||
package lua
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"github.com/valyala/fasthttp"
|
||
)
|
||
|
||
func TestLocationManagerRegister(t *testing.T) {
|
||
manager := NewLocationManager()
|
||
require.NotNil(t, manager)
|
||
|
||
// 注册 location
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.WriteString("test response")
|
||
}
|
||
manager.Register("/test", handler)
|
||
|
||
// 验证注册成功
|
||
manager.mu.Lock()
|
||
_, ok := manager.handlers["/test"]
|
||
manager.mu.Unlock()
|
||
assert.True(t, ok)
|
||
}
|
||
|
||
func TestLocationManagerCapture(t *testing.T) {
|
||
manager := NewLocationManager()
|
||
|
||
// 注册 location
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(200)
|
||
ctx.SetBodyString("hello from subrequest")
|
||
ctx.Response.Header.Set("X-Custom", "value")
|
||
}
|
||
manager.Register("/api/sub", handler)
|
||
|
||
// 创建父请求上下文
|
||
parentCtx := &fasthttp.RequestCtx{}
|
||
parentCtx.Request.SetRequestURI("/parent")
|
||
|
||
// 执行子请求
|
||
result, err := manager.Capture(parentCtx, "/api/sub", nil)
|
||
require.NoError(t, err)
|
||
require.NotNil(t, result)
|
||
|
||
assert.Equal(t, 200, result.Status)
|
||
assert.Equal(t, "hello from subrequest", string(result.Body))
|
||
assert.Equal(t, "value", result.Headers["X-Custom"])
|
||
}
|
||
|
||
func TestLocationManagerCaptureNotFound(t *testing.T) {
|
||
manager := NewLocationManager()
|
||
|
||
parentCtx := &fasthttp.RequestCtx{}
|
||
|
||
// 执行不存在的 location
|
||
result, err := manager.Capture(parentCtx, "/notexist", nil)
|
||
require.NoError(t, err)
|
||
require.NotNil(t, result)
|
||
|
||
assert.Equal(t, 404, result.Status)
|
||
}
|
||
|
||
func TestLocationManagerCaptureWithOptions(t *testing.T) {
|
||
manager := NewLocationManager()
|
||
|
||
// 注册 location
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(200)
|
||
ctx.WriteString("method: " + string(ctx.Method()) + ", body: " + string(ctx.PostBody()))
|
||
}
|
||
manager.Register("/echo", handler)
|
||
|
||
parentCtx := &fasthttp.RequestCtx{}
|
||
parentCtx.Request.SetRequestURI("/parent")
|
||
|
||
// 使用自定义选项
|
||
opts := map[string]any{
|
||
"method": "POST",
|
||
"body": "test body",
|
||
}
|
||
|
||
result, err := manager.Capture(parentCtx, "/echo", opts)
|
||
require.NoError(t, err)
|
||
require.NotNil(t, result)
|
||
|
||
assert.Equal(t, 200, result.Status)
|
||
assert.Contains(t, string(result.Body), "method: POST")
|
||
assert.Contains(t, string(result.Body), "body: test body")
|
||
}
|
||
|
||
func TestLocationLuaAPI(t *testing.T) {
|
||
engine, err := NewEngine(DefaultConfig())
|
||
require.NoError(t, err)
|
||
defer engine.Close()
|
||
|
||
L := engine.GetLStateForTest()
|
||
defer engine.PutLStateForTest(L)
|
||
|
||
// 注册 ngx.location API
|
||
ngx := L.NewTable()
|
||
L.SetGlobal("ngx", ngx)
|
||
RegisterLocationAPI(L, engine.LocationManager(), ngx)
|
||
|
||
// 测试 ngx.location.capture
|
||
err = L.DoString(`
|
||
-- 创建模拟的 location 结果
|
||
local result = ngx.location.capture("/test")
|
||
|
||
-- 验证结果结构
|
||
assert(result ~= nil)
|
||
assert(result.status ~= nil)
|
||
assert(result.body ~= nil)
|
||
`)
|
||
require.NoError(t, err)
|
||
}
|
||
|
||
// TestLocationCaptureWithParentRequest 测试子请求能够访问父请求数据
|
||
func TestLocationCaptureWithParentRequest(t *testing.T) {
|
||
engine, err := NewEngine(DefaultConfig())
|
||
require.NoError(t, err)
|
||
defer engine.Close()
|
||
|
||
// 注册 location handler,它会检查父请求的头信息
|
||
engine.LocationManager().Register("/api/sub", func(ctx *fasthttp.RequestCtx) {
|
||
// 检查是否继承了父请求的头
|
||
parentHeader := ctx.Request.Header.Peek("X-Parent-Header")
|
||
|
||
ctx.SetStatusCode(200)
|
||
ctx.SetBodyString("parent_header: " + string(parentHeader))
|
||
})
|
||
|
||
// 创建父请求上下文
|
||
parentCtx := &fasthttp.RequestCtx{}
|
||
parentCtx.Request.SetRequestURI("/parent")
|
||
parentCtx.Request.Header.Set("X-Parent-Header", "header_value")
|
||
|
||
// 使用 Capture 进行子请求
|
||
result, err := engine.LocationManager().Capture(parentCtx, "/api/sub", nil)
|
||
require.NoError(t, err)
|
||
require.NotNil(t, result)
|
||
|
||
// 验证子请求能够访问父请求数据(Headers 被继承)
|
||
assert.Equal(t, 200, result.Status)
|
||
assert.Contains(t, string(result.Body), "parent_header: header_value")
|
||
}
|