github.com/btccom/go-micro/v2@v2.9.3/api/handler/event/event.go (about)

     1  // Package event provides a handler which publishes an event
     2  package event
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"path"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/google/uuid"
    14  	"github.com/btccom/go-micro/v2/api/handler"
    15  	proto "github.com/btccom/go-micro/v2/api/proto"
    16  	"github.com/btccom/go-micro/v2/util/ctx"
    17  	"github.com/oxtoacart/bpool"
    18  )
    19  
    20  var (
    21  	bufferPool = bpool.NewSizedBufferPool(1024, 8)
    22  )
    23  
    24  type event struct {
    25  	opts handler.Options
    26  }
    27  
    28  var (
    29  	Handler   = "event"
    30  	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$")
    31  )
    32  
    33  func eventName(parts []string) string {
    34  	return strings.Join(parts, ".")
    35  }
    36  
    37  func evRoute(ns, p string) (string, string) {
    38  	p = path.Clean(p)
    39  	p = strings.TrimPrefix(p, "/")
    40  
    41  	if len(p) == 0 {
    42  		return ns, "event"
    43  	}
    44  
    45  	parts := strings.Split(p, "/")
    46  
    47  	// no path
    48  	if len(parts) == 0 {
    49  		// topic: namespace
    50  		// action: event
    51  		return strings.Trim(ns, "."), "event"
    52  	}
    53  
    54  	// Treat /v[0-9]+ as versioning
    55  	// /v1/foo/bar => topic: v1.foo action: bar
    56  	if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) {
    57  		topic := ns + "." + strings.Join(parts[:2], ".")
    58  		action := eventName(parts[1:])
    59  		return topic, action
    60  	}
    61  
    62  	// /foo => topic: ns.foo action: foo
    63  	// /foo/bar => topic: ns.foo action: bar
    64  	topic := ns + "." + strings.Join(parts[:1], ".")
    65  	action := eventName(parts[1:])
    66  
    67  	return topic, action
    68  }
    69  
    70  func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    71  	bsize := handler.DefaultMaxRecvSize
    72  	if e.opts.MaxRecvSize > 0 {
    73  		bsize = e.opts.MaxRecvSize
    74  	}
    75  
    76  	r.Body = http.MaxBytesReader(w, r.Body, bsize)
    77  
    78  	// request to topic:event
    79  	// create event
    80  	// publish to topic
    81  
    82  	topic, action := evRoute(e.opts.Namespace, r.URL.Path)
    83  
    84  	// create event
    85  	ev := &proto.Event{
    86  		Name: action,
    87  		// TODO: dedupe event
    88  		Id:        fmt.Sprintf("%s-%s-%s", topic, action, uuid.New().String()),
    89  		Header:    make(map[string]*proto.Pair),
    90  		Timestamp: time.Now().Unix(),
    91  	}
    92  
    93  	// set headers
    94  	for key, vals := range r.Header {
    95  		header, ok := ev.Header[key]
    96  		if !ok {
    97  			header = &proto.Pair{
    98  				Key: key,
    99  			}
   100  			ev.Header[key] = header
   101  		}
   102  		header.Values = vals
   103  	}
   104  
   105  	// set body
   106  	if r.Method == "GET" {
   107  		bytes, _ := json.Marshal(r.URL.Query())
   108  		ev.Data = string(bytes)
   109  	} else {
   110  		// Read body
   111  		buf := bufferPool.Get()
   112  		defer bufferPool.Put(buf)
   113  		if _, err := buf.ReadFrom(r.Body); err != nil {
   114  			http.Error(w, err.Error(), 500)
   115  			return
   116  		}
   117  		ev.Data = buf.String()
   118  	}
   119  
   120  	// get client
   121  	c := e.opts.Client
   122  
   123  	// create publication
   124  	p := c.NewMessage(topic, ev)
   125  
   126  	// publish event
   127  	if err := c.Publish(ctx.FromRequest(r), p); err != nil {
   128  		http.Error(w, err.Error(), 500)
   129  		return
   130  	}
   131  }
   132  
   133  func (e *event) String() string {
   134  	return "event"
   135  }
   136  
   137  func NewHandler(opts ...handler.Option) handler.Handler {
   138  	return &event{
   139  		opts: handler.NewOptions(opts...),
   140  	}
   141  }