124 lines
3 KiB
Go
124 lines
3 KiB
Go
package server
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"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 body, _ := ioutil.ReadAll(gc.Request.Body); len(body) > 0 {
|
|
pl := message.PayloadFromJson[lib.Data](string(body))
|
|
msg.WithPayload(pl)
|
|
}
|
|
logging.DebugM(cctx, msg).Msg("server.handleMsg")
|
|
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 Call(clbl callable) msgProc {
|
|
return func(ctx lib.Context, msg lib.Message) (int, lib.Data) {
|
|
data, err := clbl(ctx, msg)
|
|
if err != nil {
|
|
data = lib.Map{"error": err.Error()}
|
|
return http.StatusInternalServerError, data
|
|
}
|
|
return http.StatusOK, data
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// message processing function types
|
|
type msgProc func(lib.Context, lib.Message) (int, lib.Data)
|
|
type callable func(lib.Context, lib.Message) (lib.Data, error)
|