mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* add jaeger support, link hot container & req span * adds jaeger support now with FN_JAEGER_URL, there's a simple tutorial in the operating/metrics.md file now and it's pretty easy to get up and running. * links a hot request span to a hot container span. when we change this to sample at a lower ratio we'll need to finagle the hot container span to always sample or something, otherwise we'll hide that info. at least, since we're sampling at 100% for now if this is flipped on, can see freeze/unfreeze etc. if they hit. this is useful for debugging. note that zipkin's exporter does not follow the link at all, hence jaeger... and they're backed by the Cloud Empire now (CNCF) so we'll probably use it anyway. * vendor: add thrift for jaeger
249 lines
5.9 KiB
Go
249 lines
5.9 KiB
Go
// Copyright 2013 Joshua Tacoma. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package uritemplates is a level 3 implementation of RFC 6570 (URI
|
|
// Template, http://tools.ietf.org/html/rfc6570).
|
|
// uritemplates does not support composite values (in Go: slices or maps)
|
|
// and so does not qualify as a level 4 implementation.
|
|
package uritemplates
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
|
|
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
|
|
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
|
|
hex = []byte("0123456789ABCDEF")
|
|
)
|
|
|
|
func pctEncode(src []byte) []byte {
|
|
dst := make([]byte, len(src)*3)
|
|
for i, b := range src {
|
|
buf := dst[i*3 : i*3+3]
|
|
buf[0] = 0x25
|
|
buf[1] = hex[b/16]
|
|
buf[2] = hex[b%16]
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// pairWriter is a convenience struct which allows escaped and unescaped
|
|
// versions of the template to be written in parallel.
|
|
type pairWriter struct {
|
|
escaped, unescaped bytes.Buffer
|
|
}
|
|
|
|
// Write writes the provided string directly without any escaping.
|
|
func (w *pairWriter) Write(s string) {
|
|
w.escaped.WriteString(s)
|
|
w.unescaped.WriteString(s)
|
|
}
|
|
|
|
// Escape writes the provided string, escaping the string for the
|
|
// escaped output.
|
|
func (w *pairWriter) Escape(s string, allowReserved bool) {
|
|
w.unescaped.WriteString(s)
|
|
if allowReserved {
|
|
w.escaped.Write(reserved.ReplaceAllFunc([]byte(s), pctEncode))
|
|
} else {
|
|
w.escaped.Write(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
|
|
}
|
|
}
|
|
|
|
// Escaped returns the escaped string.
|
|
func (w *pairWriter) Escaped() string {
|
|
return w.escaped.String()
|
|
}
|
|
|
|
// Unescaped returns the unescaped string.
|
|
func (w *pairWriter) Unescaped() string {
|
|
return w.unescaped.String()
|
|
}
|
|
|
|
// A uriTemplate is a parsed representation of a URI template.
|
|
type uriTemplate struct {
|
|
raw string
|
|
parts []templatePart
|
|
}
|
|
|
|
// parse parses a URI template string into a uriTemplate object.
|
|
func parse(rawTemplate string) (*uriTemplate, error) {
|
|
split := strings.Split(rawTemplate, "{")
|
|
parts := make([]templatePart, len(split)*2-1)
|
|
for i, s := range split {
|
|
if i == 0 {
|
|
if strings.Contains(s, "}") {
|
|
return nil, errors.New("unexpected }")
|
|
}
|
|
parts[i].raw = s
|
|
continue
|
|
}
|
|
subsplit := strings.Split(s, "}")
|
|
if len(subsplit) != 2 {
|
|
return nil, errors.New("malformed template")
|
|
}
|
|
expression := subsplit[0]
|
|
var err error
|
|
parts[i*2-1], err = parseExpression(expression)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parts[i*2].raw = subsplit[1]
|
|
}
|
|
return &uriTemplate{
|
|
raw: rawTemplate,
|
|
parts: parts,
|
|
}, nil
|
|
}
|
|
|
|
type templatePart struct {
|
|
raw string
|
|
terms []templateTerm
|
|
first string
|
|
sep string
|
|
named bool
|
|
ifemp string
|
|
allowReserved bool
|
|
}
|
|
|
|
type templateTerm struct {
|
|
name string
|
|
explode bool
|
|
truncate int
|
|
}
|
|
|
|
func parseExpression(expression string) (result templatePart, err error) {
|
|
switch expression[0] {
|
|
case '+':
|
|
result.sep = ","
|
|
result.allowReserved = true
|
|
expression = expression[1:]
|
|
case '.':
|
|
result.first = "."
|
|
result.sep = "."
|
|
expression = expression[1:]
|
|
case '/':
|
|
result.first = "/"
|
|
result.sep = "/"
|
|
expression = expression[1:]
|
|
case ';':
|
|
result.first = ";"
|
|
result.sep = ";"
|
|
result.named = true
|
|
expression = expression[1:]
|
|
case '?':
|
|
result.first = "?"
|
|
result.sep = "&"
|
|
result.named = true
|
|
result.ifemp = "="
|
|
expression = expression[1:]
|
|
case '&':
|
|
result.first = "&"
|
|
result.sep = "&"
|
|
result.named = true
|
|
result.ifemp = "="
|
|
expression = expression[1:]
|
|
case '#':
|
|
result.first = "#"
|
|
result.sep = ","
|
|
result.allowReserved = true
|
|
expression = expression[1:]
|
|
default:
|
|
result.sep = ","
|
|
}
|
|
rawterms := strings.Split(expression, ",")
|
|
result.terms = make([]templateTerm, len(rawterms))
|
|
for i, raw := range rawterms {
|
|
result.terms[i], err = parseTerm(raw)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func parseTerm(term string) (result templateTerm, err error) {
|
|
// TODO(djd): Remove "*" suffix parsing once we check that no APIs have
|
|
// mistakenly used that attribute.
|
|
if strings.HasSuffix(term, "*") {
|
|
result.explode = true
|
|
term = term[:len(term)-1]
|
|
}
|
|
split := strings.Split(term, ":")
|
|
if len(split) == 1 {
|
|
result.name = term
|
|
} else if len(split) == 2 {
|
|
result.name = split[0]
|
|
var parsed int64
|
|
parsed, err = strconv.ParseInt(split[1], 10, 0)
|
|
result.truncate = int(parsed)
|
|
} else {
|
|
err = errors.New("multiple colons in same term")
|
|
}
|
|
if !validname.MatchString(result.name) {
|
|
err = errors.New("not a valid name: " + result.name)
|
|
}
|
|
if result.explode && result.truncate > 0 {
|
|
err = errors.New("both explode and prefix modifers on same term")
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// Expand expands a URI template with a set of values to produce the
|
|
// resultant URI. Two forms of the result are returned: one with all the
|
|
// elements escaped, and one with the elements unescaped.
|
|
func (t *uriTemplate) Expand(values map[string]string) (escaped, unescaped string) {
|
|
var w pairWriter
|
|
for _, p := range t.parts {
|
|
p.expand(&w, values)
|
|
}
|
|
return w.Escaped(), w.Unescaped()
|
|
}
|
|
|
|
func (tp *templatePart) expand(w *pairWriter, values map[string]string) {
|
|
if len(tp.raw) > 0 {
|
|
w.Write(tp.raw)
|
|
return
|
|
}
|
|
var first = true
|
|
for _, term := range tp.terms {
|
|
value, exists := values[term.name]
|
|
if !exists {
|
|
continue
|
|
}
|
|
if first {
|
|
w.Write(tp.first)
|
|
first = false
|
|
} else {
|
|
w.Write(tp.sep)
|
|
}
|
|
tp.expandString(w, term, value)
|
|
}
|
|
}
|
|
|
|
func (tp *templatePart) expandName(w *pairWriter, name string, empty bool) {
|
|
if tp.named {
|
|
w.Write(name)
|
|
if empty {
|
|
w.Write(tp.ifemp)
|
|
} else {
|
|
w.Write("=")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (tp *templatePart) expandString(w *pairWriter, t templateTerm, s string) {
|
|
if len(s) > t.truncate && t.truncate > 0 {
|
|
s = s[:t.truncate]
|
|
}
|
|
tp.expandName(w, t.name, len(s) == 0)
|
|
w.Escape(s, tp.allowReserved)
|
|
}
|