changes required to convert to Echo v5 #7

Merged
amy merged 5 commits from echo5-update into main 2026-05-03 14:59:50 -06:00
Showing only changes of commit e962c4d0c5 - Show all commits
+74 -10
View File
@@ -13,9 +13,11 @@
package ui package ui
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"runtime"
"time" "time"
"git.erbosoft.com/amy/amsterdam/config" "git.erbosoft.com/amy/amsterdam/config"
@@ -25,6 +27,23 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// panicRecoveryErr is the error created for panic recovery.
type panicRecoveryErr struct {
Phase string // phase of operation
Err error // error value
Stack []byte // stack trace
}
// Error returns the actual error string.
func (e *panicRecoveryErr) Error() string {
return fmt.Sprintf("[Panic Recovery in %s Phase] %s %s", e.Phase, e.Err.Error(), e.Stack)
}
// Unwrap returns the error "nested" inside this error.
func (e *panicRecoveryErr) Unwrap() error {
return e.Err
}
/* AmSendPageData sends page data to the output based on the command string. /* AmSendPageData sends page data to the output based on the command string.
* Parameters: * Parameters:
* ctxt - The Echo context from the request. * ctxt - The Echo context from the request.
@@ -45,21 +64,34 @@ import (
* Standard Go error status. * Standard Go error status.
*/ */
func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data any) error { func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data any) error {
// Enable panic recovery.
defer func() {
if r := recover(); r != nil {
if r == http.ErrAbortHandler {
panic(r)
}
tmperr, ok := r.(error)
if !ok {
tmperr = fmt.Errorf("%v", r)
}
stack := make([]byte, 4<<10)
length := runtime.Stack(stack, false)
log.Errorf("[Panic Recovery in SendData Phase] %s %s", tmperr.Error(), stack[:length])
}
}()
// Preprocess certain commands into different ones. // Preprocess certain commands into different ones.
httprc := http.StatusOK httprc := http.StatusOK
switch command { switch command {
case "error": case "error":
message := "" message := fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
if data == nil { if data != nil {
message = fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String()) if he, ok := data.(*echo.HTTPError); ok {
} else if he, ok := data.(*echo.HTTPError); ok {
httprc = he.Code httprc = he.Code
m1 := he.Message m1 := he.Message
e1 := he.Unwrap() e1 := he.Unwrap()
if m1 == "" { if m1 == "" {
if e1 == nil { if e1 != nil {
message = fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
} else {
message = e1.Error() message = e1.Error()
} }
} else { } else {
@@ -74,10 +106,11 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
} else { } else {
message = fmt.Sprintf("%v", data) message = fmt.Sprintf("%v", data)
} }
}
if httprc < 400 { if httprc < 400 {
httprc = http.StatusInternalServerError httprc = http.StatusInternalServerError
} }
amctxt.SetFrameTitle("Internal Server Error") amctxt.SetFrameTitle(http.StatusText(httprc))
amctxt.VarMap().Set("error", message) amctxt.VarMap().Set("error", message)
if tmp := amctxt.GetSession("lastKnownGood"); tmp != nil { if tmp := amctxt.GetSession("lastKnownGood"); tmp != nil {
amctxt.VarMap().Set("recovery", tmp) amctxt.VarMap().Set("recovery", tmp)
@@ -98,6 +131,11 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
} }
// Process commands. // Process commands.
oldreq := ctxt.Request()
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
defer cancel()
ctxt.SetRequest(oldreq.WithContext(ctx))
defer ctxt.SetRequest(oldreq)
var err error var err error
switch command { switch command {
case "bytes": case "bytes":
@@ -169,6 +207,32 @@ var expireTime string = lctime.Strftime("%c", time.Unix(1, 0))
// AmPageFunc is the definition for an Amsterdam "page function" that handles most of the work and defers to the wrapper for rendering. // AmPageFunc is the definition for an Amsterdam "page function" that handles most of the work and defers to the wrapper for rendering.
type AmPageFunc func(AmContext) (string, any) type AmPageFunc func(AmContext) (string, any)
// callWrappedPageFunc calls the specified page functon inside a wrapper that handles timeouts and panic recovery.
func callWrappedPageFunc(f AmPageFunc, ctxt *echo.Context, amctxt AmContext) (command string, arg any) {
defer func() {
if r := recover(); r != nil {
if r == http.ErrAbortHandler {
panic(r)
}
tmperr, ok := r.(error)
if !ok {
tmperr = fmt.Errorf("%v", r)
}
stack := make([]byte, 4<<10)
length := runtime.Stack(stack, false)
arg = &panicRecoveryErr{Phase: "PageFunc", Err: tmperr, Stack: stack[:length]}
command = "error"
}
}()
oldreq := ctxt.Request()
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
defer cancel()
ctxt.SetRequest(oldreq.WithContext(ctx))
defer ctxt.SetRequest(oldreq)
command, arg = f(amctxt)
return
}
/* AmWrap wraps the Amsterdam handler function in a wrapper that implements the spec for /* AmWrap wraps the Amsterdam handler function in a wrapper that implements the spec for
* Echo handler functions. * Echo handler functions.
* Parameters: * Parameters:
@@ -186,7 +250,7 @@ func AmWrap(myfunc AmPageFunc) echo.HandlerFunc {
c.Response().Header().Set("Expires", expireTime) c.Response().Header().Set("Expires", expireTime)
// Exec the wrapped function. // Exec the wrapped function.
command, arg := myfunc(ctxt) command, arg := callWrappedPageFunc(myfunc, c, ctxt)
if command != "error" && command != "ipban" { if command != "error" && command != "ipban" {
ctxt.SetSession("lastKnownGood", ctxt.Locator()) ctxt.SetSession("lastKnownGood", ctxt.Locator())
} }
@@ -225,7 +289,7 @@ func AmWithTempContext(c *echo.Context, fn AmPageFunc) error {
} }
// Call the function // Call the function
command, arg := fn(ctxt) command, arg := callWrappedPageFunc(fn, c, ctxt)
// Add the dynamic headers. // Add the dynamic headers.
c.Response().Header().Set("Pragma", "No-cache") c.Response().Header().Set("Pragma", "No-cache")