github.com/avenga/couper@v1.12.2/utils/server_timing.go (about) 1 package utils 2 3 import ( 4 "crypto/sha1" 5 "fmt" 6 "io" 7 "strings" 8 "time" 9 ) 10 11 type ServerTimings map[string]string 12 13 // CollectMetricNames collects metric names from the given Server-Timing HTTP header field. 14 // See https://www.w3.org/TR/server-timing/ 15 func CollectMetricNames(header string, timings ServerTimings) { 16 metrics := splitToMetrics(header) 17 18 for _, metric := range metrics { 19 metricName := "" 20 21 for len(metric) > 0 && isTokenChar(metric[0]) { 22 metricName += string(metric[0]) 23 24 metric = metric[1:] 25 } 26 27 if metricName == "" { 28 continue 29 } 30 31 timings[metricName] = "" 32 } 33 } 34 35 // MergeMetrics merges timings from 'src' into 'dest'. 36 func MergeMetrics(src, dest ServerTimings) { 37 var ( 38 suffix = "" 39 exists = true 40 h = sha1.New() 41 ) 42 43 for exists { 44 for k := range src { 45 _, exists = dest[k+suffix] 46 47 if exists { 48 io.WriteString(h, time.Now().String()) 49 50 suffix = string(fmt.Sprintf("_%x", h.Sum(nil)[:3])) 51 } 52 } 53 } 54 55 for k, v := range src { 56 dest[k+suffix] = v 57 } 58 } 59 60 func splitToMetrics(header string) []string { 61 var ( 62 part string 63 parts []string 64 ) 65 66 // Trim WS and ',' 67 trimLeft := func(s string) string { 68 return strings.TrimLeft(s, string([]byte{0, 9, 10, 11, 13, 32, 44})) 69 } 70 71 header = trimLeft(header) 72 73 for len(header) > 0 { 74 if header[0] == '"' { 75 // Consume '"' character 76 part += string(header[0]) 77 78 header = header[1:] 79 80 for len(header) > 0 && header[0] != '"' { 81 part += string(header[0]) 82 83 header = header[1:] 84 } 85 86 if len(header) == 0 { 87 parts = append(parts, strings.TrimSpace(part)) 88 89 break 90 } 91 92 // Consume '"' character 93 part += string(header[0]) 94 95 header = header[1:] 96 } else if header[0] == ',' { 97 parts = append(parts, strings.TrimSpace(part)) 98 99 // Trim WS and ',' 100 header = strings.TrimLeft(header, string([]byte{0, 9, 10, 11, 13, 32, 44})) 101 102 // Reset 103 part = "" 104 } else { 105 part += string(header[0]) 106 107 header = header[1:] 108 } 109 } 110 111 if part := strings.TrimSpace(part); part != "" { 112 parts = append(parts, part) 113 } 114 115 return parts 116 } 117 118 // https://httpwg.org/specs/rfc7230.html#rule.token.separators 119 func isTokenChar(ch byte) bool { 120 const separators = `()<>@,;:\"/[]?={}` + " " + "\t" 121 122 if ch <= 31 || ch == 127 { 123 return false 124 } 125 126 return !strings.Contains(separators, string(ch)) 127 }