go-scopes/server/server.go

105 lines
2.5 KiB
Go

package server
import (
"net/http"
"strings"
"time"
lib "git.sr.ht/~cco/go-scopes"
"git.sr.ht/~cco/go-scopes/config"
"git.sr.ht/~cco/go-scopes/core"
"git.sr.ht/~cco/go-scopes/core/action"
"git.sr.ht/~cco/go-scopes/core/message"
"git.sr.ht/~cco/go-scopes/logging"
"github.com/gin-gonic/gin"
)
type ServerState struct {
server *http.Server
}
func Start(cfg *Cfg) lib.Proc {
return func(ctx lib.Context) {
gin.SetMode(gin.ReleaseMode)
lib.GetCfg[*config.BaseCfg](ctx).WithDoneHandler(HandleDone)
Serve(ctx, cfg)
lib.RunCtx(ctx, core.Listen)
}
}
func HandleDone(ctx lib.Context) bool {
lib.GetState[*ServerState](ctx).server.Shutdown(ctx)
return false
}
func Serve(ctx lib.Context, cfg *Cfg) {
r := gin.New()
r.Use(gin.Recovery())
r.Use(Logger(ctx))
if cfg.Addr == "" {
if cfg.Port == "" {
cfg.Port = "8123"
}
cfg.Addr = ":" + cfg.Port
}
for _, rcfg := range cfg.routes {
setRoute(ctx, rcfg, r)
}
srv := &http.Server{Addr: cfg.Addr, Handler: r}
ctx.WithState(&ServerState{server: srv})
logging.Info(ctx).Str("addr", cfg.Addr).Msg("server.Serve")
lib.RunCtx(ctx, func(ctx lib.Context) {
srv.ListenAndServe()
})
}
func setRoute(ctx lib.Context, rcfg routeCfg, r *gin.Engine) {
switch spec := rcfg.spec.(type) {
case *fsSpec:
r.Static(rcfg.path, spec.docRoot)
case *mhSpec:
r.Match(rcfg.methods, rcfg.path+"/*msg", func(c *gin.Context) {
handleMsg(ctx, spec, c)
})
}
}
// scopes standard request (= message) handler implementation
func handleMsg(ctx lib.Context, cfg *mhSpec, gc *gin.Context) {
head := strings.Split(gc.Param("msg"), "/")[1:]
cctx := ctx.ChildContext(cfg)
msg := message.New(head...).WithSender(cctx)
// if gc.Request.Method == "POST" && data != "": msg.WithPayload(data)
logging.DebugM(cctx, msg).Msg("server.handleMessage")
var proc msgProc
for _, act := range action.Select(cctx, msg) {
act.Handle()
proc = act.Spec().(*actionSpec).proc
}
code, data := proc(cctx, msg)
gc.JSON(code, data)
}
func Async(ctx lib.Context, msg lib.Message) (int, lib.Data) {
return http.StatusOK, lib.Map{"status": "OK"}
}
func Sync(timeout int) msgProc {
return func(ctx lib.Context, msg lib.Message) (int, lib.Data) {
return sync(ctx, msg, timeout)
}
}
func sync(ctx lib.Context, msg lib.Message, to int) (int, lib.Data) {
timeout := time.Duration(to) * time.Second
select {
case msg := <-ctx.Mailbox():
return http.StatusOK, msg.Payload().Data()
case <-time.After(timeout):
return http.StatusNoContent, nil
case <-ctx.Done():
return http.StatusGone, nil
}
return http.StatusNoContent, nil
}