templating: Rework timers
This commit is contained in:
parent
66eb13df30
commit
2d163a4dad
9 changed files with 232 additions and 46 deletions
1
go.mod
1
go.mod
|
|
@ -10,7 +10,6 @@ require (
|
||||||
github.com/gliderlabs/ssh v0.3.5
|
github.com/gliderlabs/ssh v0.3.5
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/gorilla/csrf v1.7.1
|
github.com/gorilla/csrf v1.7.1
|
||||||
github.com/gorilla/mux v1.8.0
|
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
|
|
||||||
3
go.sum
3
go.sum
|
|
@ -20,8 +20,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
|
|
@ -99,6 +97,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
||||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -119,18 +119,18 @@ func (control *Cron) Start(ctx context.Context, signal <-chan struct{}) <-chan s
|
||||||
run()
|
run()
|
||||||
zerolog.Ctx(ctx).Debug().Msg("Cron() beginnning scheduling")
|
zerolog.Ctx(ctx).Debug().Msg("Cron() beginnning scheduling")
|
||||||
|
|
||||||
timer := timex.NewTimer()
|
t := timex.NewTimer()
|
||||||
|
defer timex.ReleaseTimer(t)
|
||||||
for {
|
for {
|
||||||
timex.StopTimer(timer)
|
timex.StopTimer(t)
|
||||||
timer.Reset(control.Config.CronInterval)
|
t.Reset(control.Config.CronInterval)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-t.C:
|
||||||
zerolog.Ctx(ctx).Debug().Msg("Cron() timer fired")
|
zerolog.Ctx(ctx).Debug().Msg("Cron() timer fired")
|
||||||
case <-signal:
|
case <-signal:
|
||||||
zerolog.Ctx(ctx).Debug().Msg("Cron() received signal")
|
zerolog.Ctx(ctx).Debug().Msg("Cron() received signal")
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
timex.StopTimer(timer)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package templating
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -11,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/pools"
|
"github.com/FAU-CDI/wisski-distillery/pkg/pools"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
@ -30,8 +32,21 @@ func (tpl *Template[C]) Template() *template.Template {
|
||||||
return baseTemplate
|
return baseTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LazyContext is like the lazy context
|
||||||
|
func (tpl *Template[C]) LazyContext(r *http.Request, f func() (C, error), funcs ...FlagFunc) (ctx *tContext[C]) {
|
||||||
|
ctx = tpl.context(r, funcs...)
|
||||||
|
ctx.startLazy(f)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
// Context generates the context to pass to an instance of the template returned by Template.
|
// Context generates the context to pass to an instance of the template returned by Template.
|
||||||
func (tpl *Template[C]) Context(r *http.Request, c C, funcs ...FlagFunc) (ctx *tContext[C]) {
|
func (tpl *Template[C]) Context(r *http.Request, c C, funcs ...FlagFunc) (ctx *tContext[C]) {
|
||||||
|
ctx = tpl.context(r, funcs...)
|
||||||
|
ctx.start(c, nil) // setup the request
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpl *Template[C]) context(r *http.Request, funcs ...FlagFunc) (ctx *tContext[C]) {
|
||||||
// create a new context
|
// create a new context
|
||||||
ctx = new(tContext[C])
|
ctx = new(tContext[C])
|
||||||
|
|
||||||
|
|
@ -45,16 +60,9 @@ func (tpl *Template[C]) Context(r *http.Request, c C, funcs ...FlagFunc) (ctx *t
|
||||||
// generate the rest of the options
|
// generate the rest of the options
|
||||||
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, tpl.p.funcs...)
|
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, tpl.p.funcs...)
|
||||||
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, funcs...)
|
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, funcs...)
|
||||||
|
ctx.updateEmbedded = tpl.p.hasRuntimeFlagsEmbed
|
||||||
// if the context has a runtime flags embed, then set the field properly
|
|
||||||
if tpl.p.hasRuntimeFlagsEmbed {
|
|
||||||
reflect.ValueOf(&c).Elem().
|
|
||||||
FieldByName(runtimeFlagsName).
|
|
||||||
Set(reflect.ValueOf(ctx.Runtime))
|
|
||||||
}
|
|
||||||
|
|
||||||
// the main template
|
// the main template
|
||||||
ctx.cMain = c
|
|
||||||
ctx.tMain = tpl.p.tpl
|
ctx.tMain = tpl.p.tpl
|
||||||
|
|
||||||
// the footer template
|
// the footer template
|
||||||
|
|
@ -126,22 +134,62 @@ func (tw *Template[C]) HTMLHandlerWithFlags(f func(r *http.Request) (C, []FlagFu
|
||||||
// Callers may not retain references beyond the invocation of the template.
|
// Callers may not retain references beyond the invocation of the template.
|
||||||
// Callers must not rely on the internal structure of this tContext.
|
// Callers must not rely on the internal structure of this tContext.
|
||||||
type tContext[C any] struct {
|
type tContext[C any] struct {
|
||||||
Runtime RuntimeFlags // underlying flags
|
Runtime RuntimeFlags // underlying flags
|
||||||
|
updateEmbedded bool // should we automatically update an embedded RuntimeFlags inside the context?
|
||||||
|
|
||||||
ctx context.Context // underlying context for render
|
ctx context.Context // underlying context for render
|
||||||
|
|
||||||
// the main template and context
|
// the main template and context
|
||||||
tMain *template.Template
|
eMain chan error // are we done?
|
||||||
cMain C
|
cWaiting bool
|
||||||
|
tMain *template.Template
|
||||||
|
cMain C
|
||||||
|
|
||||||
// the footer template and context
|
// the footer template and context
|
||||||
tFooter *template.Template
|
tFooter *template.Template
|
||||||
cFooter RuntimeFlags
|
cFooter RuntimeFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *tContext[C]) start(c C, err error) {
|
||||||
|
ctx.cMain = c
|
||||||
|
ctx.eMain = make(chan error, 1)
|
||||||
|
ctx.eMain <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tContext[C]) startLazy(f func() (C, error)) {
|
||||||
|
ctx.eMain = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(ctx.eMain)
|
||||||
|
|
||||||
|
// compute the result, storing the error
|
||||||
|
var err error
|
||||||
|
ctx.cMain, err = f()
|
||||||
|
ctx.eMain <- err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainDelay = time.Second
|
||||||
|
|
||||||
// Main renders the main template.
|
// Main renders the main template.
|
||||||
func (ctx *tContext[C]) Main() (template.HTML, error) {
|
func (ctx *tContext[C]) Main() (template.HTML, error) {
|
||||||
return ctx.renderSafe("main", ctx.tMain, ctx.cMain)
|
timer := timex.NewTimer()
|
||||||
|
defer timex.ReleaseTimer(timer)
|
||||||
|
|
||||||
|
timer.Reset(mainDelay)
|
||||||
|
select {
|
||||||
|
|
||||||
|
case err := <-ctx.eMain:
|
||||||
|
// we received the result within the given time
|
||||||
|
// so we can render it immediatly
|
||||||
|
ctx.cWaiting = false
|
||||||
|
return ctx.doMain(err)
|
||||||
|
|
||||||
|
case <-timer.C:
|
||||||
|
// the template is taking longer than expected.
|
||||||
|
// we should display a spinner, and do something later
|
||||||
|
ctx.cWaiting = true
|
||||||
|
return timeWait, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer renders the footer template
|
// Footer renders the footer template
|
||||||
|
|
@ -149,6 +197,52 @@ func (ctx *tContext[C]) Footer() (template.HTML, error) {
|
||||||
return ctx.renderSafe("footer", ctx.tFooter, ctx.cFooter)
|
return ctx.renderSafe("footer", ctx.tFooter, ctx.cFooter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeWait = "Loading"
|
||||||
|
errUnknown = "An unknown error occured, see the server log for details. "
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ctx *tContext[C]) doMain(err error) (template.HTML, error) {
|
||||||
|
zerolog.Ctx(ctx.ctx).Info().Msg("doMain")
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(ctx.ctx).Err(err).Msg("error lazy loading template")
|
||||||
|
return errUnknown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the context has a runtime flags embed, then set the field properly
|
||||||
|
if ctx.updateEmbedded {
|
||||||
|
reflect.ValueOf(&ctx.cMain).Elem().
|
||||||
|
FieldByName(runtimeFlagsName).
|
||||||
|
Set(reflect.ValueOf(ctx.Runtime))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.renderSafe("main", ctx.tMain, ctx.cMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tContext[C]) AfterBody() (template.HTML, error) {
|
||||||
|
zerolog.Ctx(ctx.ctx).Info().Msg("AfterBody()")
|
||||||
|
// everything was done already
|
||||||
|
if !ctx.cWaiting {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the result to appear
|
||||||
|
res, err := ctx.doMain(<-ctx.eMain)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := json.Marshal(string(res))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fix := "<script>document.getElementById('main').innerHTML=" + string(str) + "</script>"
|
||||||
|
|
||||||
|
// hook that is called after the body is complete
|
||||||
|
return template.HTML(fix), nil
|
||||||
|
}
|
||||||
|
|
||||||
const renderSafeError = "Error displaying page. See server log for details. "
|
const renderSafeError = "Error displaying page. See server log for details. "
|
||||||
|
|
||||||
func (ctx *tContext[C]) renderSafe(name string, t *template.Template, c any) (template.HTML, error) {
|
func (ctx *tContext[C]) renderSafe(name string, t *template.Template, c any) (template.HTML, error) {
|
||||||
|
|
@ -170,7 +264,7 @@ func (ctx *tContext[C]) renderSafe(name string, t *template.Template, c any) (te
|
||||||
Str("uri", ctx.Runtime.RequestURI).
|
Str("uri", ctx.Runtime.RequestURI).
|
||||||
Str("name", name).
|
Str("name", name).
|
||||||
Str("panic", fmt.Sprint(r)).
|
Str("panic", fmt.Sprint(r)).
|
||||||
Msg("templating.Main(): template panic()ed")
|
Msg("renderSafe: template panic()ed")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<nav class="pure-menu pure-menu-horizontal">
|
<nav class="pure-menu pure-menu-horizontal">
|
||||||
<ul class="pure-menu-list" role="menubar">
|
<ul class="pure-menu-list" role="menubar">
|
||||||
{{ range .Runtime.Menu }}
|
{{ range .Runtime.Menu }}
|
||||||
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}">
|
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}" role="menuitem">
|
||||||
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
|
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
|
||||||
</li>
|
</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div class="pure-g">
|
<div class="pure-g" id="main">
|
||||||
{{ .Main }}
|
{{ .Main }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
{{ .Footer }}
|
{{ .Footer }}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{{ .AfterBody }}
|
||||||
{{ .Runtime.Flags.Assets.Scripts }}
|
{{ .Runtime.Flags.Assets.Scripts }}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package cancel
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Anyways behaves like context.WithTimeout, except that if the Done() channel of ctx is closed before Anyways is called, the returned context's Done() channel is only closed after timeout.
|
// Anyways behaves like context.WithTimeout, except that if the Done() channel of ctx is closed before Anyways is called, the returned context's Done() channel is only closed after timeout.
|
||||||
|
|
@ -22,8 +24,9 @@ func Anyways(ctx context.Context, timeout time.Duration) (context.Context, conte
|
||||||
// start waiting for the timer (or the cancel to be called)
|
// start waiting for the timer (or the cancel to be called)
|
||||||
finish := make(chan struct{})
|
finish := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTimer(timeout)
|
t := timex.NewTimer()
|
||||||
defer t.Stop()
|
t.Reset(timeout)
|
||||||
|
defer timex.ReleaseTimer(t)
|
||||||
|
|
||||||
defer close(any.done)
|
defer close(any.done)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@ package httpx
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const HTMLFlushInterval = time.Second / 10
|
||||||
|
|
||||||
// WriteHTML writes a html response of type T to w.
|
// WriteHTML writes a html response of type T to w.
|
||||||
// If an error occured, writes an error response instead.
|
// If an error occured, writes an error response instead.
|
||||||
func WriteHTML[T any](result T, err error, template *template.Template, templateName string, w http.ResponseWriter, r *http.Request) (e error) {
|
func WriteHTML[T any](result T, err error, template *template.Template, templateName string, w http.ResponseWriter, r *http.Request) (e error) {
|
||||||
|
|
@ -17,17 +21,39 @@ func WriteHTML[T any](result T, err error, template *template.Template, template
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// create a synced respone writer
|
||||||
|
sw := &SyncedResponseWriter{ResponseWriter: w}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
// and regularly flush it until the end of the function
|
||||||
|
go func() {
|
||||||
|
timer := timex.NewTimer()
|
||||||
|
defer timex.ReleaseTimer(timer)
|
||||||
|
|
||||||
|
for {
|
||||||
|
timer.Reset(HTMLFlushInterval)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
sw.Flush()
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// intercept any errors
|
// intercept any errors
|
||||||
if HTMLInterceptor.Intercept(w, r, err) {
|
if HTMLInterceptor.Intercept(sw, r, err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// write out the response as html
|
// write out the response as html
|
||||||
w.Header().Set("Content-Type", "text/html")
|
sw.Header().Set("Content-Type", "text/html")
|
||||||
w.WriteHeader(http.StatusOK)
|
sw.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
// minify html!
|
// minify html!
|
||||||
minifier := MinifyHTMLWriter(w)
|
minifier := MinifyHTMLWriter(sw)
|
||||||
defer minifier.Close()
|
defer minifier.Close()
|
||||||
|
|
||||||
// and return the template
|
// and return the template
|
||||||
|
|
|
||||||
47
pkg/httpx/sync.go
Normal file
47
pkg/httpx/sync.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyncedResponseWriter wraps a http ResponseWriter to syncronize all actions
|
||||||
|
type SyncedResponseWriter struct {
|
||||||
|
m sync.Mutex
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *SyncedResponseWriter) Header() http.Header {
|
||||||
|
rw.m.Lock()
|
||||||
|
defer rw.m.Unlock()
|
||||||
|
|
||||||
|
return rw.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *SyncedResponseWriter) Write(data []byte) (int, error) {
|
||||||
|
rw.m.Lock()
|
||||||
|
defer rw.m.Unlock()
|
||||||
|
|
||||||
|
return rw.ResponseWriter.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *SyncedResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
rw.m.Lock()
|
||||||
|
defer rw.m.Unlock()
|
||||||
|
|
||||||
|
rw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes any partial output to the underlying ResponseWriter.
|
||||||
|
// If the wrapped ResponseWriter does not implement flush, the function performs no operation.
|
||||||
|
func (rw *SyncedResponseWriter) Flush() {
|
||||||
|
f, ok := rw.ResponseWriter.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.m.Lock()
|
||||||
|
defer rw.m.Unlock()
|
||||||
|
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
@ -3,18 +3,28 @@ package timex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewTimer creates a new timer with undefined interval.
|
var tPool = sync.Pool{
|
||||||
// The timer is stopped.
|
New: func() any {
|
||||||
func NewTimer() *time.Timer {
|
timer := time.NewTimer(time.Second)
|
||||||
timer := time.NewTimer(time.Second)
|
StopTimer(timer)
|
||||||
StopTimer(timer)
|
return timer
|
||||||
return timer
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopTimer stops t and drains the C channel.
|
// NewTimer returns an unusued timer from an internal timer pool.
|
||||||
|
// The timer is guaranteed to be stopped; meaning a call to timer.Reset() should be made before using it.
|
||||||
|
func NewTimer() *time.Timer {
|
||||||
|
return tPool.Get().(*time.Timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTimer stops the given timer and drains the underlying channel.
|
||||||
|
// This prevents it from firing, until a call to Reset() is made.
|
||||||
|
//
|
||||||
|
// If the timer is not running, StopTimer does nothing.
|
||||||
func StopTimer(t *time.Timer) {
|
func StopTimer(t *time.Timer) {
|
||||||
t.Stop()
|
t.Stop()
|
||||||
|
|
||||||
|
|
@ -25,6 +35,12 @@ func StopTimer(t *time.Timer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReleaseTimer stops t and returns it to the pool of timers.
|
||||||
|
func ReleaseTimer(t *time.Timer) {
|
||||||
|
StopTimer(t)
|
||||||
|
tPool.Put(t)
|
||||||
|
}
|
||||||
|
|
||||||
// TickContext is like [time.Tick], but closes the returned channel once the context closes.
|
// TickContext is like [time.Tick], but closes the returned channel once the context closes.
|
||||||
// As such it can be recovered by the garbage collector; see [time.TickContext].
|
// As such it can be recovered by the garbage collector; see [time.TickContext].
|
||||||
//
|
//
|
||||||
|
|
@ -34,24 +50,26 @@ func TickContext(c context.Context, d time.Duration) <-chan time.Time {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timer := make(chan time.Time, 1)
|
ticker := make(chan time.Time, 1)
|
||||||
timer <- time.Now()
|
ticker <- time.Now()
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTicker(d)
|
defer close(ticker)
|
||||||
defer t.Stop()
|
|
||||||
defer close(timer)
|
timer := NewTimer()
|
||||||
|
defer ReleaseTimer(timer)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
timer.Reset(d)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case tick := <-t.C:
|
case tick := <-timer.C:
|
||||||
timer <- tick
|
ticker <- tick
|
||||||
case <-c.Done():
|
case <-c.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
return timer
|
return ticker
|
||||||
}
|
}
|
||||||
|
|
||||||
// TickUntilFunc invokes f every d until either context is closed, or f returns true.
|
// TickUntilFunc invokes f every d until either context is closed, or f returns true.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue