STOR-519: Add db-connect, a SQL over HTTPS server

This commit is contained in:
Ashcon Partovi
2019-08-20 13:13:29 -05:00
parent 28f6c2ed7c
commit 5da2109811
410 changed files with 362649 additions and 666 deletions

View File

@@ -0,0 +1,12 @@
language: go
go:
- 1.9.x
- tip
script:
- go test
matrix:
allow_failures:
- go: tip

21
vendor/github.com/mitchellh/go-server-timing/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

96
vendor/github.com/mitchellh/go-server-timing/README.md generated vendored Normal file
View File

@@ -0,0 +1,96 @@
# HTTP Server-Timing for Go
[![Godoc](https://godoc.org/github.com/mitchellh/go-server-timing?status.svg)](https://godoc.org/github.com/mitchellh/go-server-timing)
This is a library including middleware for using
[HTTP Server-Timing](https://www.w3.org/TR/server-timing) with Go. This header
allows a server to send timing information from the backend, such as database
access time, file reads, etc. The timing information can be then be inspected
in the standard browser developer tools:
![Server Timing Example](https://raw.githubusercontent.com/mitchellh/go-server-timing/master/example/screenshot.png)
## Features
* Middleware for injecting the server timing struct into the request `Context`
and writing the `Server-Timing` header.
* Concurrency-safe structures for easily recording timings of multiple
concurrency tasks.
* Parse `Server-Timing` headers as a client.
* Note: No browser properly supports sending the Server-Timing header as
an [HTTP Trailer](https://tools.ietf.org/html/rfc7230#section-4.4) so
the Middleware only supports a normal header currently.
## Browser Support
Browser support is required to **view** server timings easily. Because server
timings are sent as an HTTP header, there is no negative impact to sending
the header to unsupported browsers.
* **Chrome 65 or higher** is required to properly display server timings
in the devtools.
* **Firefox is pending** with an [open bug report (ID 1403051)](https://bugzilla.mozilla.org/show_bug.cgi?id=1403051)
* IE, Opera, and others are unknown at this time.
## Usage
Example usage is shown below. A fully runnable example is available in
the `example/` directory.
```go
func main() {
// Our handler. In a real application this might be your root router,
// or some subset of your router. Wrapping this ensures that all routes
// handled by this handler have access to the server timing header struct.
var h http.Handler = http.HandlerFunc(handler)
// Wrap our handler with the server timing middleware
h = servertiming.Middleware(h, nil)
// Start!
http.ListenAndServe(":8080", h)
}
func handler(w http.ResponseWriter, r *http.Request) {
// Get our timing header builder from the context
timing := servertiming.FromContext(r.Context())
// Imagine your handler performs some tasks in a goroutine, such as
// accessing some remote service. timing is concurrency safe so we can
// record how long that takes. Let's simulate making 5 concurrent requests
// to various servicse.
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
name := fmt.Sprintf("service-%d", i)
go func(name string) {
// This creats a new metric and starts the timer. The Stop is
// deferred so when the function exits it'll record the duration.
defer timing.NewMetric(name).Start().Stop()
time.Sleep(random(25, 75))
wg.Done()
}(name)
}
// Imagine this is just some blocking code in your main handler such
// as a SQL query. Let's record that.
m := timing.NewMetric("sql").WithDesc("SQL query").Start()
time.Sleep(random(20, 50))
m.Stop()
// Wait for the goroutine to end
wg.Wait()
// You could continue recording more metrics, but let's just return now
w.WriteHeader(200)
w.Write([]byte("Done. Check your browser inspector timing details."))
}
func random(min, max int) time.Duration {
return (time.Duration(rand.Intn(max-min) + min)) * time.Millisecond
}
```

View File

@@ -0,0 +1,23 @@
package servertiming
import (
"context"
)
// NewContext returns a new Context that carries the Header value h.
func NewContext(ctx context.Context, h *Header) context.Context {
return context.WithValue(ctx, contextKey, h)
}
// FromContext returns the *Header in the context, if any. If no Header
// value exists, nil is returned.
func FromContext(ctx context.Context) *Header {
h, _ := ctx.Value(contextKey).(*Header)
return h
}
type contextKeyType struct{}
// The key where the header value is stored. This is globally unique since
// it uses a custom unexported type. The struct{} costs zero allocations.
var contextKey = contextKeyType(struct{}{})

6
vendor/github.com/mitchellh/go-server-timing/go.mod generated vendored Normal file
View File

@@ -0,0 +1,6 @@
module github.com/mitchellh/go-server-timing
require (
github.com/felixge/httpsnoop v1.0.0
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5
)

4
vendor/github.com/mitchellh/go-server-timing/go.sum generated vendored Normal file
View File

@@ -0,0 +1,4 @@
github.com/felixge/httpsnoop v1.0.0 h1:gh8fMGz0rlOv/1WmRZm7OgncIOTsAj21iNJot48omJQ=
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5 h1:yrv1uUvgXH/tEat+wdvJMRJ4g51GlIydtDpU9pFjaaI=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=

129
vendor/github.com/mitchellh/go-server-timing/header.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
package servertiming
import (
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/golang/gddo/httputil/header"
)
// HeaderKey is the specified key for the Server-Timing header.
const HeaderKey = "Server-Timing"
// Header represents a collection of metrics that can be encoded as
// a Server-Timing header value.
//
// The functions for working with metrics are concurrency-safe to make
// it easy to record metrics from goroutines. If you want to avoid the
// lock overhead, you can access the Metrics field directly.
//
// The functions for working with metrics are also usable on a nil
// Header pointer. This allows functions that use FromContext to get the
// *Header value to skip nil-checking and use it as normal. On a nil
// *Header, Metrics are not recorded.
type Header struct {
// Metrics is the list of metrics in the header.
Metrics []*Metric
// The lock that is held when Metrics is being modified. This
// ONLY NEEDS TO BE SET WHEN working with Metrics directly. If using
// the functions on the struct, the lock is managed automatically.
sync.Mutex
}
// ParseHeader parses a Server-Timing header value.
func ParseHeader(input string) (*Header, error) {
// Split the comma-separated list of metrics
rawMetrics := header.ParseList(headerParams(input))
// Parse the list of metrics. We can pre-allocate the length of the
// comma-separated list of metrics since at most it will be that and
// most likely it will be that length.
metrics := make([]*Metric, 0, len(rawMetrics))
for _, raw := range rawMetrics {
var m Metric
m.Name, m.Extra = header.ParseValueAndParams(headerParams(raw))
// Description
if v, ok := m.Extra[paramNameDesc]; ok {
m.Desc = v
delete(m.Extra, paramNameDesc)
}
// Duration. This is treated as a millisecond value since that
// is what modern browsers are treating it as. If the parsing of
// an integer fails, the set value remains in the Extra field.
if v, ok := m.Extra[paramNameDur]; ok {
m.Duration, _ = time.ParseDuration(v + "ms")
delete(m.Extra, paramNameDur)
}
metrics = append(metrics, &m)
}
return &Header{Metrics: metrics}, nil
}
// NewMetric creates a new Metric and adds it to this header.
func (h *Header) NewMetric(name string) *Metric {
return h.Add(&Metric{Name: name})
}
// Add adds the given metric to the header.
//
// This function is safe to call concurrently.
func (h *Header) Add(m *Metric) *Metric {
if h == nil {
return m
}
h.Lock()
defer h.Unlock()
h.Metrics = append(h.Metrics, m)
return m
}
// String returns the valid Server-Timing header value that can be
// sent in an HTTP response.
func (h *Header) String() string {
parts := make([]string, 0, len(h.Metrics))
for _, m := range h.Metrics {
parts = append(parts, m.String())
}
return strings.Join(parts, ",")
}
// Specified server-timing-param-name values.
const (
paramNameDesc = "desc"
paramNameDur = "dur"
)
// headerParams is a helper function that takes a header value and turns
// it into the expected argument format for the httputil/header library
// functions..
func headerParams(s string) (http.Header, string) {
const key = "Key"
return http.Header(map[string][]string{
key: {s},
}), key
}
var reNumber = regexp.MustCompile(`^\d+\.?\d*$`)
// headerEncodeParam encodes a key/value pair as a proper `key=value`
// syntax, using double-quotes if necessary.
func headerEncodeParam(key, value string) string {
// The only case we currently don't quote is numbers. We can make this
// smarter in the future.
if reNumber.MatchString(value) {
return fmt.Sprintf(`%s=%s`, key, value)
}
return fmt.Sprintf(`%s=%q`, key, value)
}

125
vendor/github.com/mitchellh/go-server-timing/metric.go generated vendored Normal file
View File

@@ -0,0 +1,125 @@
package servertiming
import (
"fmt"
"strconv"
"strings"
"time"
)
// Metric represents a single metric for the Server-Timing header.
//
// The easiest way to use the Metric is to use NewMetric and chain it. This
// results in a single line defer at the top of a function time a function.
//
// timing := FromContext(r.Context())
// defer timing.NewMetric("sql").Start().Stop()
//
// For timing around specific blocks of code:
//
// m := timing.NewMetric("sql").Start()
// // ... run your code being timed here
// m.Stop()
//
// A metric is expected to represent a single timing event. Therefore,
// no functions on the struct are safe for concurrency by default. If a single
// Metric is shared by multiple concurrenty goroutines, you must lock access
// manually.
type Metric struct {
// Name is the name of the metric. This must be a valid RFC7230 "token"
// format. In a gist, this is an alphanumeric string that may contain
// most common symbols but may not contain any whitespace. The exact
// syntax can be found in RFC7230.
//
// It is common for Name to be a unique identifier (such as "sql-1") and
// for a more human-friendly name to be used in the "desc" field.
Name string
// Duration is the duration of this Metric.
Duration time.Duration
// Desc is any string describing this metric. For example: "SQL Primary".
// The specific format of this is `token | quoted-string` according to
// RFC7230.
Desc string
// Extra is a set of extra parameters and values to send with the
// metric. The specification states that unrecognized parameters are
// to be ignored so it should be safe to add additional data here. The
// key must be a valid "token" (same syntax as Name) and the value can
// be any "token | quoted-string" (same as Desc field).
//
// If this map contains a key that would be sent by another field in this
// struct (such as "desc"), then this value is prioritized over the
// struct value.
Extra map[string]string
// startTime is the time that this metric recording was started if
// Start() was called.
startTime time.Time
}
// WithDesc is a chaining-friendly helper to set the Desc field on the Metric.
func (m *Metric) WithDesc(desc string) *Metric {
m.Desc = desc
return m
}
// Start starts a timer for recording the duration of some task. This must
// be paired with a Stop call to set the duration. Calling this again will
// reset the start time for a subsequent Stop call.
func (m *Metric) Start() *Metric {
m.startTime = time.Now()
return m
}
// Stop ends the timer started with Start and records the duration in the
// Duration field. Calling this multiple times will modify the Duration based
// on the last time Start was called.
//
// If Start was never called, this function has zero effect.
func (m *Metric) Stop() *Metric {
// Only record if we have a start time set with Start()
if !m.startTime.IsZero() {
m.Duration = time.Since(m.startTime)
}
return m
}
// String returns the valid Server-Timing metric entry value.
func (m *Metric) String() string {
// Begin building parts, expected capacity is length of extra
// fields plus id, desc, dur.
parts := make([]string, 1, len(m.Extra)+3)
parts[0] = m.Name
// Description
if _, ok := m.Extra[paramNameDesc]; !ok && m.Desc != "" {
parts = append(parts, headerEncodeParam(paramNameDesc, m.Desc))
}
// Duration
if _, ok := m.Extra[paramNameDur]; !ok && m.Duration > 0 {
parts = append(parts, headerEncodeParam(
paramNameDur,
strconv.FormatFloat(float64(m.Duration)/float64(time.Millisecond), 'f', -1, 64),
))
}
// All remaining extra params
for k, v := range m.Extra {
parts = append(parts, headerEncodeParam(k, v))
}
return strings.Join(parts, ";")
}
// GoString is needed for fmt.GoStringer so %v works on pointer value.
func (m *Metric) GoString() string {
if m == nil {
return "nil"
}
return fmt.Sprintf("*%#v", *m)
}

View File

@@ -0,0 +1,84 @@
package servertiming
import (
"net/http"
"github.com/felixge/httpsnoop"
)
// MiddlewareOpts are options for the Middleware.
type MiddlewareOpts struct {
// Nothing currently, reserved for the future.
}
// Middleware wraps an http.Handler and provides a *Header in the request
// context that can be used to set Server-Timing headers. The *Header can be
// extracted from the context using FromContext.
//
// The options supplied to this can be nil to use defaults.
//
// The Server-Timing header will be written when the status is written
// only if there are non-empty number of metrics.
//
// To control when Server-Timing is sent, the easiest approach is to wrap
// this middleware and only call it if the request should send server timings.
// For examples, see the README.
func Middleware(next http.Handler, _ *MiddlewareOpts) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
// Create the Server-Timing headers struct
h Header
// Remember if the timing header were added to the response headers
headerWritten bool
)
// This places the *Header value into the request context. This
// can be extracted again with FromContext.
r = r.WithContext(NewContext(r.Context(), &h))
// Get the header map. This is a reference and shouldn't change.
headers := w.Header()
// Hook the response writer we pass upstream so we can modify headers
// before they write them to the wire, but after we know what status
// they are writing.
hooks := httpsnoop.Hooks{
WriteHeader: func(original httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
// Return a function with same signature as
// http.ResponseWriter.WriteHeader to be called in it's place
return func(code int) {
// Write the headers
writeHeader(headers, &h)
// Remember that headers were written
headerWritten = true
// Call the original WriteHeader function
original(code)
}
},
}
w = httpsnoop.Wrap(w, hooks)
next.ServeHTTP(w, r)
// In case that next did not called WriteHeader function, add timing header to the response headers
if !headerWritten {
writeHeader(headers, &h)
}
})
}
func writeHeader(headers http.Header, h *Header) {
// Grab the lock just in case there is any ongoing concurrency that
// still has a reference and may be modifying the value.
h.Lock()
defer h.Unlock()
// If there are no metrics set, do nothing
if len(h.Metrics) == 0 {
return
}
headers.Set(HeaderKey, h.String())
}