...

Source file src/go.opencensus.io/plugin/ochttp/propagation/tracecontext/propagation.go

Documentation: go.opencensus.io/plugin/ochttp/propagation/tracecontext

     1  // Copyright 2018, OpenCensus Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package tracecontext contains HTTP propagator for TraceContext standard.
    16  // See https://github.com/w3c/distributed-tracing for more information.
    17  package tracecontext // import "go.opencensus.io/plugin/ochttp/propagation/tracecontext"
    18  
    19  import (
    20  	"encoding/hex"
    21  	"fmt"
    22  	"net/http"
    23  	"net/textproto"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"go.opencensus.io/trace"
    28  	"go.opencensus.io/trace/propagation"
    29  	"go.opencensus.io/trace/tracestate"
    30  )
    31  
    32  const (
    33  	supportedVersion  = 0
    34  	maxVersion        = 254
    35  	maxTracestateLen  = 512
    36  	traceparentHeader = "traceparent"
    37  	tracestateHeader  = "tracestate"
    38  	trimOWSRegexFmt   = `^[\x09\x20]*(.*[^\x20\x09])[\x09\x20]*$`
    39  )
    40  
    41  var trimOWSRegExp = regexp.MustCompile(trimOWSRegexFmt)
    42  
    43  var _ propagation.HTTPFormat = (*HTTPFormat)(nil)
    44  
    45  // HTTPFormat implements the TraceContext trace propagation format.
    46  type HTTPFormat struct{}
    47  
    48  // SpanContextFromRequest extracts a span context from incoming requests.
    49  func (f *HTTPFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
    50  	tp, _ := getRequestHeader(req, traceparentHeader, false)
    51  	ts, _ := getRequestHeader(req, tracestateHeader, true)
    52  	return f.SpanContextFromHeaders(tp, ts)
    53  }
    54  
    55  // SpanContextFromHeaders extracts a span context from provided header values.
    56  func (f *HTTPFormat) SpanContextFromHeaders(tp string, ts string) (sc trace.SpanContext, ok bool) {
    57  	if tp == "" {
    58  		return trace.SpanContext{}, false
    59  	}
    60  	sections := strings.Split(tp, "-")
    61  	if len(sections) < 4 {
    62  		return trace.SpanContext{}, false
    63  	}
    64  
    65  	if len(sections[0]) != 2 {
    66  		return trace.SpanContext{}, false
    67  	}
    68  	ver, err := hex.DecodeString(sections[0])
    69  	if err != nil {
    70  		return trace.SpanContext{}, false
    71  	}
    72  	version := int(ver[0])
    73  	if version > maxVersion {
    74  		return trace.SpanContext{}, false
    75  	}
    76  
    77  	if version == 0 && len(sections) != 4 {
    78  		return trace.SpanContext{}, false
    79  	}
    80  
    81  	if len(sections[1]) != 32 {
    82  		return trace.SpanContext{}, false
    83  	}
    84  	tid, err := hex.DecodeString(sections[1])
    85  	if err != nil {
    86  		return trace.SpanContext{}, false
    87  	}
    88  	copy(sc.TraceID[:], tid)
    89  
    90  	if len(sections[2]) != 16 {
    91  		return trace.SpanContext{}, false
    92  	}
    93  	sid, err := hex.DecodeString(sections[2])
    94  	if err != nil {
    95  		return trace.SpanContext{}, false
    96  	}
    97  	copy(sc.SpanID[:], sid)
    98  
    99  	opts, err := hex.DecodeString(sections[3])
   100  	if err != nil || len(opts) < 1 {
   101  		return trace.SpanContext{}, false
   102  	}
   103  	sc.TraceOptions = trace.TraceOptions(opts[0])
   104  
   105  	// Don't allow all zero trace or span ID.
   106  	if sc.TraceID == [16]byte{} || sc.SpanID == [8]byte{} {
   107  		return trace.SpanContext{}, false
   108  	}
   109  
   110  	sc.Tracestate = tracestateFromHeader(ts)
   111  	return sc, true
   112  }
   113  
   114  // getRequestHeader returns a combined header field according to RFC7230 section 3.2.2.
   115  // If commaSeparated is true, multiple header fields with the same field name using be
   116  // combined using ",".
   117  // If no header was found using the given name, "ok" would be false.
   118  // If more than one headers was found using the given name, while commaSeparated is false,
   119  // "ok" would be false.
   120  func getRequestHeader(req *http.Request, name string, commaSeparated bool) (hdr string, ok bool) {
   121  	v := req.Header[textproto.CanonicalMIMEHeaderKey(name)]
   122  	switch len(v) {
   123  	case 0:
   124  		return "", false
   125  	case 1:
   126  		return v[0], true
   127  	default:
   128  		return strings.Join(v, ","), commaSeparated
   129  	}
   130  }
   131  
   132  // TODO(rghetia): return an empty Tracestate when parsing tracestate header encounters an error.
   133  // Revisit to return additional boolean value to indicate parsing error when following issues
   134  // are resolved.
   135  // https://github.com/w3c/distributed-tracing/issues/172
   136  // https://github.com/w3c/distributed-tracing/issues/175
   137  func tracestateFromHeader(ts string) *tracestate.Tracestate {
   138  	if ts == "" {
   139  		return nil
   140  	}
   141  
   142  	var entries []tracestate.Entry
   143  	pairs := strings.Split(ts, ",")
   144  	hdrLenWithoutOWS := len(pairs) - 1 // Number of commas
   145  	for _, pair := range pairs {
   146  		matches := trimOWSRegExp.FindStringSubmatch(pair)
   147  		if matches == nil {
   148  			return nil
   149  		}
   150  		pair = matches[1]
   151  		hdrLenWithoutOWS += len(pair)
   152  		if hdrLenWithoutOWS > maxTracestateLen {
   153  			return nil
   154  		}
   155  		kv := strings.Split(pair, "=")
   156  		if len(kv) != 2 {
   157  			return nil
   158  		}
   159  		entries = append(entries, tracestate.Entry{Key: kv[0], Value: kv[1]})
   160  	}
   161  	tsParsed, err := tracestate.New(nil, entries...)
   162  	if err != nil {
   163  		return nil
   164  	}
   165  
   166  	return tsParsed
   167  }
   168  
   169  func tracestateToHeader(sc trace.SpanContext) string {
   170  	var pairs = make([]string, 0, len(sc.Tracestate.Entries()))
   171  	if sc.Tracestate != nil {
   172  		for _, entry := range sc.Tracestate.Entries() {
   173  			pairs = append(pairs, strings.Join([]string{entry.Key, entry.Value}, "="))
   174  		}
   175  		h := strings.Join(pairs, ",")
   176  
   177  		if h != "" && len(h) <= maxTracestateLen {
   178  			return h
   179  		}
   180  	}
   181  	return ""
   182  }
   183  
   184  // SpanContextToHeaders serialize the SpanContext to traceparent and tracestate headers.
   185  func (f *HTTPFormat) SpanContextToHeaders(sc trace.SpanContext) (tp string, ts string) {
   186  	tp = fmt.Sprintf("%x-%x-%x-%x",
   187  		[]byte{supportedVersion},
   188  		sc.TraceID[:],
   189  		sc.SpanID[:],
   190  		[]byte{byte(sc.TraceOptions)})
   191  	ts = tracestateToHeader(sc)
   192  	return
   193  }
   194  
   195  // SpanContextToRequest modifies the given request to include traceparent and tracestate headers.
   196  func (f *HTTPFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
   197  	tp, ts := f.SpanContextToHeaders(sc)
   198  	req.Header.Set(traceparentHeader, tp)
   199  	if ts != "" {
   200  		req.Header.Set(tracestateHeader, ts)
   201  	}
   202  }
   203  

View as plain text