chore: bump http kit (#817)

* use new httpkit runner

* refactor out last httpkit changes

* fix timeout defaults

* fix wrong time input - closes #819
This commit is contained in:
Hayden 2024-03-01 18:07:03 -06:00 committed by GitHub
parent 77b4d594af
commit 2867a05c92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 121 additions and 67 deletions

View file

@ -1,22 +1,18 @@
package main package main
import ( import (
"time"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config" "github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/pkgs/mailer" "github.com/hay-kot/homebox/backend/pkgs/mailer"
"github.com/hay-kot/httpkit/server"
) )
type app struct { type app struct {
conf *config.Config conf *config.Config
mailer mailer.Mailer mailer mailer.Mailer
db *ent.Client db *ent.Client
server *server.Server
repos *repo.AllRepos repos *repo.AllRepos
services *services.AllServices services *services.AllServices
bus *eventbus.EventBus bus *eventbus.EventBus
@ -37,13 +33,3 @@ func new(conf *config.Config) *app {
return s return s
} }
func (a *app) startBgTask(t time.Duration, fn func()) {
timer := time.NewTimer(t)
for {
timer.Reset(t)
a.server.Background(fn)
<-timer.C
}
}

View file

@ -0,0 +1,37 @@
package main
import (
"context"
"time"
)
type BackgroundTask struct {
name string
Interval time.Duration
Fn func(context.Context)
}
func (tsk *BackgroundTask) Name() string {
return tsk.name
}
func NewTask(name string, interval time.Duration, fn func(context.Context)) *BackgroundTask {
return &BackgroundTask{
Interval: interval,
Fn: fn,
}
}
func (tsk *BackgroundTask) Start(ctx context.Context) error {
timer := time.NewTimer(tsk.Interval)
for {
select {
case <-ctx.Done():
return nil
case <-timer.C:
timer.Reset(tsk.Interval)
tsk.Fn(ctx)
}
}
}

View file

@ -23,7 +23,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/sys/config" "github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid" "github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server" "github.com/hay-kot/httpkit/graceful"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors" "github.com/rs/zerolog/pkgerrors"
@ -178,41 +178,54 @@ func run(cfg *config.Config) error {
middleware.StripSlashes, middleware.StripSlashes,
) )
chain := errchain.New(mid.Errors(app.server, logger)) chain := errchain.New(mid.Errors(logger))
app.mountRoutes(router, chain, app.repos) app.mountRoutes(router, chain, app.repos)
app.server = server.NewServer( runner := graceful.NewRunner()
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port), runner.AddFunc("server", func(ctx context.Context) error {
server.WithReadTimeout(app.conf.Web.ReadTimeout), httpserver := http.Server{
server.WithWriteTimeout(app.conf.Web.WriteTimeout), Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
server.WithIdleTimeout(app.conf.Web.IdleTimeout), Handler: router,
) ReadTimeout: cfg.Web.ReadTimeout,
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port) WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
go func() {
<-ctx.Done()
_ = httpserver.Shutdown(context.Background())
}()
log.Info().Msgf("Server is running on %s:%s", cfg.Web.Host, cfg.Web.Port)
return httpserver.ListenAndServe()
})
// ========================================================================= // =========================================================================
// Start Reoccurring Tasks // Start Reoccurring Tasks
go app.bus.Run() runner.AddFunc("eventbus", app.bus.Run)
go app.startBgTask(time.Duration(24)*time.Hour, func() { runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background()) _, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Msg("failed to purge expired tokens") Msg("failed to purge expired tokens")
} }
}) }))
go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.Groups.InvitationPurge(context.Background()) runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.Groups.InvitationPurge(ctx)
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Msg("failed to purge expired invitations") Msg("failed to purge expired invitations")
} }
}) }))
go app.startBgTask(time.Duration(1)*time.Hour, func() {
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
now := time.Now() now := time.Now()
if now.Hour() == 8 { if now.Hour() == 8 {
@ -224,7 +237,7 @@ func run(cfg *config.Config) error {
Msg("failed to send notifiers") Msg("failed to send notifiers")
} }
} }
}) }))
// TODO: Remove through external API that does setup // TODO: Remove through external API that does setup
if cfg.Demo { if cfg.Demo {
@ -233,13 +246,24 @@ func run(cfg *config.Config) error {
} }
if cfg.Debug.Enabled { if cfg.Debug.Enabled {
debugrouter := app.debugRouter() runner.AddFunc("debug", func(ctx context.Context) error {
go func() { debugserver := http.Server{
if err := http.ListenAndServe(":"+cfg.Debug.Port, debugrouter); err != nil { Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
log.Fatal().Err(err).Msg("failed to start debug server") Handler: app.debugRouter(),
} ReadTimeout: cfg.Web.ReadTimeout,
}() WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
} }
return app.server.Start(router) go func() {
<-ctx.Done()
_ = debugserver.Shutdown(context.Background())
}()
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
return debugserver.ListenAndServe()
})
}
return runner.Start(context.Background())
} }

View file

