134 lines
4.2 KiB
Go
134 lines
4.2 KiB
Go
// modules combines caching, outbound calls, middleware and background jobs.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
omni "git.yuntuo.hk/k2safe/go-omni"
|
|
"git.yuntuo.hk/k2safe/go-omni/auth"
|
|
"git.yuntuo.hk/k2safe/go-omni/cache"
|
|
"git.yuntuo.hk/k2safe/go-omni/httpc"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
"git.yuntuo.hk/k2safe/go-omni/middleware"
|
|
"git.yuntuo.hk/k2safe/go-omni/task"
|
|
)
|
|
|
|
func newApp(settings omni.Config, token, upstream string) (application *omni.App, err error) {
|
|
application = omni.New(omni.WithConfig(settings))
|
|
defer func(app *omni.App) {
|
|
if err != nil {
|
|
_ = app.Close()
|
|
}
|
|
}(application)
|
|
store, err := cache.NewMemory(cache.MemoryConfig{MaxEntries: 128, MaxBytes: 1 << 20})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
application.OnShutdown(store.Close)
|
|
loader, err := cache.NewLoader(store, cache.LoaderConfig{MaxLoads: 8, Timeout: time.Second})
|
|
if err != nil {
|
|
return application, err
|
|
}
|
|
client, err := httpc.New(httpc.Config{Timeout: time.Second, MaxRetries: 1, MaxConnsPerHost: 8})
|
|
if err != nil {
|
|
return application, err
|
|
}
|
|
application.OnShutdown(client.Close)
|
|
jobs, err := task.New(task.Config{Workers: 2, QueueSize: 16, TaskTimeout: 2 * time.Second})
|
|
if err != nil {
|
|
return application, err
|
|
}
|
|
// Reverse cleanup order: drain jobs before closing their dependencies.
|
|
application.OnShutdown(jobs.Close)
|
|
if err = jobs.Every("reportStats", time.Minute, func(ctx context.Context) error {
|
|
logx.InfoContext(ctx, "jobs.stats", "stats", jobs.Stats())
|
|
return nil
|
|
}); err != nil {
|
|
return application, err
|
|
}
|
|
limiter, err := middleware.NewRateLimiter(middleware.RateLimitConfig{Rate: 20, Burst: 40, MaxKeys: 128, Key: func(c *omni.Context) string { p, _ := auth.FromContext(c); return p.UserID }})
|
|
if err != nil {
|
|
return application, err
|
|
}
|
|
idempotency, err := middleware.NewIdempotency(middleware.IdempotencyConfig{Scope: func(c *omni.Context) (string, error) { p, _ := auth.FromContext(c); return p.UserID, nil }})
|
|
if err != nil {
|
|
return application, err
|
|
}
|
|
api := application.Group("/api", middleware.Timeout(2*time.Second), auth.RequireAuth(auth.StaticAuthenticator{token: {UserID: "demo-user"}}), limiter.Middleware())
|
|
api.GET("/catalog", func(c *omni.Context) error {
|
|
value, err := loader.GetOrLoad(c.Context(), "publicCatalog:v1", 30*time.Second, func(ctx context.Context) ([]byte, error) {
|
|
data := map[string]any{"name": "demo catalog"}
|
|
if upstream != "" {
|
|
if err := client.JSON(ctx, "GET", upstream, nil, &data); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return json.Marshal(data)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.OK(json.RawMessage(value))
|
|
})
|
|
var nextID atomic.Uint64
|
|
api.POST("/jobs", func(c *omni.Context) error {
|
|
var input struct {
|
|
Name string `json:"name" binding:"required,max=100"`
|
|
}
|
|
if err := c.BindJSON(&input); err != nil {
|
|
return err
|
|
}
|
|
id := nextID.Add(1)
|
|
// This job deliberately outlives the HTTP request. Runner still applies
|
|
// its own deadline and shutdown cancellation; log correlation survives.
|
|
_, err := jobs.Submit(context.WithoutCancel(c.Context()), "demoJob", func(ctx context.Context) error {
|
|
logx.InfoContext(ctx, "job.finished", "jobId", id, "name", input.Name)
|
|
return nil
|
|
})
|
|
if errors.Is(err, task.ErrQueueFull) {
|
|
return omni.NewError(503, "QUEUE_FULL", "job queue is full")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Accepted(map[string]uint64{"jobId": id})
|
|
}, idempotency.Middleware())
|
|
api.GET("/jobs", func(c *omni.Context) error { return c.OK(jobs.Stats()) })
|
|
return application, nil
|
|
}
|
|
|
|
func run(ctx context.Context) error {
|
|
settings, err := omni.LoadConfig("")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if os.Getenv("GO_OMNI_HTTP_ADDR") == "" {
|
|
settings.Address = "127.0.0.1:8080"
|
|
}
|
|
token := os.Getenv("GO_OMNI_DEV_TOKEN")
|
|
if token == "" {
|
|
token = "dev-token"
|
|
}
|
|
application, err := newApp(settings, token, os.Getenv("GO_OMNI_DEMO_UPSTREAM"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return application.RunContext(ctx)
|
|
}
|
|
|
|
func main() {
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
if err := run(ctx); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|