97 lines
3.3 KiB
Markdown
97 lines
3.3 KiB
Markdown
|
# 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
|
||
|
}
|
||
|
```
|