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  }