You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

188 lines
5.3 KiB

  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. // Package tracecontext contains HTTP propagator for TraceContext standard.
  15. // See https://github.com/w3c/distributed-tracing for more information.
  16. package tracecontext // import "go.opencensus.io/plugin/ochttp/propagation/tracecontext"
  17. import (
  18. "encoding/hex"
  19. "fmt"
  20. "net/http"
  21. "net/textproto"
  22. "regexp"
  23. "strings"
  24. "go.opencensus.io/trace"
  25. "go.opencensus.io/trace/propagation"
  26. "go.opencensus.io/trace/tracestate"
  27. )
  28. const (
  29. supportedVersion = 0
  30. maxVersion = 254
  31. maxTracestateLen = 512
  32. traceparentHeader = "traceparent"
  33. tracestateHeader = "tracestate"
  34. trimOWSRegexFmt = `^[\x09\x20]*(.*[^\x20\x09])[\x09\x20]*$`
  35. )
  36. var trimOWSRegExp = regexp.MustCompile(trimOWSRegexFmt)
  37. var _ propagation.HTTPFormat = (*HTTPFormat)(nil)
  38. // HTTPFormat implements the TraceContext trace propagation format.
  39. type HTTPFormat struct{}
  40. // SpanContextFromRequest extracts a span context from incoming requests.
  41. func (f *HTTPFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
  42. h, ok := getRequestHeader(req, traceparentHeader, false)
  43. if !ok {
  44. return trace.SpanContext{}, false
  45. }
  46. sections := strings.Split(h, "-")
  47. if len(sections) < 4 {
  48. return trace.SpanContext{}, false
  49. }
  50. if len(sections[0]) != 2 {
  51. return trace.SpanContext{}, false
  52. }
  53. ver, err := hex.DecodeString(sections[0])
  54. if err != nil {
  55. return trace.SpanContext{}, false
  56. }
  57. version := int(ver[0])
  58. if version > maxVersion {
  59. return trace.SpanContext{}, false
  60. }
  61. if version == 0 && len(sections) != 4 {
  62. return trace.SpanContext{}, false
  63. }
  64. if len(sections[1]) != 32 {
  65. return trace.SpanContext{}, false
  66. }
  67. tid, err := hex.DecodeString(sections[1])
  68. if err != nil {
  69. return trace.SpanContext{}, false
  70. }
  71. copy(sc.TraceID[:], tid)
  72. if len(sections[2]) != 16 {
  73. return trace.SpanContext{}, false
  74. }
  75. sid, err := hex.DecodeString(sections[2])
  76. if err != nil {
  77. return trace.SpanContext{}, false
  78. }
  79. copy(sc.SpanID[:], sid)
  80. opts, err := hex.DecodeString(sections[3])
  81. if err != nil || len(opts) < 1 {
  82. return trace.SpanContext{}, false
  83. }
  84. sc.TraceOptions = trace.TraceOptions(opts[0])
  85. // Don't allow all zero trace or span ID.
  86. if sc.TraceID == [16]byte{} || sc.SpanID == [8]byte{} {
  87. return trace.SpanContext{}, false
  88. }
  89. sc.Tracestate = tracestateFromRequest(req)
  90. return sc, true
  91. }
  92. // getRequestHeader returns a combined header field according to RFC7230 section 3.2.2.
  93. // If commaSeparated is true, multiple header fields with the same field name using be
  94. // combined using ",".
  95. // If no header was found using the given name, "ok" would be false.
  96. // If more than one headers was found using the given name, while commaSeparated is false,
  97. // "ok" would be false.
  98. func getRequestHeader(req *http.Request, name string, commaSeparated bool) (hdr string, ok bool) {
  99. v := req.Header[textproto.CanonicalMIMEHeaderKey(name)]
  100. switch len(v) {
  101. case 0:
  102. return "", false
  103. case 1:
  104. return v[0], true
  105. default:
  106. return strings.Join(v, ","), commaSeparated
  107. }
  108. }
  109. // TODO(rghetia): return an empty Tracestate when parsing tracestate header encounters an error.
  110. // Revisit to return additional boolean value to indicate parsing error when following issues
  111. // are resolved.
  112. // https://github.com/w3c/distributed-tracing/issues/172
  113. // https://github.com/w3c/distributed-tracing/issues/175
  114. func tracestateFromRequest(req *http.Request) *tracestate.Tracestate {
  115. h, _ := getRequestHeader(req, tracestateHeader, true)
  116. if h == "" {
  117. return nil
  118. }
  119. var entries []tracestate.Entry
  120. pairs := strings.Split(h, ",")
  121. hdrLenWithoutOWS := len(pairs) - 1 // Number of commas
  122. for _, pair := range pairs {
  123. matches := trimOWSRegExp.FindStringSubmatch(pair)
  124. if matches == nil {
  125. return nil
  126. }
  127. pair = matches[1]
  128. hdrLenWithoutOWS += len(pair)
  129. if hdrLenWithoutOWS > maxTracestateLen {
  130. return nil
  131. }
  132. kv := strings.Split(pair, "=")
  133. if len(kv) != 2 {
  134. return nil
  135. }
  136. entries = append(entries, tracestate.Entry{Key: kv[0], Value: kv[1]})
  137. }
  138. ts, err := tracestate.New(nil, entries...)
  139. if err != nil {
  140. return nil
  141. }
  142. return ts
  143. }
  144. func tracestateToRequest(sc trace.SpanContext, req *http.Request) {
  145. var pairs = make([]string, 0, len(sc.Tracestate.Entries()))
  146. if sc.Tracestate != nil {
  147. for _, entry := range sc.Tracestate.Entries() {
  148. pairs = append(pairs, strings.Join([]string{entry.Key, entry.Value}, "="))
  149. }
  150. h := strings.Join(pairs, ",")
  151. if h != "" && len(h) <= maxTracestateLen {
  152. req.Header.Set(tracestateHeader, h)
  153. }
  154. }
  155. }
  156. // SpanContextToRequest modifies the given request to include traceparent and tracestate headers.
  157. func (f *HTTPFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
  158. h := fmt.Sprintf("%x-%x-%x-%x",
  159. []byte{supportedVersion},
  160. sc.TraceID[:],
  161. sc.SpanID[:],
  162. []byte{byte(sc.TraceOptions)})
  163. req.Header.Set(traceparentHeader, h)
  164. tracestateToRequest(sc, req)
  165. }