feat: add cache, outbound HTTP, request protection, tasks and module scaffolding
This commit is contained in:
133
examples/modules/main.go
Normal file
133
examples/modules/main.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user