diff --git a/topology/agent/collector/psutil_collector.go b/topology/agent/collector/psutil_collector.go index 531eea9b95f664003c242ce795a572481fa3f2d1..a17158cc0bdba4725e53aa9af20c3aa5124b6c1a 100644 --- a/topology/agent/collector/psutil_collector.go +++ b/topology/agent/collector/psutil_collector.go @@ -3,12 +3,10 @@ package collector import ( "encoding/json" "fmt" - "runtime" "strconv" "gitee.com/openeuler/PilotGo-plugin-topology-agent/utils" "gitee.com/openeuler/PilotGo-plugins/sdk/logger" - "github.com/pkg/errors" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/process" @@ -33,16 +31,16 @@ func CreatePsutilCollector() *PsutilCollector { func (pc *PsutilCollector) Collect_host_data() error { hostinit, err := host.Info() if err != nil { - filepath, line, funcname := utils.CallerInfo(err) - logger.Error("file: %s, line: %d, func: %s, err: %s\n", filepath, line, funcname, err.Error()) - return fmt.Errorf("file: %s, line: %d, func: %s, err -> %s", filepath, line, funcname, err.Error()) + // err = errors.New(err.Error()) + logger.Error(err.Error()) + return err } m_u_bytes, err := utils.FileReadBytes(utils.Agentuuid_filepath) if err != nil { - filepath, line, funcname := utils.CallerInfo(err) - logger.Error("file: %s, line: %d, func: %s, err: %s\n", filepath, line, funcname, err.Error()) - return fmt.Errorf("file: %s, line: %d, func: %s, err -> %s", filepath, line, funcname, err.Error()) + // err = errors.New(err.Error()) + logger.Error(err.Error()) + return err } type machineuuid struct { Agentuuid string `json:"agent_uuid"` @@ -72,14 +70,17 @@ func (pc *PsutilCollector) Collect_host_data() error { func (pc *PsutilCollector) Collect_process_instant_data() error { Echo_process_err := func(method string, err error, processid int32) { if err != nil { - _, filepath, line, _ := runtime.Caller(1) - fmt.Printf("file: %s, line: %d, func: %s, processid: %d, err: %s\n", filepath, line-1, method, processid, err.Error()) + // _, filepath, line, _ := runtime.Caller(1) + // fmt.Printf("file: %s, line: %d, func: %s, processid: %d, err: %s\n", filepath, line-1, method, processid, err.Error()) + // err = errors.Errorf("%s: %s, %d", err.Error(), method, processid) + fmt.Printf("%v: %s, %d\n", err.Error(), method, processid) } } processes_0, err := process.Processes() if err != nil { - err = errors.Errorf("failed to get processes: %s", err) + // err = errors.Errorf("failed to get processes: %s", err) + logger.Error("failed to get processes: %s", err) return err } @@ -209,7 +210,8 @@ func (pc *PsutilCollector) Collect_process_instant_data() error { func (pc *PsutilCollector) Collect_netconnection_all_data() error { connections, err := net.Connections("all") if err != nil { - err = errors.Errorf("failed to run net.connections: %s", err) + // err = errors.Errorf("failed to run net.connections: %s", err) + logger.Error("failed to run net.connections: %s", err) return err } @@ -240,7 +242,8 @@ func (pc *PsutilCollector) Collect_netconnection_all_data() error { func (pc *PsutilCollector) Collect_addrInterfaceMap_data() error { interfaces, err := net.Interfaces() if err != nil { - err = errors.Errorf("failed to run net.interfaces: %s", err) + // err = errors.Errorf("failed to run net.interfaces: %s", err) + logger.Error("failed to run net.interfaces: %s", err) return err } @@ -259,7 +262,8 @@ func (pc *PsutilCollector) Collect_addrInterfaceMap_data() error { func (pc *PsutilCollector) Collect_interfaces_io_data() error { iocounters, err := net.IOCounters(true) if err != nil { - err = errors.Errorf("failed to collect interfaces io: %s", err.Error()) + // err = errors.Errorf("failed to collect interfaces io: %s", err.Error()) + logger.Error("failed to collect interfaces io: %s", err.Error()) return err } @@ -287,7 +291,8 @@ func (pc *PsutilCollector) Collect_interfaces_io_data() error { func (pc *PsutilCollector) Collect_disk_data() error { partitions, err := disk.Partitions(false) if err != nil { - err = errors.Errorf("failed to collect disk partitions: %s", err.Error()) + // err = errors.Errorf("failed to collect disk partitions: %s", err.Error()) + logger.Error("failed to collect disk partitions: %s", err.Error()) return err } @@ -297,7 +302,8 @@ func (pc *PsutilCollector) Collect_disk_data() error { iocounter, err := disk.IOCounters([]string{disk_entity.Partition.Device}...) if err != nil { - err = errors.Errorf("failed to collect disk io: %s", err.Error()) + // err = errors.Errorf("failed to collect disk io: %s", err.Error()) + logger.Error("failed to collect disk io: %s", err.Error()) return err } @@ -305,7 +311,8 @@ func (pc *PsutilCollector) Collect_disk_data() error { usage, err := disk.Usage(partition.Mountpoint) if err != nil { - err = errors.Errorf("failed to collect disk usage: %s", err.Error()) + // err = errors.Errorf("failed to collect disk usage: %s", err.Error()) + logger.Error("failed to collect disk usage: %s", err.Error()) return err } @@ -320,7 +327,8 @@ func (pc *PsutilCollector) Collect_disk_data() error { func (pc *PsutilCollector) Collect_cpu_data() error { cputimes, err := cpu.Times(true) if err != nil { - err = errors.Errorf("failed to collect cpu times: %s", err.Error()) + // err = errors.Errorf("failed to collect cpu times: %s", err.Error()) + logger.Error("failed to collect cpu times: %s", err.Error()) return err } @@ -330,7 +338,8 @@ func (pc *PsutilCollector) Collect_cpu_data() error { cpuinfos, err := cpu.Info() if err != nil { - err = errors.Errorf("failed to collect cpu info: %s", err.Error()) + // err = errors.Errorf("failed to collect cpu info: %s", err.Error()) + logger.Error("failed to collect cpu info: %s", err.Error()) return err } cpu_entity.Info = cpuinfos[i] diff --git a/topology/agent/utils/init.go b/topology/agent/utils/init.go index 3c1fe3aa0e7a45db537caa7ef3f01dae89e3cd53..edee346322f6f1c121863a07defde6d7a775ded1 100644 --- a/topology/agent/utils/init.go +++ b/topology/agent/utils/init.go @@ -1,6 +1,5 @@ package utils const ( - Agentuuid_filepath = "/opt/PilotGo/agent/.pilotgo-agent.data" + Agentuuid_filepath = "/etc/PilotGo/agent/.pilotgo-agent.data" ) - diff --git a/topology/server/go.mod b/topology/server/go.mod index cda71ec4cfa5f3ad0b80c6b070d4fa3def423624..b94c4515522ffe672e75e62346da8fc4a8f67b58 100644 --- a/topology/server/go.mod +++ b/topology/server/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( gitee.com/openeuler/PilotGo-plugins/sdk v0.0.0-20230926064010-69fe9dd4adc7 + github.com/gin-contrib/timeout v0.0.3 github.com/gin-gonic/gin v1.7.7 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 diff --git a/topology/server/go.sum b/topology/server/go.sum index 311ef13570807819136b2cc2213e35abb2e4ae54..6a77cf558ae482fa245ddda2fc0c11a2296a718d 100644 --- a/topology/server/go.sum +++ b/topology/server/go.sum @@ -8,6 +8,9 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/timeout v0.0.3 h1:ysZQ7kChgqlzBkuLgwTTDjTPP2uqdI68XxRyqIFK68g= +github.com/gin-contrib/timeout v0.0.3/go.mod h1:F3fjkmFc4I1QdF7MyVwtO6ZkPueBckNoiOVpU73HGgU= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= diff --git a/topology/server/handler/handler.go b/topology/server/handler/handler.go index f3fc2a9bb5b3825317acaea800607b9797b831b2..d13f4ac35786dd9d647b868348571820c90ec388 100644 --- a/topology/server/handler/handler.go +++ b/topology/server/handler/handler.go @@ -90,7 +90,7 @@ func SingleHostTreeHandle(ctx *gin.Context) { func MultiHostHandle(ctx *gin.Context) { agentmanager.Topo.UpdateMachineList() - + nodes, edges, collect_errlist, process_errlist := service.MultiHostService() if len(collect_errlist) != 0 || len(process_errlist) != 0 { @@ -129,7 +129,7 @@ func MultiHostHandle(ctx *gin.Context) { func AgentListHandle(ctx *gin.Context) { agentmanager.Topo.UpdateMachineList() - + agentmap := make(map[string]string) agentmanager.Topo.AgentMap.Range(func(key, value interface{}) bool { agent := value.(*agentmanager.Agent_m) diff --git a/topology/server/handler/router.go b/topology/server/handler/router.go index 3db0ce17cbbdd323abde27e6ac4a5b1e7061d726..5bf663bebbcb6f01497c0edc05faf21c2f88d115 100644 --- a/topology/server/handler/router.go +++ b/topology/server/handler/router.go @@ -1,7 +1,11 @@ package handler import ( + "net/http" "os" + "time" + + "github.com/gin-contrib/timeout" "gitee.com/openeuler/PilotGo-plugin-topology-server/agentmanager" "gitee.com/openeuler/PilotGo-plugin-topology-server/conf" @@ -11,6 +15,7 @@ import ( func InitWebServer() { engine := gin.Default() + engine.Use(TimeoutMiddleware()) agentmanager.Topo.Sdkmethod.RegisterHandlers(engine) InitRouter(engine) StaticRouter(engine) @@ -36,3 +41,21 @@ func InitRouter(router *gin.Engine) { api.GET("/multi_host", MultiHostHandle) } } + +func TimeoutResponse(ctx *gin.Context) { + ctx.JSON(http.StatusGatewayTimeout, gin.H{ + "code": http.StatusGatewayTimeout, + "error": "timeout", + "data": nil, + }) +} + +func TimeoutMiddleware() gin.HandlerFunc { + return timeout.New( + timeout.WithTimeout(600*time.Second), + timeout.WithHandler(func(ctx *gin.Context) { + ctx.Next() + }), + timeout.WithResponse(TimeoutResponse), + ) +} diff --git a/topology/server/toposerver b/topology/server/toposerver new file mode 100644 index 0000000000000000000000000000000000000000..bb0fdd64336cbda4d591028ecbbb815f6f3d9996 Binary files /dev/null and b/topology/server/toposerver differ diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/.gitignore b/topology/server/vendor/github.com/gin-contrib/timeout/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d1baad60c6ac03cf9d298139bb648d2febe68988 --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +.idea \ No newline at end of file diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/.golangci.yml b/topology/server/vendor/github.com/gin-contrib/timeout/.golangci.yml new file mode 100644 index 0000000000000000000000000000000000000000..5a0031c301e1f17c6cf44e406f8046097b53144d --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/.golangci.yml @@ -0,0 +1,43 @@ +linters: + enable-all: false + disable-all: true + fast: false + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exportloopref + - exhaustive + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - noctx + - nolintlint + - rowserrcheck + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + - gofumpt + +run: + timeout: 3m diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/LICENSE b/topology/server/vendor/github.com/gin-contrib/timeout/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..40316ecf60c3922dd0df20792c7d51899cdb9c98 --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Gin-Gonic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/README.md b/topology/server/vendor/github.com/gin-contrib/timeout/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fe9b4199a1f28f117d294c7f85619ba404886bad --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/README.md @@ -0,0 +1,64 @@ +# Timeout + +[![Run Tests](https://github.com/gin-contrib/timeout/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/gin-contrib/timeout/actions/workflows/go.yml) +[![codecov](https://codecov.io/gh/gin-contrib/timeout/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/timeout) +[![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/timeout)](https://goreportcard.com/report/github.com/gin-contrib/timeout) +[![GoDoc](https://godoc.org/github.com/gin-contrib/timeout?status.svg)](https://pkg.go.dev/github.com/gin-contrib/timeout?tab=doc) +[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin) + +Timeout wraps a handler and aborts the process of the handler if the timeout is reached. + +## Example + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-contrib/timeout" + "github.com/gin-gonic/gin" +) + +func emptySuccessResponse(c *gin.Context) { + time.Sleep(200 * time.Microsecond) + c.String(http.StatusOK, "") +} + +func main() { + r := gin.New() + + r.GET("/", timeout.New( + timeout.WithTimeout(100*time.Microsecond), + timeout.WithHandler(emptySuccessResponse), + )) + + // Listen and Server in 0.0.0.0:8080 + if err := r.Run(":8080"); err != nil { + log.Fatal(err) + } +} + +``` + +### custom error response + +Add new error response func: + +```go +func testResponse(c *gin.Context) { + c.String(http.StatusRequestTimeout, "test response") +} +``` + +Add `WithResponse` option. + +```go + r.GET("/", timeout.New( + timeout.WithTimeout(100*time.Microsecond), + timeout.WithHandler(emptySuccessResponse), + timeout.WithResponse(testResponse), + )) +``` diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/buffer_pool.go b/topology/server/vendor/github.com/gin-contrib/timeout/buffer_pool.go new file mode 100644 index 0000000000000000000000000000000000000000..7a35c237907de896a2343b49089b1c8838625477 --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/buffer_pool.go @@ -0,0 +1,25 @@ +package timeout + +import ( + "bytes" + "sync" +) + +// BufferPool is Pool of *bytes.Buffer +type BufferPool struct { + pool sync.Pool +} + +// Get a bytes.Buffer pointer +func (p *BufferPool) Get() *bytes.Buffer { + buf := p.pool.Get() + if buf == nil { + return &bytes.Buffer{} + } + return buf.(*bytes.Buffer) +} + +// Put a bytes.Buffer pointer to BufferPool +func (p *BufferPool) Put(buf *bytes.Buffer) { + p.pool.Put(buf) +} diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/option.go b/topology/server/vendor/github.com/gin-contrib/timeout/option.go new file mode 100644 index 0000000000000000000000000000000000000000..dfb29a25d38cd0ee26d09b991f48d0b0bde2e0ef --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/option.go @@ -0,0 +1,43 @@ +package timeout + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// Option for timeout +type Option func(*Timeout) + +// WithTimeout set timeout +func WithTimeout(timeout time.Duration) Option { + return func(t *Timeout) { + t.timeout = timeout + } +} + +// WithHandler add gin handler +func WithHandler(h gin.HandlerFunc) Option { + return func(t *Timeout) { + t.handler = h + } +} + +// WithResponse add gin handler +func WithResponse(h gin.HandlerFunc) Option { + return func(t *Timeout) { + t.response = h + } +} + +func defaultResponse(c *gin.Context) { + c.String(http.StatusRequestTimeout, http.StatusText(http.StatusRequestTimeout)) +} + +// Timeout struct +type Timeout struct { + timeout time.Duration + handler gin.HandlerFunc + response gin.HandlerFunc +} diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/timeout.go b/topology/server/vendor/github.com/gin-contrib/timeout/timeout.go new file mode 100644 index 0000000000000000000000000000000000000000..ec591437af507314a2e9af60dfdbba3cd539936e --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/timeout.go @@ -0,0 +1,93 @@ +package timeout + +import ( + "time" + + "github.com/gin-gonic/gin" +) + +var bufPool *BufferPool + +const ( + defaultTimeout = 5 * time.Second +) + +// New wraps a handler and aborts the process of the handler if the timeout is reached +func New(opts ...Option) gin.HandlerFunc { + t := &Timeout{ + timeout: defaultTimeout, + handler: nil, + response: defaultResponse, + } + + // Loop through each option + for _, opt := range opts { + if opt == nil { + panic("timeout Option not be nil") + } + + // Call the option giving the instantiated + opt(t) + } + + if t.timeout <= 0 { + return t.handler + } + + bufPool = &BufferPool{} + + return func(c *gin.Context) { + finish := make(chan struct{}, 1) + panicChan := make(chan interface{}, 1) + + w := c.Writer + buffer := bufPool.Get() + tw := NewWriter(w, buffer) + c.Writer = tw + buffer.Reset() + + go func() { + defer func() { + if p := recover(); p != nil { + panicChan <- p + } + }() + t.handler(c) + finish <- struct{}{} + }() + + select { + case p := <-panicChan: + tw.FreeBuffer() + c.Writer = w + panic(p) + + case <-finish: + c.Next() + tw.mu.Lock() + defer tw.mu.Unlock() + dst := tw.ResponseWriter.Header() + for k, vv := range tw.Header() { + dst[k] = vv + } + tw.ResponseWriter.WriteHeader(tw.code) + if _, err := tw.ResponseWriter.Write(buffer.Bytes()); err != nil { + panic(err) + } + tw.FreeBuffer() + bufPool.Put(buffer) + + case <-time.After(t.timeout): + c.Abort() + tw.mu.Lock() + defer tw.mu.Unlock() + tw.timeout = true + tw.FreeBuffer() + bufPool.Put(buffer) + + c.Writer = w + t.response(c) + c.Writer = tw + } + } +} diff --git a/topology/server/vendor/github.com/gin-contrib/timeout/writer.go b/topology/server/vendor/github.com/gin-contrib/timeout/writer.go new file mode 100644 index 0000000000000000000000000000000000000000..d0cb79b6fe71554496fa04ce48886787623cd228 --- /dev/null +++ b/topology/server/vendor/github.com/gin-contrib/timeout/writer.go @@ -0,0 +1,79 @@ +package timeout + +import ( + "bytes" + "fmt" + "net/http" + "sync" + + "github.com/gin-gonic/gin" +) + +// Writer is a writer with memory buffer +type Writer struct { + gin.ResponseWriter + body *bytes.Buffer + headers http.Header + mu sync.Mutex + timeout bool + wroteHeaders bool + code int +} + +// NewWriter will return a timeout.Writer pointer +func NewWriter(w gin.ResponseWriter, buf *bytes.Buffer) *Writer { + return &Writer{ResponseWriter: w, body: buf, headers: make(http.Header)} +} + +// Write will write data to response body +func (w *Writer) Write(data []byte) (int, error) { + if w.timeout || w.body == nil { + return 0, nil + } + + w.mu.Lock() + defer w.mu.Unlock() + + return w.body.Write(data) +} + +// WriteHeader will write http status code +func (w *Writer) WriteHeader(code int) { + checkWriteHeaderCode(code) + if w.timeout || w.wroteHeaders { + return + } + + w.mu.Lock() + defer w.mu.Unlock() + + w.writeHeader(code) +} + +func (w *Writer) writeHeader(code int) { + w.wroteHeaders = true + w.code = code +} + +// Header will get response headers +func (w *Writer) Header() http.Header { + return w.headers +} + +// WriteString will write string to response body +func (w *Writer) WriteString(s string) (int, error) { + return w.Write([]byte(s)) +} + +// FreeBuffer will release buffer pointer +func (w *Writer) FreeBuffer() { + // if not reset body,old bytes will put in bufPool + w.body.Reset() + w.body = nil +} + +func checkWriteHeaderCode(code int) { + if code < 100 || code > 999 { + panic(fmt.Sprintf("invalid http status code: %d", code)) + } +} diff --git a/topology/server/vendor/modules.txt b/topology/server/vendor/modules.txt index c05017020e4141f18ee5bf7246e734fd33144d6b..e726d17f449c42447e335ebb06ccec164834c09f 100644 --- a/topology/server/vendor/modules.txt +++ b/topology/server/vendor/modules.txt @@ -13,6 +13,9 @@ github.com/gabriel-vasile/mimetype/internal/magic # github.com/gin-contrib/sse v0.1.0 ## explicit; go 1.12 github.com/gin-contrib/sse +# github.com/gin-contrib/timeout v0.0.3 +## explicit; go 1.15 +github.com/gin-contrib/timeout # github.com/gin-gonic/gin v1.7.7 ## explicit; go 1.13 github.com/gin-gonic/gin