event-gateway/internal/source/http/http.go

129 lines
3.3 KiB
Go

// Copyright © 2023 Roberto Hidalgo <event-gateway@un.rob.mx>
// 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
}