worked in the timeout and panic protection for AmWrap and friends
This commit is contained in:
+87
-23
@@ -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,39 +64,53 @@ 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 = e1.Error()
|
||||||
message = fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
|
}
|
||||||
} else {
|
} else {
|
||||||
message = e1.Error()
|
if e1 == nil {
|
||||||
|
message = fmt.Sprintf("%v", m1)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("%v (%v)", m1, e1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if er, ok := data.(error); ok {
|
||||||
|
message = er.Error()
|
||||||
} else {
|
} else {
|
||||||
if e1 == nil {
|
message = fmt.Sprintf("%v", data)
|
||||||
message = fmt.Sprintf("%v", m1)
|
|
||||||
} else {
|
|
||||||
message = fmt.Sprintf("%v (%v)", m1, e1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if er, ok := data.(error); ok {
|
|
||||||
message = er.Error()
|
|
||||||
} else {
|
|
||||||
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user