package api import ( "encoding/json" "fmt" "net/http" "os" "time" "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs" "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/sse" context "github.com/kataras/iris/v12/context" "go.mongodb.org/mongo-driver/bson/primitive" ) type DebugStage struct { DebugEvent `json:",inline"` Events []*DebugEvent `json:"events"` } type DebugEvent struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` Created int64 `json:"created"` Error *errs.Error `json:"error"` Data interface{} `json:"data"` } type Request struct { ID string `json:"id"` Status string `json:"status"` Created int64 `json:"created"` } type DebugTaks struct { ID string `json:"id"` Status string `json:"status"` Created int64 `json:"created"` Stages []*DebugStage `json:"stages"` CurrentStage *DebugStage `json:"-"` Debug *Debugger `json:"-"` Request interface{} `json:"request"` } func NewDebugTaks() *DebugTaks { return &DebugTaks{ Stages: []*DebugStage{}, CurrentStage: &DebugStage{}, } } func (debug *DebugTaks) Stage(id string) *DebugStage { stage := &DebugStage{} stage.ID = id stage.Events = []*DebugEvent{} debug.Stages = append(debug.Stages, stage) debug.CurrentStage = stage return stage } func (stage *DebugStage) PushEvent(event *DebugEvent) { stage.Events = append(stage.Events, event) } func (debug *DebugTaks) Event(eventType, eventId string) *DebugEvent { event := &DebugEvent{ ID: eventId, Type: eventType, Created: time.Now().Unix(), } debug.CurrentStage.PushEvent(event) return event } func (task *DebugTaks) Finalize() { var ( debug = task.Debug out []byte err error ) for _, stage := range task.Stages { for _, event := range stage.Events { if event.Error != nil { event.Status = "error" stage.Status = "error" task.Status = "error" goto end } } } // task.Event() end: if out, err = json.Marshal(task); err != nil { debug.Emit(&sse.Event{ Kind: "debugger.error", Payload: err.Error(), }) } else { debug.Emit(&sse.Event{ Kind: "request", Payload: string(out), }) } } type Debugger struct { ChannelID string Tasks []*DebugTaks Hub *sse.SSEHub } func (debug *Debugger) Handler() func(context.Context) { return func(ctx context.Context) { var err error task := debug.CreateTask() if task.Request, err = parseRequest(ctx.Request()); err != nil { fmt.Println("Request debug error", err.Error()) } ctx.Values().Set("#debug", task) ctx.Next() } } func (debug *Debugger) CreateTask() *DebugTaks { task := &DebugTaks{ ID: primitive.NewObjectID().Hex(), Status: "", Created: time.Now().Unix(), Debug: debug, } debug.Tasks = append([]*DebugTaks{task}, debug.Tasks...) return task } func (debug *Debugger) Emit(events ...*sse.Event) { channel := debug.Hub.GetChannel(debug.ChannelID) for _, event := range events { channel.Emit(event) } } func (debug *Debugger) EventStream() func(context.Context) (interface{}, *errs.Error) { return func(ctx context.Context) (resp interface{}, err *errs.Error) { go func() { if len(debug.Tasks) > 0 { time.Sleep(time.Second) fmt.Println("initialize debug with ", len(debug.Tasks)) for _, task := range debug.Tasks[:10] { task.Finalize() } debug.Tasks = debug.Tasks[:10] } }() if err = debug.Hub.UpgradeConnection( ctx, debug.ChannelID, ); err != nil { return } resp = true return } } func NewDebug() *Debugger { return &Debugger{ ChannelID: "debug", Tasks: []*DebugTaks{}, Hub: sse.NewSSEHub(&sse.SSEOptions{ URI: os.Getenv("REDIS_URI"), Password: os.Getenv("REDIS_PASSWD"), ChannelCollection: "debugger", }), } } func parseRequest(req *http.Request) (result map[string]interface{}, err error) { save := req.Body defer func() { req.Body = save }() result = map[string]interface{}{} // if !body || req.Body == nil { // req.Body = nil // } else { // save, req.Body, err = drainBody(req.Body) // if err != nil { // return nil, err // } // } var ( reqURI string reqMethod string ) if reqURI = req.RequestURI; reqURI == "" { reqURI = req.URL.RequestURI() } if reqMethod = req.Method; req.Method == "" { reqMethod = "GET" } result["URI"] = reqURI result["URL"] = map[string]interface{}{ "Scheme": req.URL.Scheme, "Host": req.URL.Hostname(), "IsAbs": req.URL.IsAbs(), "EscapedPath": req.URL.EscapedPath(), "Port": req.URL.Port(), "Uri": req.URL.String(), "query": req.URL.Query(), } result["Method"] = reqMethod result["ProtoMajor"] = req.ProtoMajor result["ProtoMinor"] = req.ProtoMinor result["Proto"] = req.Proto result["RemoteAddr"] = req.RemoteAddr result["Header"] = req.Header result["Host"] = req.Host result["Close"] = req.Close result["ContentLength"] = req.ContentLength // for name, value := range req.Header { // fmt.Printf("%v: %v\n", name, value) // } // fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), // reqURI, req.ProtoMajor, req.ProtoMinor) // absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") // if !absRequestURI { // host := req.Host // if host == "" && req.URL != nil { // host = req.URL.Host // } // if host != "" { // fmt.Fprintf(&b, "Host: %s\r\n", host) // } // } // chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" // if len(req.TransferEncoding) > 0 { // fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) // } // if req.Close { // fmt.Fprintf(&b, "Connection: close\r\n") // } // err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) // if err != nil { // return nil, err // } // io.WriteString(&b, "\r\n") // if req.Body != nil { // var dest io.Writer = &b // if chunked { // dest = NewChunkedWriter(dest) // } // _, err = io.Copy(dest, req.Body) // if chunked { // dest.(io.Closer).Close() // io.WriteString(&b, "\r\n") // } // } // req.Body = save // if err != nil { // return nil, err // } // return b.Bytes(), nil return }