mirror of https://gitlab.com/ngerakines/tavern.git
202 lines
5.5 KiB
Go
202 lines
5.5 KiB
Go
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
// HTTPClient provides the http.Do function in a generic way.
|
|
type HTTPClient interface {
|
|
Do(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
type AcceptHeader struct {
|
|
Type string
|
|
SubType string
|
|
}
|
|
|
|
type AcceptHeaders []AcceptHeader
|
|
|
|
// DefaultHTTPClient returns an HTTPClient with a reasonable default timeout.
|
|
func DefaultHTTPClient() *http.Client {
|
|
return &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
|
|
// By default, the HTTP client shouldn't follow redirects.
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
}
|
|
|
|
func InstrumentedDefaultHTTPClient(metricFactory promauto.Factory, namespace, subsystem string) *http.Client {
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
|
|
// By default, the HTTP client shouldn't follow redirects.
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
|
|
inFlightGauge := metricFactory.NewGauge(prometheus.GaugeOpts{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
Name: "in_flight_requests",
|
|
Help: "A gauge of in-flight requests for the wrapped client.",
|
|
})
|
|
|
|
counter := metricFactory.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
Name: "requests_total",
|
|
Help: "A counter for requests from the wrapped client.",
|
|
},
|
|
[]string{"code", "method"},
|
|
)
|
|
|
|
dnsLatencyVec := metricFactory.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
Name: "dns_duration_seconds",
|
|
Help: "Trace dns latency histogram.",
|
|
Buckets: []float64{.005, .01, .025, .05},
|
|
},
|
|
[]string{"event"},
|
|
)
|
|
|
|
tlsLatencyVec := metricFactory.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
Name: "tls_duration_seconds",
|
|
Help: "Trace tls latency histogram.",
|
|
Buckets: []float64{.05, .1, .25, .5},
|
|
},
|
|
[]string{"event"},
|
|
)
|
|
|
|
histVec := metricFactory.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: namespace,
|
|
Subsystem: subsystem,
|
|
Name: "request_duration_seconds",
|
|
Help: "A histogram of request latencies.",
|
|
Buckets: prometheus.DefBuckets,
|
|
},
|
|
[]string{"method"},
|
|
)
|
|
|
|
trace := &promhttp.InstrumentTrace{
|
|
DNSStart: func(t float64) {
|
|
dnsLatencyVec.WithLabelValues("dns_start").Observe(t)
|
|
},
|
|
DNSDone: func(t float64) {
|
|
dnsLatencyVec.WithLabelValues("dns_done").Observe(t)
|
|
},
|
|
TLSHandshakeStart: func(t float64) {
|
|
tlsLatencyVec.WithLabelValues("tls_handshake_start").Observe(t)
|
|
},
|
|
TLSHandshakeDone: func(t float64) {
|
|
tlsLatencyVec.WithLabelValues("tls_handshake_done").Observe(t)
|
|
},
|
|
}
|
|
|
|
client.Transport = promhttp.InstrumentRoundTripperInFlight(inFlightGauge,
|
|
promhttp.InstrumentRoundTripperCounter(counter,
|
|
promhttp.InstrumentRoundTripperTrace(trace,
|
|
promhttp.InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
|
|
),
|
|
),
|
|
)
|
|
return client
|
|
}
|
|
|
|
func ParseAccept(input string) AcceptHeaders {
|
|
// From the spec, https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
// If no Accept header field is present, then it is assumed that the client
|
|
// accepts all media types.
|
|
if len(input) == 0 {
|
|
return []AcceptHeader{
|
|
{Type: "*", SubType: "*"},
|
|
}
|
|
}
|
|
|
|
parts := strings.FieldsFunc(input, func(r rune) bool {
|
|
return r == ','
|
|
})
|
|
results := make([]AcceptHeader, 0)
|
|
for _, part := range parts {
|
|
mediaType := part
|
|
if pos := strings.IndexRune(mediaType, ';'); pos > -1 {
|
|
mediaType = mediaType[0:pos]
|
|
}
|
|
mediaTypeParts := strings.Split(mediaType, "/")
|
|
if len(mediaTypeParts) == 2 {
|
|
results = append(results, AcceptHeader{
|
|
Type: mediaTypeParts[0],
|
|
SubType: mediaTypeParts[1],
|
|
})
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (a AcceptHeader) String() string {
|
|
return fmt.Sprintf("%s/%s", a.Type, a.SubType)
|
|
}
|
|
|
|
// Match returns true if a given type is contained. This assumes the order of
|
|
// accepted headers and matching types is provided as intended.
|
|
// (text/plain, */*).Match(text/plain) => true (text/plain)
|
|
// (text/plain, */*).Match(*/*) => true (*/*)
|
|
// (text/*, */*).Match(text/plain) => true (text/plain)
|
|
// (text/*, text/plain).Match(text/plain) => true (text/*)
|
|
func (a AcceptHeaders) Match(exact bool, types ...string) (string, string, bool) {
|
|
parsedTypes := make(AcceptHeaders, 0)
|
|
for _, t := range types {
|
|
mediaTypeParts := strings.Split(t, "/")
|
|
if len(mediaTypeParts) == 2 {
|
|
parsedTypes = append(parsedTypes, AcceptHeader{
|
|
Type: mediaTypeParts[0],
|
|
SubType: mediaTypeParts[1],
|
|
})
|
|
}
|
|
}
|
|
for _, acceptHeader := range a {
|
|
for _, parsedType := range parsedTypes {
|
|
// fmt.Println("accept", acceptHeader.Type, acceptHeader.SubType, "parsed", parsedType.Type, parsedType.SubType)
|
|
if !exact && acceptHeader.Type == "*" && acceptHeader.SubType == "*" {
|
|
return acceptHeader.String(), parsedType.String(), true
|
|
}
|
|
if acceptHeader.Type == parsedType.Type {
|
|
if !exact && acceptHeader.SubType == "*" {
|
|
return acceptHeader.String(), parsedType.String(), true
|
|
}
|
|
if acceptHeader.SubType == parsedType.SubType {
|
|
return acceptHeader.String(), parsedType.String(), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
func (a AcceptHeaders) MatchJRD(exact bool) bool {
|
|
_, _, m := a.Match(exact, "application/json")
|
|
return m
|
|
}
|
|
|
|
func (a AcceptHeaders) MatchHTML(exact bool) bool {
|
|
_, _, m := a.Match(exact, "text/html")
|
|
return m
|
|
}
|