diff --git a/cmd/run.go b/cmd/run.go index d91fd3e..848039a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -20,6 +20,7 @@ import ( "github.com/daeuniverse/dae-wing/graphql" "github.com/daeuniverse/dae-wing/graphql/service/config" "github.com/daeuniverse/dae-wing/webrender" + "github.com/daeuniverse/dae-wing/ws" "github.com/golang-jwt/jwt/v5" "github.com/graph-gophers/graphql-go/relay" "github.com/rs/cors" @@ -118,6 +119,7 @@ var ( } mux := http.NewServeMux() mux.Handle("/graphql", auth(cors.AllowAll().Handler(&relay.Handler{Schema: schema}))) + mux.Handle("/ws", auth(cors.AllowAll().Handler(&ws.Handler{}))) if err = webrender.Handle(mux); err != nil { errorExit(err) } @@ -209,7 +211,11 @@ func shouldReload() (ok bool, err error) { func auth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authorization := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + authorization := r.Header.Get("Authorization") + if authorization == "" { + authorization = r.URL.Query().Get("authorization") + } + authorization = strings.TrimPrefix(authorization, "Bearer ") var user db.User token, err := jwt.Parse(authorization, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: @@ -238,6 +244,9 @@ func auth(next http.Handler) http.Handler { ctx = context.WithValue(ctx, "user", &user) } } + w.Header().Set("x-auth-result", "1") + } else { + w.Header().Set("x-auth-result", "0") } next.ServeHTTP(w, r.WithContext(ctx)) }) diff --git a/dae-core b/dae-core index 0f8277b..91cc147 160000 --- a/dae-core +++ b/dae-core @@ -1 +1 @@ -Subproject commit 0f8277b5a4db39cbe883a79c6806832b2034d79e +Subproject commit 91cc147d182a6ddb16c2fd4c71899984cb56d1ec diff --git a/dae/daemsg_producer.go b/dae/daemsg_producer.go new file mode 100644 index 0000000..ea6aa15 --- /dev/null +++ b/dae/daemsg_producer.go @@ -0,0 +1,66 @@ +package dae + +import ( + "container/list" + "context" + + "github.com/daeuniverse/dae/control" +) + +type DaeMsgProducer struct { + chMsg <-chan *control.Msg + // resetChMsg is used to re-assign chMsg. + resetChMsg chan struct{} + daeSubscriber chan *daeSubscriber +} + +func NewDaeMsgProducer(chMsg <-chan *control.Msg) *DaeMsgProducer { + r := &DaeMsgProducer{ + chMsg: chMsg, + resetChMsg: make(chan struct{}), + daeSubscriber: make(chan *daeSubscriber), + } + return r +} + +func (r *DaeMsgProducer) ReassignChMsg(chMsg <-chan *control.Msg) { + r.chMsg = chMsg + r.resetChMsg <- struct{}{} +} + +func (r *DaeMsgProducer) Run() { + subs := list.New().Init() + for { + select { + // New subscriber. + case sub := <-r.daeSubscriber: + subs.PushBack(sub) + // Reset pointer to r.chMsg. + case <-r.resetChMsg: + + // Producer msg. + case msg := <-r.chMsg: + for node := subs.Front(); node != nil; node = node.Next() { + sub := node.Value.(*daeSubscriber) + select { + case <-sub.stop: + subs.Remove(node) + case sub.msgs <- msg: + default: + // Busy. + } + } + } + } +} + +type daeSubscriber struct { + msgs chan<- *control.Msg + stop <-chan struct{} +} + +func (r *DaeMsgProducer) Subscribe(ctx context.Context) <-chan *control.Msg { + c := make(chan *control.Msg, 10) + r.daeSubscriber <- &daeSubscriber{msgs: c, stop: ctx.Done()} + return c +} diff --git a/dae/run.go b/dae/run.go index 54482f9..191df28 100644 --- a/dae/run.go +++ b/dae/run.go @@ -30,6 +30,8 @@ var GracefullyExit = make(chan struct{}) var EmptyConfig *daeConfig.Config var c *control.ControlPlane var onceWaitingNetwork sync.Once +var ChMsg chan *control.Msg +var MsgProducer *DaeMsgProducer func init() { sections, err := config_parser.Parse(`global{} routing{}`) @@ -43,10 +45,11 @@ func init() { } func ControlPlane() (*control.ControlPlane, error) { - if c == nil { + ctl := c + if ctl == nil { return nil, ErrControlPlaneNotInit } - return c, nil + return ctl, nil } func Run(log *logrus.Logger, conf *daeConfig.Config, externGeoDataDirs []string, disableTimestamp bool, dry bool) (err error) { @@ -67,7 +70,10 @@ func Run(log *logrus.Logger, conf *daeConfig.Config, externGeoDataDirs []string, } // New c. - c, err = newControlPlane(log, nil, nil, conf, externGeoDataDirs) + ChMsg = make(chan *control.Msg, 10) + MsgProducer = NewDaeMsgProducer(ChMsg) + go MsgProducer.Run() + c, err = newControlPlane(log, nil, nil, conf, externGeoDataDirs, ChMsg) if err != nil { return err } @@ -144,8 +150,10 @@ loop: // Only keep dns cache when ip version preference not change. dnsCache = c.CloneDnsCache() } + // New ChMsg. + newChMsg := make(chan *control.Msg, 10) log.Warnln("[Reload] Load new control plane") - newC, err := newControlPlane(log, obj, dnsCache, newConf, externGeoDataDirs) + newC, err := newControlPlane(log, obj, dnsCache, newConf, externGeoDataDirs, newChMsg) if err != nil { /* dae-wing start */ errReload = err @@ -155,7 +163,7 @@ loop: "err": err, }).Errorln("[Reload] Failed to reload; try to roll back configuration") // Load last config back. - newC, err = newControlPlane(log, obj, dnsCache, conf, externGeoDataDirs) + newC, err = newControlPlane(log, obj, dnsCache, conf, externGeoDataDirs, ChMsg) if err != nil { obj.Close() c.Close() @@ -164,6 +172,7 @@ loop: }).Fatalln("[Reload] Failed to roll back configuration") } newConf = conf + newChMsg = ChMsg log.Errorln("[Reload] Last reload failed; rolled back configuration") } else { log.Warnln("[Reload] Stopped old control plane") @@ -180,6 +189,8 @@ loop: oldC := c c = newC conf = newConf + ChMsg = newChMsg + MsgProducer.ReassignChMsg(newChMsg) reloading = true /* dae-wing start */ chCallback = newReloadMsg.Callback @@ -195,7 +206,7 @@ loop: return nil } -func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string) (c *control.ControlPlane, err error) { +func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string, chMsg chan<- *control.Msg) (c *control.ControlPlane, err error) { // Print configuration. if log.IsLevelEnabled(logrus.DebugLevel) { @@ -229,17 +240,18 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c } // New dae control plane. - c, err = control.NewControlPlane( - log, - bpf, - dnsCache, - subscriptionToNodeList, - conf.Group, - &conf.Routing, - &conf.Global, - &conf.Dns, - externGeoDataDirs, - ) + c, err = control.NewControlPlane(&control.Options{ + Log: log, + Bpf: bpf, + DnsCache: dnsCache, + TagToNodeList: subscriptionToNodeList, + Groups: conf.Group, + RoutingA: &conf.Routing, + Global: &conf.Global, + DnsConfig: &conf.Dns, + ExternGeoDataDirs: externGeoDataDirs, + ChMsg: chMsg, + }) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index d811356..12e6258 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/daeuniverse/dae-wing -go 1.19 +go 1.21.0 + +toolchain go1.21.3 require ( github.com/daeuniverse/dae v0.2.0 @@ -8,6 +10,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.0.0 github.com/graph-gophers/graphql-go v1.5.1-0.20230228210639-f05ace9f4a41 github.com/json-iterator/go v1.1.12 + github.com/lesismal/nbio v1.3.20 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mzz2017/softwind v0.0.0-20230803152605-5f1f6bc06934 @@ -35,7 +38,6 @@ require ( github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect github.com/cilium/ebpf v0.11.0 // indirect github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d // indirect - github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d // indirect github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1 // indirect github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d // indirect github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb // indirect @@ -54,6 +56,7 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/lesismal/llib v1.1.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect @@ -75,7 +78,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/v2rayA/ahocorasick-domain v0.0.0-20231231085011-99ceb8ef3208 // indirect + github.com/v2rayA/ahocorasick-domain v0.0.0-20230218160829-122a074c48c8 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37 // indirect @@ -96,4 +99,6 @@ require ( replace github.com/daeuniverse/dae => ./dae-core +replace github.com/graph-gophers/graphql-transport-ws => github.com/mikhailv/graphql-transport-ws v0.0.0-20230405003623-3bf02386d7ce + // replace github.com/daeuniverse/dae => ../dae diff --git a/go.sum b/go.sum index 4381290..3ee763b 100644 --- a/go.sum +++ b/go.sum @@ -9,13 +9,12 @@ github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= github.com/bool64/dev v0.2.22 h1:YJFKBRKplkt+0Emq/5Xk1Z5QRmMNzc1UOJkR3rxJksA= +github.com/bool64/dev v0.2.22/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M= -github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d h1:hEZDwJvoTATxtNU8/kirJP9GK0tFxekXzT00cGXO0xg= -github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d/go.mod h1:RlBqzRS0OfxDxmD1bgNfTmz9uzs8wQSmSG2vonMwSd0= github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1 h1:qh16GLF9TfntnLIwos49rj7Yj4EHICDe9ToesIjTm1c= github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1/go.mod h1:ly72DcZIxHlKbOEz1qaSCh99lr7ns8T5JLpfww8hXrI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -33,9 +32,11 @@ github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvS github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk= +github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY= github.com/eknkc/basex v1.0.1/go.mod h1:k/F/exNEHFdbs3ZHuasoP2E7zeWwZblG84Y7Z59vQRo= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= @@ -47,6 +48,7 @@ github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqS github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -58,6 +60,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= @@ -78,7 +81,13 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lesismal/llib v1.1.12 h1:KJFB8bL02V+QGIvILEw/w7s6bKj9Ps9Px97MZP2EOk0= +github.com/lesismal/llib v1.1.12/go.mod h1:70tFXXe7P1FZ02AU9l8LgSOK7d7sRrpnkUr3rd3gKSg= +github.com/lesismal/nbio v1.3.20 h1:btQdW4u8yAo2xg1PeU/gOWR0IPj2wUK+ZeVc5zHIEn4= +github.com/lesismal/nbio v1.3.20/go.mod h1:KWlouFT5cgDdW5sMX8RsHASUMGniea9X0XIellZ0B38= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= @@ -111,9 +120,11 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+ github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd h1:+iAPaTbi1gZpcpDwe/BW1fx7Xoesv69hLNGPheoyhBs= github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd/go.mod h1:4soZNh0zW0LtYGdQ416i0jO0EIqMGcbtaspRS4BDvRQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -126,6 +137,7 @@ github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -152,6 +164,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -164,8 +177,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/v2rayA/ahocorasick-domain v0.0.0-20231231085011-99ceb8ef3208 h1:s/K1ome/+rTDictkqGhqLuAleUymyWnvgNWARjblS9U= -github.com/v2rayA/ahocorasick-domain v0.0.0-20231231085011-99ceb8ef3208/go.mod h1:mWch8I826zic/bKaCyE9ZZbWtFgEW0ox3EQ0NGm5DGw= +github.com/v2rayA/ahocorasick-domain v0.0.0-20230218160829-122a074c48c8 h1:2Liq3JvM/acVQZ7Gq9U5PpznMzlFRPYMPQxC2yXSi74= +github.com/v2rayA/ahocorasick-domain v0.0.0-20230218160829-122a074c48c8/go.mod h1:mWch8I826zic/bKaCyE9ZZbWtFgEW0ox3EQ0NGm5DGw= github.com/vearutop/statigz v1.3.0 h1:RoIbvbMilsT8gXtPflWLcVlce89l5qZP9SHlKhXtEsg= github.com/vearutop/statigz v1.3.0/go.mod h1:jqlOPvLAdiQktMtYAkyguI3Ee0FA26iXKeEx2pS5l88= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= @@ -175,36 +188,69 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37 h1:ZrWBE3u/o9cHU2mySXf1687MaK09JOeZt1A+fHnCjmU= gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37/go.mod h1:3x6b94nWCP/a2XB/joOPMiGYUBvqbLfeY/BkHLeDs6s= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw= golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= diff --git a/graphql/root_schema.go b/graphql/root_schema.go index c9daad3..dfd4487 100644 --- a/graphql/root_schema.go +++ b/graphql/root_schema.go @@ -16,6 +16,8 @@ import ( var rootSchema = ` scalar Duration scalar Time +scalar Int64 +scalar Uint64 directive @hasRole(role: Role!) on FIELD_DEFINITION @@ -144,6 +146,7 @@ type Mutation { # removeGroup is to remove a group. removeGroup(id: ID!): Int! @hasRole(role: ADMIN) } + enum Role { admin } diff --git a/graphql/scalar/duration.go b/graphql/scalar/duration.go index 174a55c..430a3fc 100644 --- a/graphql/scalar/duration.go +++ b/graphql/scalar/duration.go @@ -36,7 +36,7 @@ func (t *Duration) UnmarshalGraphQL(input interface{}) (err error) { return err } default: - return fmt.Errorf("wrong type for Time: %T", input) + return fmt.Errorf("wrong type for time.Duration: %v (%T)", input, input) } return nil } diff --git a/graphql/scalar/int64.go b/graphql/scalar/int64.go new file mode 100644 index 0000000..ab35a7f --- /dev/null +++ b/graphql/scalar/int64.go @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, daeuniverse Organization + */ + +package scalar + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type Int64 struct { + Int64 int64 +} + +// ImplementsGraphQLType maps this custom Go type +// to the graphql scalar type in the schema. +func (Int64) ImplementsGraphQLType(name string) bool { + return name == "Int64" +} + +// UnmarshalGraphQL is a custom unmarshaler for int64 +// +// This function will be called whenever you use the +// Int64 scalar as an input +func (t *Int64) UnmarshalGraphQL(input interface{}) (err error) { + switch input := input.(type) { + case int64: + t.Int64 = input + return nil + case string: + t.Int64, err = strconv.ParseInt(input, 10, 64) + if err != nil { + return err + } + default: + return fmt.Errorf("wrong type for Int64: %v (%T)", input, input) + } + return nil +} + +// MarshalJSON is a custom marshaler for Time +// +// This function will be called whenever you +// query for fields that use the Time type +func (t Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatInt(t.Int64, 10)) +} diff --git a/graphql/scalar/uint64.go b/graphql/scalar/uint64.go new file mode 100644 index 0000000..947f74f --- /dev/null +++ b/graphql/scalar/uint64.go @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, daeuniverse Organization + */ + +package scalar + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type Uint64 struct { + Uint64 uint64 +} + +// ImplementsGraphQLType maps this custom Go type +// to the graphql scalar type in the schema. +func (Uint64) ImplementsGraphQLType(name string) bool { + return name == "Uint64" +} + +// UnmarshalGraphQL is a custom unmarshaler for uint64 +// +// This function will be called whenever you use the +// Uint64 scalar as an input +func (t *Uint64) UnmarshalGraphQL(input interface{}) (err error) { + switch input := input.(type) { + case uint64: + t.Uint64 = input + return nil + case string: + t.Uint64, err = strconv.ParseUint(input, 10, 64) + if err != nil { + return err + } + case float64: + if uint64(input*10)%10 != 0 { + return fmt.Errorf("wrong type for Uint64: %v (%T)", input, input) + } + t.Uint64 = uint64(input) + default: + return fmt.Errorf("wrong type for Uint64: %v (%T)", input, input) + } + return nil +} + +// MarshalJSON is a custom marshaler for Time +// +// This function will be called whenever you +// query for fields that use the Time type +func (t Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatUint(t.Uint64, 10)) +} diff --git a/graphql/service/config/global/generator/input/input.go b/graphql/service/config/global/generator/input/input.go index 437abc7..49c0a2b 100644 --- a/graphql/service/config/global/generator/input/input.go +++ b/graphql/service/config/global/generator/input/input.go @@ -13,7 +13,6 @@ import ( "time" daeConfig "github.com/daeuniverse/dae/config" - "github.com/sirupsen/logrus" "github.com/stoewer/go-strcase" ) @@ -56,6 +55,11 @@ func (b *builder) WriteMethodScalar(fieldName string, name string, scalarField s b.WriteMethodLine(2, fmt.Sprintf("g.%v = i.%v.%v", fieldName, strcase.UpperCamelCase(name), scalarField)) b.WriteMethodLine(1, fmt.Sprintf("}")) } +func (b *builder) WriteMethodScalarCast(fieldName string, name string, retTyp string, scalarField string) { + b.WriteMethodLine(1, fmt.Sprintf("if i.%v != nil {", strcase.UpperCamelCase(name))) + b.WriteMethodLine(2, fmt.Sprintf("g.%v = %v(i.%v.%v)", fieldName, retTyp, strcase.UpperCamelCase(name), scalarField)) + b.WriteMethodLine(1, fmt.Sprintf("}")) +} func (b *builder) Build() (string, error) { @@ -76,19 +80,12 @@ func (b *builder) Build() (string, error) { return "", fmt.Errorf("field %v has no required mapstructure", structField.Name) } switch field := field.Interface().(type) { - case uint, uint8, uint16, uint32, uint64, - int, int8, int16, int32, int64: - // Int. - switch field.(type) { - case uint, uint32, uint64, int64: - logrus.WithFields(logrus.Fields{ - "name": structField.Name, - "type": structField.Type.String(), - }).Warnln("dangerous converting: may exceeds graphQL int32 range") - } - - b.WriteField(name, "int32") - b.WriteMethodTransform(structField.Name, name, structField.Type.String(), true) + case uint, uint8, uint16, uint32, uint64: + b.WriteField(name, "scalar.Uint64") + b.WriteMethodScalarCast(structField.Name, name, structField.Type.String(), "Uint64") + case int, int8, int16, int32, int64: + b.WriteField(name, "scalar.Int64") + b.WriteMethodScalarCast(structField.Name, name, structField.Type.String(), "Int64") case string, bool, []string: b.WriteField(name, structField.Type.String()) b.WriteMethodTransform(structField.Name, name, structField.Type.String(), false) diff --git a/graphql/service/config/global/generator/resolver/resolver.go b/graphql/service/config/global/generator/resolver/resolver.go index 121f33c..9ac2ff4 100644 --- a/graphql/service/config/global/generator/resolver/resolver.go +++ b/graphql/service/config/global/generator/resolver/resolver.go @@ -7,13 +7,13 @@ package main import ( "fmt" - daeConfig "github.com/daeuniverse/dae/config" - "github.com/sirupsen/logrus" - "github.com/stoewer/go-strcase" "os" "reflect" "strings" "time" + + daeConfig "github.com/daeuniverse/dae/config" + "github.com/stoewer/go-strcase" ) type builder struct { @@ -63,18 +63,10 @@ func (b *builder) Build() (string, error) { return "", fmt.Errorf("field %v has no required mapstructure", structField.Name) } switch field := field.Interface().(type) { - case uint, uint8, uint16, uint32, uint64, - int, int8, int16, int32, int64: - // Int. - switch field.(type) { - case uint, uint32, uint64, int64: - logrus.WithFields(logrus.Fields{ - "name": structField.Name, - "type": structField.Type.String(), - }).Warnln("dangerous converting: may exceeds graphQL int32 range") - } - - b.WriteFunc(structField.Name, name, "int32", true) + case uint, uint8, uint16, uint32, uint64: + b.WriteMarshalFunc(structField.Name, name, "scalar.Uint64") + case int, int8, int16, int32, int64: + b.WriteMarshalFunc(structField.Name, name, "scalar.Int64") case string, bool, []string: b.WriteFunc(structField.Name, name, structField.Type.String(), false) case time.Duration: diff --git a/graphql/service/config/global/schema.go b/graphql/service/config/global/schema.go index b5fbc7a..47b6b4a 100644 --- a/graphql/service/config/global/schema.go +++ b/graphql/service/config/global/schema.go @@ -7,12 +7,12 @@ package global import ( "fmt" - daeConfig "github.com/daeuniverse/dae/config" - "github.com/sirupsen/logrus" - "github.com/stoewer/go-strcase" "reflect" "strings" "time" + + daeConfig "github.com/daeuniverse/dae/config" + "github.com/stoewer/go-strcase" ) type builder struct { @@ -42,18 +42,10 @@ func (b *builder) Build() (string, error) { // To lower camel case. name = strcase.LowerCamelCase(name) switch field := field.Interface().(type) { - case uint, uint8, uint16, uint32, uint64, - int, int8, int16, int32, int64: - // Int. - switch field.(type) { - case uint, uint32, uint64, int64: - logrus.WithFields(logrus.Fields{ - "name": structField.Name, - "type": structField.Type.String(), - }).Warnln("dangerous converting: may exceeds graphQL int32 range") - } - - b.WriteLine(1, name+": Int"+b.NotNullString) + case uint, uint8, uint16, uint32, uint64: + b.WriteLine(1, name+": Uint64"+b.NotNullString) + case int, int8, int16, int32, int64: + b.WriteLine(1, name+": Int64"+b.NotNullString) case string: b.WriteLine(1, name+": String"+b.NotNullString) case time.Duration: diff --git a/ws/handler.go b/ws/handler.go new file mode 100644 index 0000000..7f9718a --- /dev/null +++ b/ws/handler.go @@ -0,0 +1,19 @@ +package ws + +import ( + "net/http" +) + +type Handler struct{} + +var _ http.Handler = &Handler{} + +func (*Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if w.Header().Get("x-auth-result") == "0" { + w.WriteHeader(401) + } + _, err := upgrader.Upgrade(w, r, nil) + if err != nil { + panic(err) + } +} diff --git a/ws/upgrader.go b/ws/upgrader.go new file mode 100644 index 0000000..29b5f0c --- /dev/null +++ b/ws/upgrader.go @@ -0,0 +1,72 @@ +package ws + +import ( + "context" + "sync" + "time" + + "github.com/daeuniverse/dae-wing/dae" + jsoniter "github.com/json-iterator/go" + "github.com/lesismal/nbio/nbhttp/websocket" +) + +var upgrader = newUpgrader() + +func subscribe(ctx context.Context, mp *dae.DaeMsgProducer, c *websocket.Conn) { + chMsg := mp.Subscribe(ctx) + go func() { + for msg := range chMsg { + bMsg, err := jsoniter.Marshal(msg) + if err != nil { + panic(err) + } + c.WriteMessage(websocket.TextMessage, bMsg) + } + }() +} + +func newUpgrader() *websocket.Upgrader { + u := websocket.NewUpgrader() + type Subscripber struct { + cancel context.CancelFunc + } + subscribers := sync.Map{} + u.OnOpen(func(c *websocket.Conn) { + identifier := c.RemoteAddr().String() + ctx, cancel := context.WithCancel(context.TODO()) + subscribers.Store(identifier, &Subscripber{ + cancel: cancel, + }) + if mp := dae.MsgProducer; mp != nil { + subscribe(ctx, mp, c) + } else { + // Waiting for the initializing of dae.MsgProducer. + go func(ctx context.Context) { + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for range ticker.C { + select { + case <-ctx.Done(): + // User has left. + return + default: + if mp := dae.MsgProducer; mp != nil { + // dae.MsgProducer is initialized. + subscribe(ctx, mp, c) + } + } + } + }(ctx) + } + }) + u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { + }) + u.OnClose(func(c *websocket.Conn, err error) { + identifier := c.RemoteAddr().String() + subscriber, ok := subscribers.LoadAndDelete(identifier) + if ok { + subscriber.(*Subscripber).cancel() + } + }) + return u +}