193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
package runner
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
|
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
)
|
|
|
|
// HTTPResponse represents an HTTP response from Lua
|
|
type HTTPResponse struct {
|
|
Status int `json:"status"`
|
|
Headers map[string]string `json:"headers"`
|
|
Body any `json:"body"`
|
|
Cookies []*http.Cookie `json:"-"`
|
|
}
|
|
|
|
// Response pool to reduce allocations
|
|
var responsePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &HTTPResponse{
|
|
Status: 200,
|
|
Headers: make(map[string]string, 8), // Pre-allocate with reasonable capacity
|
|
Cookies: make([]*http.Cookie, 0, 4), // Pre-allocate with reasonable capacity
|
|
}
|
|
},
|
|
}
|
|
|
|
// NewHTTPResponse creates a default HTTP response, potentially reusing one from the pool
|
|
func NewHTTPResponse() *HTTPResponse {
|
|
return responsePool.Get().(*HTTPResponse)
|
|
}
|
|
|
|
// ReleaseResponse returns the response to the pool after clearing its values
|
|
func ReleaseResponse(resp *HTTPResponse) {
|
|
if resp == nil {
|
|
return
|
|
}
|
|
|
|
// Clear all values to prevent data leakage
|
|
resp.Status = 200 // Reset to default
|
|
|
|
// Clear headers
|
|
for k := range resp.Headers {
|
|
delete(resp.Headers, k)
|
|
}
|
|
|
|
// Clear cookies
|
|
resp.Cookies = resp.Cookies[:0] // Keep capacity but set length to 0
|
|
|
|
// Clear body
|
|
resp.Body = nil
|
|
|
|
responsePool.Put(resp)
|
|
}
|
|
|
|
// LuaHTTPModule is the pure Lua implementation of the HTTP module
|
|
const LuaHTTPModule = `
|
|
-- Table to store response data
|
|
__http_responses = {}
|
|
|
|
-- HTTP module implementation
|
|
local http = {
|
|
-- Set HTTP status code
|
|
set_status = function(code)
|
|
if type(code) ~= "number" then
|
|
error("http.set_status: status code must be a number", 2)
|
|
end
|
|
|
|
local resp = __http_responses[1] or {}
|
|
resp.status = code
|
|
__http_responses[1] = resp
|
|
end,
|
|
|
|
-- Set HTTP header
|
|
set_header = function(name, value)
|
|
if type(name) ~= "string" or type(value) ~= "string" then
|
|
error("http.set_header: name and value must be strings", 2)
|
|
end
|
|
|
|
local resp = __http_responses[1] or {}
|
|
resp.headers = resp.headers or {}
|
|
resp.headers[name] = value
|
|
__http_responses[1] = resp
|
|
end
|
|
}
|
|
|
|
-- Set content type; set_header helper
|
|
http.set_content_type = function(content_type)
|
|
http.set_header("Content-Type", content_type)
|
|
end
|
|
|
|
-- Install HTTP module
|
|
_G.http = http
|
|
|
|
-- Override sandbox executor to clear HTTP responses
|
|
local old_execute_sandbox = __execute_sandbox
|
|
__execute_sandbox = function(bytecode, ctx)
|
|
-- Clear previous response for this thread
|
|
__http_responses[1] = nil
|
|
|
|
-- Execute the original function
|
|
local result = old_execute_sandbox(bytecode, ctx)
|
|
|
|
-- Return the result unchanged
|
|
return result
|
|
end
|
|
|
|
-- Make sure the HTTP module is accessible in sandbox
|
|
if __env_system and __env_system.base_env then
|
|
__env_system.base_env.http = http
|
|
end
|
|
`
|
|
|
|
// HTTPModuleInitFunc returns an initializer function for the HTTP module
|
|
func HTTPModuleInitFunc() StateInitFunc {
|
|
return func(state *luajit.State) error {
|
|
// Initialize pure Lua HTTP module
|
|
return state.DoString(LuaHTTPModule)
|
|
}
|
|
}
|
|
|
|
// GetHTTPResponse extracts the HTTP response from Lua state
|
|
func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) {
|
|
response := NewHTTPResponse()
|
|
|
|
// Get response table
|
|
state.GetGlobal("__http_responses")
|
|
if state.IsNil(-1) {
|
|
state.Pop(1)
|
|
ReleaseResponse(response) // Return unused response to pool
|
|
return nil, false
|
|
}
|
|
|
|
// Check for response at thread index
|
|
state.PushNumber(1)
|
|
state.GetTable(-2)
|
|
if state.IsNil(-1) {
|
|
state.Pop(2)
|
|
ReleaseResponse(response) // Return unused response to pool
|
|
return nil, false
|
|
}
|
|
|
|
// Get status
|
|
state.GetField(-1, "status")
|
|
if state.IsNumber(-1) {
|
|
response.Status = int(state.ToNumber(-1))
|
|
}
|
|
state.Pop(1)
|
|
|
|
// Get headers
|
|
state.GetField(-1, "headers")
|
|
if state.IsTable(-1) {
|
|
// Iterate through headers table
|
|
state.PushNil() // Start iteration
|
|
for state.Next(-2) {
|
|
// Stack has key at -2 and value at -1
|
|
if state.IsString(-2) && state.IsString(-1) {
|
|
key := state.ToString(-2)
|
|
value := state.ToString(-1)
|
|
response.Headers[key] = value
|
|
}
|
|
state.Pop(1) // Pop value, leave key for next iteration
|
|
}
|
|
}
|
|
state.Pop(1)
|
|
|
|
// Get cookies
|
|
state.GetField(-1, "cookies")
|
|
if state.IsTable(-1) {
|
|
// Iterate through cookies array
|
|
length := state.GetTableLength(-1)
|
|
for i := 1; i <= length; i++ {
|
|
state.PushNumber(float64(i))
|
|
state.GetTable(-2)
|
|
|
|
if state.IsTable(-1) {
|
|
cookie := extractCookie(state)
|
|
if cookie != nil {
|
|
response.Cookies = append(response.Cookies, cookie)
|
|
}
|
|
}
|
|
state.Pop(1)
|
|
}
|
|
}
|
|
state.Pop(1)
|
|
|
|
// Clean up
|
|
state.Pop(2) // Pop response table and __http_responses
|
|
|
|
return response, true
|
|
}
|