// Copyright © 2023 Roberto Hidalgo // SPDX-License-Identifier: Apache-2.0 package http import ( "context" "encoding/json" "fmt" "net/http" "git.rob.mx/nidito/chinampa/pkg/logger" "git.rob.mx/nidito/event-gateway/internal/config" "git.rob.mx/nidito/event-gateway/internal/payload" "git.rob.mx/nidito/event-gateway/internal/sink" "git.rob.mx/nidito/event-gateway/internal/sink/types" types1 "git.rob.mx/nidito/event-gateway/internal/source/types" "github.com/julienschmidt/httprouter" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) var log = logger.Sub("event-gateway.source.http") type listener struct { ID string Path string `json:"path"` Event types.Event } type Source struct { Router *httprouter.Router listeners map[string]*listener byPath map[string]*listener } var _ config.Source = &Source{} func New(router *httprouter.Router) *Source { if router == nil { router = httprouter.New() } s := &Source{ Router: router, } s.Initialize() return s } func (src *Source) Kind() types1.Kind { return types1.HTTP } func (src *Source) Initialize() { src.listeners = map[string]*listener{} src.byPath = map[string]*listener{} handler := otelhttp.NewHandler(src, "http-event") src.Router.Handler("GET", "/-/:source", handler) src.Router.Handler("POST", "/-/:source", handler) } func (src *Source) Register(l *config.Listener) error { source := &listener{ID: l.ID, Event: l.Event} if err := json.Unmarshal(l.Source.Config, &source); err != nil { return err } if source.Path == "" { return fmt.Errorf("no path set for http source %s: %+v", l.ID, l.Source.Config) } log.Debugf("http: registering %s (%s)", source.ID, source.Path) src.listeners[source.ID] = source src.byPath[source.Path] = source return nil } func (src *Source) Deregister(id string) { if existing, ok := src.listeners[id]; ok { log.Debugf("http: deregistering %s (%s)", id, existing.Path) delete(src.byPath, existing.Path) delete(src.listeners, id) } } func (src *Source) ServeHTTP(w http.ResponseWriter, r *http.Request) { p := httprouter.ParamsFromContext(r.Context()) var response string sourceID := p.ByName("source") source, ok := src.byPath[sourceID] span := trace.SpanFromContext(r.Context()) if ok { span.SetAttributes(attribute.String("source", source.ID)) log.Infof("Incoming event from source: http:%s", source.ID) response = "processed" go func(r *http.Request) { ctx := payload.Context(r.Context(), &Payload{Req: r.Clone(context.Background())}) if err := sink.Dispatch(ctx, source.Event); err != nil { log.Errorf("could not trigger source: %s\n", err) span.RecordError(err) span.SetStatus(codes.Error, "could not trigger source") span.End() return } span.SetStatus(codes.Ok, "") }(r) } else { span.SetAttributes(attribute.String("source", sourceID)) span.RecordError(fmt.Errorf("no listener for source")) span.End() response = "ignored" log.Debugf("Ignoring unknown source: http:%s", sourceID) } w.Write([]byte(response)) } func (src *Source) Events() []string { res := []string{} for _, l := range src.listeners { res = append(res, l.ID) } return res }