-
Notifications
You must be signed in to change notification settings - Fork 4
/
act.go
94 lines (85 loc) · 3 KB
/
act.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// Package controller - Content managed by Project Forge, see [projectforge.md] for details.
package controller
import (
"fmt"
"net/http"
"time"
"github.com/pkg/errors"
"projectforge.dev/projectforge/app"
"projectforge.dev/projectforge/app/controller/cutil"
"projectforge.dev/projectforge/app/lib/telemetry"
"projectforge.dev/projectforge/app/site"
"projectforge.dev/projectforge/app/util"
)
type ActFn func(as *app.State, ps *cutil.PageState) (string, error)
func Act(key string, w http.ResponseWriter, r *http.Request, f ActFn) {
as := _currentAppState
wc := cutil.NewWriteCounter(w)
ps := cutil.LoadPageState(as, wc, r, key, _currentAppRootLogger)
if err := initAppRequest(as, ps); err != nil {
ps.Logger.Warnf("%+v", err)
}
actComplete(key, as, ps, wc, r, f)
}
func ActSite(key string, w http.ResponseWriter, r *http.Request, f func(as *app.State, ps *cutil.PageState) (string, error)) {
as := _currentSiteState
wc := cutil.NewWriteCounter(w)
ps := cutil.LoadPageState(as, wc, r, key, _currentAppRootLogger)
ps.Menu = site.Menu(ps.Context, as, ps.Profile, ps.Logger)
if err := initSiteRequest(as, ps); err != nil {
ps.Logger.Warnf("%+v", err)
}
actComplete(key, as, ps, ps.W, r, f)
}
func actComplete(key string, as *app.State, ps *cutil.PageState, w *cutil.WriteCounter, r *http.Request, f ActFn) {
err := ps.Clean(r, as)
if err != nil {
ps.Logger.Warnf("error while cleaning request, somehow: %+v", err)
}
status := http.StatusOK
cutil.WriteCORS(w)
var redir string
logger := ps.Logger
ctx := ps.Context
if !telemetry.SkipControllerMetrics {
var span *telemetry.Span
ctx, span, logger = telemetry.StartSpan(ps.Context, "controller."+key, ps.Logger)
defer span.Complete()
}
logger = logger.With("path", r.URL.Path, "method", ps.Method, "status", status)
ps.Context = ctx
if ps.ForceRedirect == "" || ps.ForceRedirect == r.URL.Path {
redir, err = safeRun(f, as, ps)
if err != nil {
redir, err = handleError(key, as, ps, r, err)
if err != nil {
ps.Logger.Warnf("unable to handle error: %+v", err)
}
}
} else {
redir = ps.ForceRedirect
}
if redir != "" {
w.Header().Set("Location", redir)
w.WriteHeader(http.StatusFound)
}
elapsedMillis := float64((util.TimeCurrentNanos()-ps.Started.UnixNano())/int64(time.Microsecond)) / float64(1000)
ps.ResponseBytes = w.Count()
defer ps.Close()
w.Header().Set("Server-Timing", fmt.Sprintf("server:dur=%.3f", elapsedMillis))
logger = logger.With("elapsed", elapsedMillis)
logger.Debugf("processed request in [%.3fms] (render: %.3fms, response: %s)", elapsedMillis, ps.RenderElapsed, util.ByteSizeSI(ps.ResponseBytes))
}
func safeRun(f func(as *app.State, ps *cutil.PageState) (string, error), as *app.State, ps *cutil.PageState) (s string, e error) {
defer func() {
if rec := recover(); rec != nil {
if recoverErr, ok := rec.(error); ok {
e = errors.Wrap(recoverErr, "panic")
} else {
e = errors.Errorf("controller encountered panic recovery of type [%T]: %s", rec, fmt.Sprint(rec))
}
}
}()
s, e = f(as, ps)
return
}