@ -14,7 +14,7 @@ require (
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.2.1 github.com/gorilla/schema v1.2.1
github.com/hay-kot/httpkit v0.0.6 github.com/hay-kot/httpkit v0.0.9
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
github.com/olahol/melody v1.1.4 github.com/olahol/melody v1.1.4
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1

View file

@ -86,6 +86,12 @@ github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5R
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hay-kot/httpkit v0.0.6 h1:BidC4UrkS7zRhoTdpKLeF8ODJPKcOZkJ2tk2t2ZIQjQ= github.com/hay-kot/httpkit v0.0.6 h1:BidC4UrkS7zRhoTdpKLeF8ODJPKcOZkJ2tk2t2ZIQjQ=
github.com/hay-kot/httpkit v0.0.6/go.mod h1:1s/OJwWRyH6tBtTw76jTp6kwBYvjswziXaokPQH7eKQ= github.com/hay-kot/httpkit v0.0.6/go.mod h1:1s/OJwWRyH6tBtTw76jTp6kwBYvjswziXaokPQH7eKQ=
github.com/hay-kot/httpkit v0.0.7 h1:KxGi+MwXFavfFUfJEMpye5cnMef9TlFu3v7UZipUB8U=
github.com/hay-kot/httpkit v0.0.7/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/hay-kot/httpkit v0.0.8 h1:n+Z5z35YZcdD9cGwbnIPRbrgDw9LY6lqakH4zYr5z+A=
github.com/hay-kot/httpkit v0.0.8/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0=
github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=

View file

@ -2,6 +2,7 @@
package eventbus package eventbus
import ( import (
"context"
"sync" "sync"
"github.com/google/uuid" "github.com/google/uuid"
@ -43,14 +44,18 @@ func New() *EventBus {
} }
} }
func (e *EventBus) Run() { func (e *EventBus) Run(ctx context.Context) error {
if e.started { if e.started {
panic("event bus already started") panic("event bus already started")
} }
e.started = true e.started = true
for event := range e.ch { for {
select {
case <-ctx.Done():
return nil
case event := <-e.ch:
e.mu.RLock() e.mu.RLock()
arr, ok := e.subscribers[event.event] arr, ok := e.subscribers[event.event]
e.mu.RUnlock() e.mu.RUnlock()
@ -63,6 +68,7 @@ func (e *EventBus) Run() {
fn(event.data) fn(event.data)
} }
} }
}
} }
func (e *EventBus) Publish(event Event, data any) { func (e *EventBus) Publish(event Event, data any) {

View file

@ -45,7 +45,9 @@ func TestMain(m *testing.M) {
log.Fatalf("failed opening connection to sqlite: %v", err) log.Fatalf("failed opening connection to sqlite: %v", err)
} }
go tbus.Run() go func() {
_ = tbus.Run(context.Background())
}()
err = client.Schema.Create(context.Background()) err = client.Schema.Create(context.Background())
if err != nil { if err != nil {

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"time"
"github.com/ardanlabs/conf/v3" "github.com/ardanlabs/conf/v3"
) )
@ -42,9 +43,9 @@ type WebConfig struct {
Port string `yaml:"port" conf:"default:7745"` Port string `yaml:"port" conf:"default:7745"`
Host string `yaml:"host"` Host string `yaml:"host"`
MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"` MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
ReadTimeout int `yaml:"read_timeout" conf:"default:10"` ReadTimeout time.Duration `yaml:"read_timeout" conf:"default:10s"`
WriteTimeout int `yaml:"write_timeout" conf:"default:10"` WriteTimeout time.Duration `yaml:"write_timeout" conf:"default:10s"`
IdleTimeout int `yaml:"idle_timeout" conf:"default:30"` IdleTimeout time.Duration `yaml:"idle_timeout" conf:"default:30s"`
} }
// New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the // New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the

View file

@ -16,7 +16,7 @@ type ErrorResponse struct {
Fields map[string]string `json:"fields,omitempty"` Fields map[string]string `json:"fields,omitempty"`
} }
func Errors(svr *server.Server, log zerolog.Logger) errchain.ErrorHandler { func Errors(log zerolog.Logger) errchain.ErrorHandler {
return func(h errchain.Handler) http.Handler { return func(h errchain.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := h.ServeHTTP(w, r) err := h.ServeHTTP(w, r)
@ -71,14 +71,6 @@ func Errors(svr *server.Server, log zerolog.Logger) errchain.ErrorHandler {
if err := server.JSON(w, code, resp); err != nil { if err := server.JSON(w, code, resp); err != nil {
log.Err(err).Msg("failed to write response") log.Err(err).Msg("failed to write response")
} }
// If Showdown error, return error
if server.IsShutdownError(err) {
err := svr.Shutdown(err.Error())
if err != nil {
log.Err(err).Msg("failed to shutdown server")
}
}
} }
}) })
} }

View file

@ -3,7 +3,7 @@
<label class="label"> <label class="label">
<span class="label-text"> {{ label }}</span> <span class="label-text"> {{ label }}</span>
</label> </label>
<input ref="input" v-model="selected" type="date" class="input input-bordered w-full" /> <VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" />
</div> </div>
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4"> <div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label class="label"> <label class="label">