changes required to convert to Echo v5 #7
+34
-20
@@ -100,7 +100,10 @@ type AmConfig struct {
|
|||||||
SessionExpire string `yaml:"sessionExpire"`
|
SessionExpire string `yaml:"sessionExpire"`
|
||||||
UserAgreementResource string `yaml:"userAgreementResource"`
|
UserAgreementResource string `yaml:"userAgreementResource"`
|
||||||
PolicyResource string `yaml:"policyResource"`
|
PolicyResource string `yaml:"policyResource"`
|
||||||
|
FrameTemplate string `yaml:"frameTemplate"`
|
||||||
FooterTemplate string `yaml:"footerTemplate"`
|
FooterTemplate string `yaml:"footerTemplate"`
|
||||||
|
TopMenuId string `yaml:"topMenuId"`
|
||||||
|
FixedMenuId string `yaml:"fixedMenuId"`
|
||||||
DefaultCommunityLogo string `yaml:"defaultCommunityLogo"`
|
DefaultCommunityLogo string `yaml:"defaultCommunityLogo"`
|
||||||
DefaultUserPhoto string `yaml:"defaultUserPhoto"`
|
DefaultUserPhoto string `yaml:"defaultUserPhoto"`
|
||||||
WelcomeTitle string `yaml:"welcomeTitle"`
|
WelcomeTitle string `yaml:"welcomeTitle"`
|
||||||
@@ -148,6 +151,9 @@ type AmConfig struct {
|
|||||||
Prioritize string `yaml:"prioritize"`
|
Prioritize string `yaml:"prioritize"`
|
||||||
} `yaml:"countryList"`
|
} `yaml:"countryList"`
|
||||||
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
|
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
|
||||||
|
PanicRecovery struct {
|
||||||
|
StackDataSize string `yaml:"stackDataSize"`
|
||||||
|
} `yaml:"panicRecovery"`
|
||||||
} `yaml:"rendering"`
|
} `yaml:"rendering"`
|
||||||
Resources struct {
|
Resources struct {
|
||||||
ViewTemplateDir string `yaml:"viewTemplateDir"`
|
ViewTemplateDir string `yaml:"viewTemplateDir"`
|
||||||
@@ -168,9 +174,11 @@ type AmConfig struct {
|
|||||||
Tuning struct {
|
Tuning struct {
|
||||||
WorkerTasks int `yaml:"workerTasks"`
|
WorkerTasks int `yaml:"workerTasks"`
|
||||||
Timeouts struct {
|
Timeouts struct {
|
||||||
HttpRead int `yaml:"httpRead"`
|
HttpRead int `yaml:"httpRead"`
|
||||||
HttpWrite int `yaml:"httpWrite"`
|
HttpWrite int `yaml:"httpWrite"`
|
||||||
HttpIdle int `yaml:"httpIdle"`
|
HttpIdle int `yaml:"httpIdle"`
|
||||||
|
PageExecute int `yaml:"pageExecute"`
|
||||||
|
PageRender int `yaml:"pageRender"`
|
||||||
} `yaml:"timeouts"`
|
} `yaml:"timeouts"`
|
||||||
Queues struct {
|
Queues struct {
|
||||||
AuditWrites int `yaml:"auditWrites"`
|
AuditWrites int `yaml:"auditWrites"`
|
||||||
@@ -207,22 +215,23 @@ func (c *AmConfig) ExPath(path string) string {
|
|||||||
|
|
||||||
// AmConfigComputed is the configuration values which are "computed" based only on values in AmConfig and CommandLine.
|
// AmConfigComputed is the configuration values which are "computed" based only on values in AmConfig and CommandLine.
|
||||||
type AmConfigComputed struct {
|
type AmConfigComputed struct {
|
||||||
DebugMode bool // are we in debug mode?
|
DebugMode bool // are we in debug mode?
|
||||||
LogLevel string // the logging level
|
LogLevel string // the logging level
|
||||||
Listen string // listen address
|
Listen string // listen address
|
||||||
DatabaseDriver string // name of database driver
|
DatabaseDriver string // name of database driver
|
||||||
DatabaseHost string // hostname for database
|
DatabaseHost string // hostname for database
|
||||||
DatabaseUser string // user name for database
|
DatabaseUser string // user name for database
|
||||||
DatabasePassword string // password for database
|
DatabasePassword string // password for database
|
||||||
DatabaseName string // database name
|
DatabaseName string // database name
|
||||||
MailHost string // SMTP host
|
MailHost string // SMTP host
|
||||||
MailPort int // SMTP port
|
MailPort int // SMTP port
|
||||||
MailTLS string // SMTP TLS setting
|
MailTLS string // SMTP TLS setting
|
||||||
MailAuthType string // SMTP auth type
|
MailAuthType string // SMTP auth type
|
||||||
MailUser string // SMTP user name
|
MailUser string // SMTP user name
|
||||||
MailPassword string // SMTP password
|
MailPassword string // SMTP password
|
||||||
UploadMaxSize int32 // maximum upload size in bytes
|
PanicRecoveryStack int32 // stack size for panic recovery
|
||||||
UploadNoCompress map[string]bool // which upload types are not compressed?
|
UploadMaxSize int32 // maximum upload size in bytes
|
||||||
|
UploadNoCompress map[string]bool // which upload types are not compressed?
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed default.yaml
|
//go:embed default.yaml
|
||||||
@@ -403,7 +412,12 @@ func SetupConfig() {
|
|||||||
GlobalComputedConfig.MailAuthType = util.IIF(CommandLine.MailAuthType != "", CommandLine.MailAuthType, GlobalConfig.Email.AuthType)
|
GlobalComputedConfig.MailAuthType = util.IIF(CommandLine.MailAuthType != "", CommandLine.MailAuthType, GlobalConfig.Email.AuthType)
|
||||||
GlobalComputedConfig.MailUser = util.IIF(CommandLine.MailUser != "", CommandLine.MailUser, GlobalConfig.Email.User)
|
GlobalComputedConfig.MailUser = util.IIF(CommandLine.MailUser != "", CommandLine.MailUser, GlobalConfig.Email.User)
|
||||||
GlobalComputedConfig.MailPassword = util.IIF(CommandLine.MailPassword != "", CommandLine.MailPassword, GlobalConfig.Email.Password)
|
GlobalComputedConfig.MailPassword = util.IIF(CommandLine.MailPassword != "", CommandLine.MailPassword, GlobalConfig.Email.Password)
|
||||||
tmp, err := humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
tmp, err := humanize.ParseBytes(GlobalConfig.Rendering.PanicRecovery.StackDataSize)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
GlobalComputedConfig.PanicRecoveryStack = int32(tmp)
|
||||||
|
tmp, err = humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ site:
|
|||||||
sessionExpire: "3h"
|
sessionExpire: "3h"
|
||||||
userAgreementResource: "useragreement.html"
|
userAgreementResource: "useragreement.html"
|
||||||
policyResource: "policy.html"
|
policyResource: "policy.html"
|
||||||
|
frameTemplate: "frame.jet"
|
||||||
footerTemplate: "footer.jet"
|
footerTemplate: "footer.jet"
|
||||||
|
topMenuId: "top"
|
||||||
|
fixedMenuId: "fixed"
|
||||||
defaultCommunityLogo: "/img/builtin/default-community.jpg"
|
defaultCommunityLogo: "/img/builtin/default-community.jpg"
|
||||||
defaultUserPhoto: "/img/builtin/no-user.png"
|
defaultUserPhoto: "/img/builtin/no-user.png"
|
||||||
welcomeTitle: "Welcome to Amsterdam"
|
welcomeTitle: "Welcome to Amsterdam"
|
||||||
@@ -72,6 +75,8 @@ rendering:
|
|||||||
countryList:
|
countryList:
|
||||||
prioritize: US
|
prioritize: US
|
||||||
veniceCompatibleImageURLs: false
|
veniceCompatibleImageURLs: false
|
||||||
|
panicRecovery:
|
||||||
|
stackDataSize: "4 KiB"
|
||||||
resources:
|
resources:
|
||||||
viewTemplateDir: ""
|
viewTemplateDir: ""
|
||||||
dialogTemplateDir: ""
|
dialogTemplateDir: ""
|
||||||
@@ -95,6 +100,8 @@ tuning:
|
|||||||
httpRead: 30
|
httpRead: 30
|
||||||
httpWrite: 30
|
httpWrite: 30
|
||||||
httpIdle: 120
|
httpIdle: 120
|
||||||
|
pageExecute: 15
|
||||||
|
pageRender: 15
|
||||||
queues:
|
queues:
|
||||||
auditWrites: 16
|
auditWrites: 16
|
||||||
contextRecycle: 16
|
contextRecycle: 16
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ func setupEcho() *echo.Echo {
|
|||||||
e.Renderer = &ui.TemplateRenderer{}
|
e.Renderer = &ui.TemplateRenderer{}
|
||||||
e.HTTPErrorHandler = AmErrorHandler
|
e.HTTPErrorHandler = AmErrorHandler
|
||||||
if !config.CommandLine.DebugPanic {
|
if !config.CommandLine.DebugPanic {
|
||||||
e.Use(middleware.RecoverWithConfig(middleware.DefaultRecoverConfig))
|
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
||||||
|
StackSize: int(config.GlobalComputedConfig.PanicRecoveryStack),
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
log.Warn("WARNING: --debug-panic in effect - DO NOT use this in production!")
|
log.Warn("WARNING: --debug-panic in effect - DO NOT use this in production!")
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-71
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Amsterdam Web Communities System
|
* Amsterdam Web Communities System
|
||||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@@ -44,6 +44,51 @@ func (e *panicRecoveryErr) Unwrap() error {
|
|||||||
return e.Err
|
return e.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doFrameRender renders the outer frame template with an inner template.
|
||||||
|
func doFrameRender(ctxt *echo.Context, amctxt AmContext, statusCode int, innerPage string) error {
|
||||||
|
if amctxt.FrameTitle() == "" {
|
||||||
|
log.Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
||||||
|
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
||||||
|
}
|
||||||
|
amctxt.VarMap().Set("__innerPage", innerPage)
|
||||||
|
menus := make([]*MenuDefinition, 2)
|
||||||
|
switch amctxt.LeftMenu() {
|
||||||
|
case "top":
|
||||||
|
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||||
|
case "community":
|
||||||
|
comm := amctxt.CurrentCommunity()
|
||||||
|
if comm != nil {
|
||||||
|
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
menus[0] = md
|
||||||
|
} else {
|
||||||
|
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
||||||
|
}
|
||||||
|
menus[1] = AmMenu(config.GlobalConfig.Site.FixedMenuId)
|
||||||
|
amctxt.VarMap().Set("__leftMenus", menus)
|
||||||
|
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
||||||
|
if err != nil {
|
||||||
|
ad = &database.Advert{
|
||||||
|
AdId: -1,
|
||||||
|
ImagePath: "",
|
||||||
|
PathStyle: -1,
|
||||||
|
Caption: nil,
|
||||||
|
LinkURL: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
amctxt.VarMap().Set("__bannerad", ad)
|
||||||
|
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
||||||
|
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
||||||
|
amctxt.VarMap().Set("__suppressLogin", true)
|
||||||
|
}
|
||||||
|
return ctxt.Render(statusCode, config.GlobalConfig.Site.FrameTemplate, amctxt)
|
||||||
|
}
|
||||||
|
|
||||||
/* 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.
|
||||||
@@ -65,20 +110,22 @@ func (e *panicRecoveryErr) Unwrap() error {
|
|||||||
*/
|
*/
|
||||||
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.
|
// Enable panic recovery.
|
||||||
defer func() {
|
if !config.CommandLine.DebugPanic {
|
||||||
if r := recover(); r != nil {
|
defer func() {
|
||||||
if r == http.ErrAbortHandler {
|
if r := recover(); r != nil {
|
||||||
panic(r)
|
if r == http.ErrAbortHandler {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
tmperr, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
tmperr = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||||
|
length := runtime.Stack(stack, false)
|
||||||
|
log.Errorf("[Panic Recovery in SendData Phase] %s %s", tmperr.Error(), stack[:length])
|
||||||
}
|
}
|
||||||
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
|
||||||
@@ -132,7 +179,7 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
|
|||||||
|
|
||||||
// Process commands.
|
// Process commands.
|
||||||
oldreq := ctxt.Request()
|
oldreq := ctxt.Request()
|
||||||
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageRender)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ctxt.SetRequest(oldreq.WithContext(ctx))
|
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||||
defer ctxt.SetRequest(oldreq)
|
defer ctxt.SetRequest(oldreq)
|
||||||
@@ -151,47 +198,7 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
|
|||||||
case "template":
|
case "template":
|
||||||
err = ctxt.Render(httprc, data.(string), amctxt)
|
err = ctxt.Render(httprc, data.(string), amctxt)
|
||||||
case "framed":
|
case "framed":
|
||||||
if amctxt.FrameTitle() == "" {
|
err = doFrameRender(ctxt, amctxt, httprc, data.(string))
|
||||||
log.Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
|
||||||
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
|
||||||
}
|
|
||||||
amctxt.VarMap().Set("__innerPage", data)
|
|
||||||
menus := make([]*MenuDefinition, 2)
|
|
||||||
switch amctxt.LeftMenu() {
|
|
||||||
case "top":
|
|
||||||
menus[0] = AmMenu("top")
|
|
||||||
case "community":
|
|
||||||
comm := amctxt.CurrentCommunity()
|
|
||||||
if comm != nil {
|
|
||||||
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
menus[0] = md
|
|
||||||
} else {
|
|
||||||
menus[0] = AmMenu("top")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
|
||||||
}
|
|
||||||
menus[1] = AmMenu("fixed")
|
|
||||||
amctxt.VarMap().Set("__leftMenus", menus)
|
|
||||||
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
|
||||||
if err != nil {
|
|
||||||
ad = &database.Advert{
|
|
||||||
AdId: -1,
|
|
||||||
ImagePath: "",
|
|
||||||
PathStyle: -1,
|
|
||||||
Caption: nil,
|
|
||||||
LinkURL: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
amctxt.VarMap().Set("__bannerad", ad)
|
|
||||||
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
|
||||||
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
|
||||||
amctxt.VarMap().Set("__suppressLogin", true)
|
|
||||||
}
|
|
||||||
err = ctxt.Render(httprc, "frame.jet", amctxt)
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("AmSendPageData(): unknown rendering type: %s", command)
|
err = fmt.Errorf("AmSendPageData(): unknown rendering type: %s", command)
|
||||||
}
|
}
|
||||||
@@ -209,23 +216,25 @@ type AmPageFunc func(AmContext) (string, any)
|
|||||||
|
|
||||||
// callWrappedPageFunc calls the specified page functon inside a wrapper that handles timeouts and panic recovery.
|
// 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) {
|
func callWrappedPageFunc(f AmPageFunc, ctxt *echo.Context, amctxt AmContext) (command string, arg any) {
|
||||||
defer func() {
|
if !config.CommandLine.DebugPanic {
|
||||||
if r := recover(); r != nil {
|
defer func() {
|
||||||
if r == http.ErrAbortHandler {
|
if r := recover(); r != nil {
|
||||||
panic(r)
|
if r == http.ErrAbortHandler {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
tmperr, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
tmperr = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||||
|
length := runtime.Stack(stack, false)
|
||||||
|
arg = &panicRecoveryErr{Phase: "PageFunc", Err: tmperr, Stack: stack[:length]}
|
||||||
|
command = "error"
|
||||||
}
|
}
|
||||||
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()
|
oldreq := ctxt.Request()
|
||||||
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageExecute)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ctxt.SetRequest(oldreq.WithContext(ctx))
|
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||||
defer ctxt.SetRequest(oldreq)
|
defer ctxt.SetRequest(oldreq)
|
||||||
|
|||||||
Reference in New Issue
Block a user