1
0
mirror of https://github.com/go-micro/go-micro.git synced 2024-12-18 08:26:38 +02:00
go-micro/api/handler/event/event.go
2024-06-04 21:40:43 +01:00

148 lines
3.0 KiB
Go

// Package event provides a handler which publishes an event
package event
import (
"encoding/json"
"fmt"
"net/http"
"path"
"regexp"
"strings"
"time"
"github.com/google/uuid"
"github.com/oxtoacart/bpool"
"go-micro.dev/v5/api/handler"
proto "go-micro.dev/v5/api/proto"
"go-micro.dev/v5/util/ctx"
)
var (
bufferPool = bpool.NewSizedBufferPool(1024, 8)
)
type event struct {
opts handler.Options
}
var (
// Handler is the name of this handler.
Handler = "event"
versionRe = regexp.MustCompilePOSIX("^v[0-9]+$")
)
func eventName(parts []string) string {
return strings.Join(parts, ".")
}
func evRoute(namespace, myPath string) (string, string) {
myPath = path.Clean(myPath)
myPath = strings.TrimPrefix(myPath, "/")
if len(myPath) == 0 {
return namespace, Handler
}
parts := strings.Split(myPath, "/")
// no path
if len(parts) == 0 {
// topic: namespace
// action: event
return strings.Trim(namespace, "."), Handler
}
// Treat /v[0-9]+ as versioning
// /v1/foo/bar => topic: v1.foo action: bar
if len(parts) >= 2 && versionRe.MatchString(parts[0]) {
topic := namespace + "." + strings.Join(parts[:2], ".")
action := eventName(parts[1:])
return topic, action
}
// /foo => topic: ns.foo action: foo
// /foo/bar => topic: ns.foo action: bar
topic := namespace + "." + strings.Join(parts[:1], ".")
action := eventName(parts[1:])
return topic, action
}
func (e *event) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
bsize := handler.DefaultMaxRecvSize
if e.opts.MaxRecvSize > 0 {
bsize = e.opts.MaxRecvSize
}
req.Body = http.MaxBytesReader(rsp, req.Body, bsize)
// request to topic:event
// create event
// publish to topic
topic, action := evRoute(e.opts.Namespace, req.URL.Path)
// create event
event := &proto.Event{
Name: action,
// TODO: dedupe event
Id: fmt.Sprintf("%s-%s-%s", topic, action, uuid.New().String()),
Header: make(map[string]*proto.Pair),
Timestamp: time.Now().Unix(),
}
// set headers
for key, vals := range req.Header {
header, ok := event.Header[key]
if !ok {
header = &proto.Pair{
Key: key,
}
event.Header[key] = header
}
header.Values = vals
}
// set body
if req.Method == http.MethodGet {
bytes, _ := json.Marshal(req.URL.Query())
event.Data = string(bytes)
} else {
// Read body
buf := bufferPool.Get()
defer bufferPool.Put(buf)
if _, err := buf.ReadFrom(req.Body); err != nil {
http.Error(rsp, err.Error(), http.StatusInternalServerError)
return
}
event.Data = buf.String()
}
// get client
c := e.opts.Client
// create publication
p := c.NewMessage(topic, event)
// publish event
if err := c.Publish(ctx.FromRequest(req), p); err != nil {
http.Error(rsp, err.Error(), http.StatusInternalServerError)
return
}
}
func (e *event) String() string {
return Handler
}
// NewHandler returns a new event handler.
func NewHandler(opts ...handler.Option) handler.Handler {
return &event{
opts: handler.NewOptions(opts...),
}
}