errorpages
This commit is contained in:
parent
474ed89dda
commit
be57476a8e
135
core/runner/http.go
Normal file
135
core/runner/http.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPResponse creates a default HTTP response
|
||||||
|
func NewHTTPResponse() *HTTPResponse {
|
||||||
|
return &HTTPResponse{
|
||||||
|
Status: 200,
|
||||||
|
Headers: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return response, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for response at thread index
|
||||||
|
state.PushNumber(1)
|
||||||
|
state.GetTable(-2)
|
||||||
|
if state.IsNil(-1) {
|
||||||
|
state.Pop(2)
|
||||||
|
return response, 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)
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
state.Pop(2) // Pop response table and __http_responses
|
||||||
|
|
||||||
|
return response, true
|
||||||
|
}
|
227
core/utils/errorpages.go
Normal file
227
core/utils/errorpages.go
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorPageConfig holds configuration for generating error pages
|
||||||
|
type ErrorPageConfig struct {
|
||||||
|
OverrideDir string // Directory where override templates are stored
|
||||||
|
DebugMode bool // Whether to show debug information
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorType represents HTTP error types
|
||||||
|
type ErrorType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorTypeNotFound ErrorType = 404
|
||||||
|
ErrorTypeMethodNotAllowed ErrorType = 405
|
||||||
|
ErrorTypeInternalError ErrorType = 500
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorPage generates an HTML error page based on the error type
|
||||||
|
// It first checks for an override file, and if not found, generates a default page
|
||||||
|
func ErrorPage(config ErrorPageConfig, errorType ErrorType, url string, errMsg string) string {
|
||||||
|
// Check for override file
|
||||||
|
if config.OverrideDir != "" {
|
||||||
|
var filename string
|
||||||
|
switch errorType {
|
||||||
|
case ErrorTypeNotFound:
|
||||||
|
filename = "404.html"
|
||||||
|
case ErrorTypeMethodNotAllowed:
|
||||||
|
filename = "405.html"
|
||||||
|
case ErrorTypeInternalError:
|
||||||
|
filename = "500.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
if filename != "" {
|
||||||
|
overridePath := filepath.Join(config.OverrideDir, filename)
|
||||||
|
if content, err := os.ReadFile(overridePath); err == nil {
|
||||||
|
return string(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No override found, generate default page
|
||||||
|
switch errorType {
|
||||||
|
case ErrorTypeNotFound:
|
||||||
|
return generateNotFoundHTML(url)
|
||||||
|
case ErrorTypeMethodNotAllowed:
|
||||||
|
return generateMethodNotAllowedHTML(url)
|
||||||
|
case ErrorTypeInternalError:
|
||||||
|
return generateInternalErrorHTML(config.DebugMode, url, errMsg)
|
||||||
|
default:
|
||||||
|
// Fallback to internal error
|
||||||
|
return generateInternalErrorHTML(config.DebugMode, url, errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFoundPage generates a 404 Not Found error page
|
||||||
|
func NotFoundPage(config ErrorPageConfig, url string) string {
|
||||||
|
return ErrorPage(config, ErrorTypeNotFound, url, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodNotAllowedPage generates a 405 Method Not Allowed error page
|
||||||
|
func MethodNotAllowedPage(config ErrorPageConfig, url string) string {
|
||||||
|
return ErrorPage(config, ErrorTypeMethodNotAllowed, url, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalErrorPage generates a 500 Internal Server Error page
|
||||||
|
func InternalErrorPage(config ErrorPageConfig, url string, errMsg string) string {
|
||||||
|
return ErrorPage(config, ErrorTypeInternalError, url, errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateInternalErrorHTML creates a 500 Internal Server Error page
|
||||||
|
func generateInternalErrorHTML(debugMode bool, url string, errMsg string) string {
|
||||||
|
errorMessages := []string{
|
||||||
|
"Oops! Something went wrong",
|
||||||
|
"Oh no! The server choked",
|
||||||
|
"Well, this is embarrassing...",
|
||||||
|
"Houston, we have a problem",
|
||||||
|
"Gremlins in the system",
|
||||||
|
"The server is taking a coffee break",
|
||||||
|
"Moonshark encountered a lunar eclipse",
|
||||||
|
"Our code monkeys are working on it",
|
||||||
|
"The server is feeling under the weather",
|
||||||
|
"500 Brain Not Found",
|
||||||
|
}
|
||||||
|
|
||||||
|
randomMessage := errorMessages[rand.Intn(len(errorMessages))]
|
||||||
|
codeContent := url
|
||||||
|
if errMsg != "" {
|
||||||
|
codeContent = url + " → " + errMsg
|
||||||
|
}
|
||||||
|
return generateErrorHTML("500", randomMessage, "Internal Server Error", debugMode, codeContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateNotFoundHTML creates a 404 Not Found error page
|
||||||
|
func generateNotFoundHTML(url string) string {
|
||||||
|
errorMessages := []string{
|
||||||
|
"Nothing to see here",
|
||||||
|
"This page is on vacation",
|
||||||
|
"The page is missing in action",
|
||||||
|
"This page has left the building",
|
||||||
|
"This page is in another castle",
|
||||||
|
"Sorry, we can't find that",
|
||||||
|
"The page you're looking for doesn't exist",
|
||||||
|
"Lost in space",
|
||||||
|
"That's a 404",
|
||||||
|
"Page not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
randomMessage := errorMessages[rand.Intn(len(errorMessages))]
|
||||||
|
return generateErrorHTML("404", randomMessage, "Page Not Found", false, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateMethodNotAllowedHTML creates a 405 Method Not Allowed error page
|
||||||
|
func generateMethodNotAllowedHTML(url string) string {
|
||||||
|
errorMessages := []string{
|
||||||
|
"That's not how this works",
|
||||||
|
"Method not allowed",
|
||||||
|
"Wrong way!",
|
||||||
|
"This method is not supported",
|
||||||
|
"You can't do that here",
|
||||||
|
"Sorry, wrong door",
|
||||||
|
"That method won't work here",
|
||||||
|
"Try a different approach",
|
||||||
|
"Access denied for this method",
|
||||||
|
"Method mismatch",
|
||||||
|
}
|
||||||
|
|
||||||
|
randomMessage := errorMessages[rand.Intn(len(errorMessages))]
|
||||||
|
return generateErrorHTML("405", randomMessage, "Method Not Allowed", false, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateErrorHTML creates the common HTML structure for error pages
|
||||||
|
func generateErrorHTML(errorCode, mainMessage, subMessage string, showDebugInfo bool, codeContent string) string {
|
||||||
|
errorHTML := `<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>` + errorCode + `</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-color: #2d2e2d;
|
||||||
|
--bg-gradient: linear-gradient(to bottom, #2d2e2d 0%, #000 100%);
|
||||||
|
--text-color: white;
|
||||||
|
--code-bg: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg-color: #f5f5f5;
|
||||||
|
--bg-gradient: linear-gradient(to bottom, #f5f5f5 0%, #ddd 100%);
|
||||||
|
--text-color: #333;
|
||||||
|
--code-bg: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
background: var(--bg-gradient);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.sub-message {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: var(--code-bg);
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
margin-top: 1rem;
|
||||||
|
max-width: 90vw;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h1>` + errorCode + `</h1>
|
||||||
|
<p>` + mainMessage + `</p>
|
||||||
|
<div class="sub-message">` + subMessage + `</div>`
|
||||||
|
|
||||||
|
if codeContent != "" {
|
||||||
|
errorHTML += `
|
||||||
|
<code>` + codeContent + `</code>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a note for debug mode
|
||||||
|
if showDebugInfo {
|
||||||
|
errorHTML += `
|
||||||
|
<p style="font-size: 0.9rem; margin-top: 1rem;">
|
||||||
|
An error occurred while processing your request.<br>
|
||||||
|
Please check the server logs for details.
|
||||||
|
</p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHTML += `
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
return errorHTML
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user