Skip to content
Snippets Groups Projects
collector.go 2.27 KiB
Newer Older
  • Learn to ignore specific revisions
  • ale's avatar
    ale committed
    package reportscollector
    
    import (
    	"errors"
    	"fmt"
    	"log"
    	"mime"
    	"net/http"
    
    ale's avatar
    ale committed
    
    	"github.com/prometheus/client_golang/prometheus"
    
    ale's avatar
    ale committed
    )
    
    var ErrNoMatch = errors.New("no match")
    
    type Handler interface {
    	Parse(string, *http.Request) ([]Event, error)
    }
    
    type Sink interface {
    	Send(Event)
    }
    
    type Collector struct {
    	handlers []Handler
    	sink     Sink
    }
    
    func NewCollector(sink Sink, handlers ...Handler) *Collector {
    	return &Collector{
    		handlers: handlers,
    		sink:     sink,
    	}
    }
    
    func (c *Collector) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    
    	switch req.Method {
    	case http.MethodPost:
    	case http.MethodOptions:
    		w.Header().Set("Access-Control-Allow-Origin", "*")
    		w.Header().Set("Access-Control-Allow-Methods", "POST")
    		w.Header().Set("Access-Control-Allow-Headers", "content-type")
    		w.Header().Set("Access-Control-Max-Age", "86400")
    		w.WriteHeader(http.StatusNoContent)
    		return
    	default:
    
    ale's avatar
    ale committed
    		http.Error(w, "Bad method", http.StatusMethodNotAllowed)
    		return
    	}
    
    	ct, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
    	if err != nil {
    		http.Error(w, fmt.Sprintf("Bad Content-Type: %v", err.Error()), http.StatusBadRequest)
    		log.Printf("error parsing content-type: %v", err)
    		return
    	}
    
    	// Find a handler that can successfully parse the request, and
    	// get a list of Events.
    	var events []Event
    
    ale's avatar
    ale committed
    	matched := false
    
    ale's avatar
    ale committed
    hloop:
    	for _, h := range c.handlers {
    		var err error
    		events, err = h.Parse(ct, req)
    		switch err {
    		case ErrNoMatch:
    			continue
    		case nil:
    
    ale's avatar
    ale committed
    			matched = true
    
    			w.WriteHeader(http.StatusOK)
    
    ale's avatar
    ale committed
    			break hloop
    		default:
    
    ale's avatar
    ale committed
    			log.Printf("error parsing report (%s): %v", ct, err)
    
    ale's avatar
    ale committed
    			http.Error(w, err.Error(), http.StatusBadRequest)
    			return
    		}
    	}
    
    
    ale's avatar
    ale committed
    	if !matched {
    		log.Printf("no matching handlers for \"%s\"", ct)
    		http.Error(w, "No matching handlers", http.StatusBadRequest)
    		return
    	}
    
    
    ale's avatar
    ale committed
    	// Augment the Events with additional information obtained
    	// from the HTTP request, and send them to the forwarder.
    	for _, e := range events {
    		c.sink.Send(e)
    
    ale's avatar
    ale committed
    		reportsByType.WithLabelValues(
    			e.GetString("type"), e.GetString("domain")).Inc()
    
    ale's avatar
    ale committed
    	}
    }
    
    ale's avatar
    ale committed
    
    var (
    	reportsByType = prometheus.NewCounterVec(
    		prometheus.CounterOpts{
    			Name: "reports_total",
    			Help: "Number of reports by type.",
    		},
    		[]string{"type", "domain"},
    	)
    )
    
    
    func init() {
    	prometheus.MustRegister(reportsByType)
    }