2017-08-03 00:58:26 +00:00
|
|
|
package zipkin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"compress/gzip"
|
2017-08-22 00:24:54 +00:00
|
|
|
"fmt"
|
2017-08-03 00:58:26 +00:00
|
|
|
"io/ioutil"
|
2017-08-22 00:24:54 +00:00
|
|
|
"mime"
|
2017-08-03 00:58:26 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2017-08-22 00:24:54 +00:00
|
|
|
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/jsonV1"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift"
|
2017-08-03 00:58:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// SpanHandler is an implementation of a Handler which accepts zipkin thrift
|
|
|
|
// span data and sends it to the recorder
|
|
|
|
type SpanHandler struct {
|
2017-08-22 00:24:54 +00:00
|
|
|
Path string
|
|
|
|
recorder Recorder
|
2017-08-03 00:58:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewSpanHandler returns a new server instance given path to handle
|
|
|
|
func NewSpanHandler(path string) *SpanHandler {
|
|
|
|
return &SpanHandler{
|
|
|
|
Path: path,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func cors(next http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
|
|
w.Header().Set(`Access-Control-Allow-Origin`, origin)
|
|
|
|
w.Header().Set(`Access-Control-Allow-Methods`, strings.Join([]string{
|
|
|
|
`OPTIONS`,
|
|
|
|
`POST`,
|
|
|
|
}, ", "))
|
|
|
|
|
|
|
|
w.Header().Set(`Access-Control-Allow-Headers`, strings.Join([]string{
|
|
|
|
`Accept`,
|
|
|
|
`Accept-Encoding`,
|
|
|
|
`Content-Length`,
|
|
|
|
`Content-Type`,
|
|
|
|
}, ", "))
|
|
|
|
|
|
|
|
w.Header().Set(`Access-Control-Expose-Headers`, strings.Join([]string{
|
|
|
|
`Date`,
|
|
|
|
}, ", "))
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register implements the Service interface. Register accepts zipkin thrift data
|
|
|
|
// POSTed to the path of the mux router
|
|
|
|
func (s *SpanHandler) Register(router *mux.Router, recorder Recorder) error {
|
|
|
|
handler := cors(http.HandlerFunc(s.Spans))
|
|
|
|
router.Handle(s.Path, handler).Methods("POST", "OPTIONS")
|
|
|
|
s.recorder = recorder
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spans handles zipkin thrift spans
|
|
|
|
func (s *SpanHandler) Spans(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
body := r.Body
|
|
|
|
var err error
|
|
|
|
// Handle gzip decoding of the body
|
|
|
|
if r.Header.Get("Content-Encoding") == "gzip" {
|
|
|
|
body, err = gzip.NewReader(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
s.recorder.Error(err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer body.Close()
|
|
|
|
}
|
|
|
|
|
2017-08-22 00:24:54 +00:00
|
|
|
decoder, err := ContentDecoder(r)
|
|
|
|
if err != nil {
|
|
|
|
s.recorder.Error(err)
|
|
|
|
w.WriteHeader(http.StatusUnsupportedMediaType)
|
|
|
|
}
|
|
|
|
|
2017-08-03 00:58:26 +00:00
|
|
|
octets, err := ioutil.ReadAll(body)
|
|
|
|
if err != nil {
|
|
|
|
s.recorder.Error(err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-22 00:24:54 +00:00
|
|
|
spans, err := decoder.Decode(octets)
|
2017-08-03 00:58:26 +00:00
|
|
|
if err != nil {
|
|
|
|
s.recorder.Error(err)
|
2017-08-22 00:24:54 +00:00
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2017-08-03 00:58:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-22 00:24:54 +00:00
|
|
|
trace, err := codec.NewTrace(spans)
|
|
|
|
if err != nil {
|
|
|
|
s.recorder.Error(err)
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2017-08-03 00:58:26 +00:00
|
|
|
|
|
|
|
if err = s.recorder.Record(trace); err != nil {
|
|
|
|
s.recorder.Error(err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2017-08-22 00:24:54 +00:00
|
|
|
// ContentDecoer returns a Decoder that is able to produce Traces from bytes.
|
|
|
|
// Failure should yield an HTTP 415 (`http.StatusUnsupportedMediaType`)
|
|
|
|
// If a Content-Type is not set, zipkin assumes application/json
|
|
|
|
func ContentDecoder(r *http.Request) (codec.Decoder, error) {
|
|
|
|
contentType := r.Header.Get("Content-Type")
|
|
|
|
if contentType == "" {
|
|
|
|
return &jsonV1.JSON{}, nil
|
2017-08-03 00:58:26 +00:00
|
|
|
}
|
|
|
|
|
2017-08-22 00:24:54 +00:00
|
|
|
for _, v := range strings.Split(contentType, ",") {
|
|
|
|
t, _, err := mime.ParseMediaType(v)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if t == "application/json" {
|
|
|
|
return &jsonV1.JSON{}, nil
|
|
|
|
} else if t == "application/x-thrift" {
|
|
|
|
return &thrift.Thrift{}, nil
|
2017-08-03 00:58:26 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-22 00:24:54 +00:00
|
|
|
return nil, fmt.Errorf("Unknown Content-Type: %s", contentType)
|
2017-08-03 00:58:26 +00:00
|
|
|
}
|