Go (golang) library for creating and consuming HTTP Server-Timing headers

HTTP Server-Timing for Go

Godoc

This is a library including middleware for using HTTP 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

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 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.

  • Either Chrome 65 or higher or Firefox 71 or higher is required to properly display server timings in the devtools.

  • 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.

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
}
Comments
  • more realistic example wanted

    more realistic example wanted

    In practise people will often have more that one handler, e.g.

        http.HandleFunc("/path1", path1Handler)
        http.HandleFunc("/path2", path2Handler)
        http.Handle("/metrics", promhttp.Handler())
    	
        http.ListenAndServe(":80", nil)
    

    if tried to change the last line to:

        h := servertiming.Middleware(http.DefaultServeMux, nil)
        http.ListenAndServe(":80", h)
    

    but get not "Server-Timing" headers.

  • Headers still may not be written

    Headers still may not be written

    Hello, thanks for this. Your fix for #10 wasn't complete. After you have written to the response you can no longer add headers. To fix this you'll need to catch Write with httpsnoop. For example the following works well for me.

    			Write: func(original httpsnoop.WriteFunc) httpsnoop.WriteFunc {
    				return func(b []byte) (int, error) {
    					// Write the headers
    					writeHeader(headers, &h)
    
    					// Remember that headers were written
    					headerWritten = true
    
    					// Call the original Write function
    					return original(b)
    				}
    			},
    
  • duration as float?

    duration as float?

    Hi, looking at the W3C doc I'm hoping that duration can be a float?

    I've got a mod for that at-- https://github.com/reedwade/go-server-timing/tree/float-duration

    which causes the example to emit a header that looks like:

    Server-Timing: service-4;dur=66.236352,service-2;dur=31.155601,service-1;dur=67.140114,service-3;dur=41.247412,service-0;dur=70.299034
    

    This seems a lot more useful for a lot of server side things that happen quite fast (ideally).

  • Add option to disable writing headers

    Add option to disable writing headers

    This option prevents metrics from being written in the corresponding header of the response.

    This is useful to collect the metrics all the time but have a setting to actually write it in the answer. For instance, this is desirable for https://github.com/miniflux/miniflux/pull/806/

  • fix style issues after linters and add code quality badges

    fix style issues after linters and add code quality badges

    I've run gometalinter --enable-all ./... and fixed all issues. Now project should have A+ badge on goreportcard/golangci. And I've added their badges.

    Also it would be nice to integrate code quality checks into CI: with .travis.yml + gometalinter or with golangci

  • fix: add timing headers even if WriteHeaders was not called by next handler

    fix: add timing headers even if WriteHeaders was not called by next handler

    In case that next handler do not call WriteHeaders, the timing headers are not added to the response. In this case we should add the headers in the middleware.

    Fixes #10

  • Headers might not be added

    Headers might not be added

    In go, if a handler doesn't call WriteHeader, the response is still valid, and will be 200. If the next handler wrapped in middleware does not call this function, the timing headers won't be in the response.

    I'm not sure this is an issue, but it can be fixed and save people time trying to figure out why the headers were not added.

  • consider changing the Middleware signature to use options pattern

    consider changing the Middleware signature to use options pattern

    The options pattern:

    • Instead of func Middleware(next http.Handler, _ *MiddlewareOpts) use func Middleware(next http.Handler, _ ...MiddlewareOpts).
    • Instead of type MiddlewareOpts struct have type options struct and type MiddlewareOpts func(*options)

    Then, the call for the middleware won't have nil as second argument.

    I know that this is a breaking change, but you might consider it.

  • Hook Write to send headers

    Hook Write to send headers

    This fixes GH-14 and the details are there.

    If Write is called before WriteHeader, then the headers are never properly sent down. Although the net/http docs state that Write should trigger WriteHeader, this happens after httpsnoop so the httpsnoop callback is never called. We have to manually do this in our Write callback.

  • Fix number regex

    Fix number regex

    In case that a string contains a number, it is considered as a number and not quoted. This fix fixes the regex to match numbers and floating point numbers

  • added StopUnlessStopped, an idempotent Stop

    added StopUnlessStopped, an idempotent Stop

    This is for when you want to call Stop but not if Stop was already called.

    I've got some situations where I want to start a timer then there's many lines of thing that may exit the function early or may make it to where I've got the Stop I really care about.

    Adding this in a defer at the top makes sure we do get a Stop if we had to exit early but don't trample on the one we care about.

  • Update dependencies

    Update dependencies

    I ran:

    go get -u github.com/felixge/httpsnoop go get -u github.com/google/go-cmp go mod tidy -go=1.17

    This means users can depend on go-server-timing without bringing an old version of github.com/gogo/protobuf into the module graph, which can make dependabot think they're vulnerable to CVE-2021-3121.

  • Be less precise about timings

    Be less precise about timings

    Currently durations are rendered as:

    strconv.FormatFloat(float64(m.Duration)/float64(time.Millisecond), 'f', -1, 64)
    

    I think this is over precise: all digits beyond millisecond? That could even be a security issue as somebody could do timing attacks determining how long some operation is taking (e.g., password checking). I think there is really no point in sending more than 1 extra digit after milliseconds. I mean, how precise those measurements are anyway, no? They are not an average of multiple measurements.

    So I suggest:

    strconv.FormatFloat(float64(m.Duration)/float64(time.Millisecond), 'f', 1, 64)
    

    I could be convinced to increase 1, but not to have it unbounded.

  • Support for trailers

    Support for trailers

    It seems Firefox supports them now (at least over HTTP2) and Chrome had support for them, but they have currently a regression: https://bugs.chromium.org/p/chromium/issues/detail?id=1280260

    Maybe it is time to add support for trailers, too. So that we can add total handler time finally.

  • Extract handler to standalone for easier usage in libraries

    Extract handler to standalone for easier usage in libraries

    Because the handler is currently entirely inside the middleware, it is very difficult to fine tune it (such as enabling/disabling it), and takes a reasonably large work around when used with a library like echo or gin.

    Please put the handler into a standalone function so that it can be called manually from whatever library a user chooses/ in a more flexible manner in general.

  • How to merge timings with downstream timinngs

    How to merge timings with downstream timinngs

    The code in https://github.com/mitchellh/go-server-timing/blob/master/middleware.go#L97 currently calls:

    headers.Set(HeaderKey, h.String())
    

    I am adding the timings to a http proxy and it would be good to not clober the downstream timing from the actual backend. This could probably be done by using Add instead of Set. Is there a reason for not merging the headers?

    If I use the servertiming.MiddlewareOpts{DisableHeaders: true} on the proxy I see my backend timings. Otherwise the get overwritten.

Fault injection library in Go using standard http middleware

Fault The fault package provides go http middleware that makes it easy to inject faults into your service. Use the fault package to reject incoming re

Dec 25, 2022
Redcon is a custom Redis server framework for Go that is fast and simple to use.
Redcon is a custom Redis server framework for Go that is fast and simple to use.

Redcon is a custom Redis server framework for Go that is fast and simple to use. The reason for this library it to give an efficient server front-end for the BuntDB and Tile38 projects.

Dec 28, 2022
Go net/http configurable handler to handle CORS requests

Go CORS handler CORS is a net/http handler implementing Cross Origin Resource Sharing W3 specification in Golang. Getting Started After installing Go

Jan 7, 2023
Go net/http handler to transparently manage posted JSON

Go JSON handler FormJSON is a net/http handler implementing content negotiation for posted data in order to transparently expose posted JSON as if it

Sep 27, 2022
Simple middleware to rate-limit HTTP requests.

Tollbooth This is a generic middleware to rate-limit HTTP requests. NOTE 1: This library is considered finished. NOTE 2: Major version changes are bac

Jan 4, 2023
哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP、TCP、Ping 监控报警,命令批量执行和计划任务
哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP、TCP、Ping 监控报警,命令批量执行和计划任务

哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP、TCP、Ping 监控报警,命令批量执行和计划任务

Jan 8, 2023
Restish is a CLI for interacting with REST-ish HTTP APIs with some nice features built-in
Restish is a CLI for interacting with REST-ish HTTP APIs with some nice features built-in

Restish is a CLI for interacting with REST-ish HTTP APIs with some nice features built-in, like always having the latest API resources, fields, and operations available when they go live on the API without needing to install or update anything.

Jan 5, 2023
👄 The most accurate natural language detection library in the Go ecosystem, suitable for long and short text alike
👄 The most accurate natural language detection library in the Go ecosystem, suitable for long and short text alike

Its task is simple: It tells you which language some provided textual data is written in. This is very useful as a preprocessing step for linguistic data in natural language processing applications such as text classification and spell checking. Other use cases, for instance, might include routing e-mails to the right geographically located customer service department, based on the e-mails' languages.

Dec 29, 2022
A Golang Middleware to handle X-Forwarded-For Header

X-Forwarded-For middleware fo Go Package xff is a net/http middleware/handler to parse Forwarded HTTP Extension in Golang. Example usage Install xff:

Nov 3, 2022
A mock code autogenerator for Golang
A mock code autogenerator for Golang

mockery provides the ability to easily generate mocks for golang interfaces using the stretchr/testify/mock package. It removes the boilerplate coding required to use mocks.

Jan 9, 2023
Go middleware for monetizing your API on a per-request basis with Bitcoin and Lightning ⚡️

ln-paywall Go middleware for monetizing your API on a per-request basis with Bitcoin and Lightning ⚡️ Middlewares for: net/http HandlerFunc net/http H

Jan 6, 2023
A simple and lightweight encrypted password manager written in Go.
A simple and lightweight encrypted password manager written in Go.

A simple and lightweight encrypted password manager written in Go.

Jun 16, 2022
Remark42 is a self-hosted, lightweight, and simple comment engine
Remark42 is a self-hosted, lightweight, and simple comment engine

Remark42 is a self-hosted, lightweight, and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles or any other place where readers add comments.

Dec 28, 2022
Stash is a locally hosted web-based app written in Go which organizes and serves your porn.

Stash is a locally hosted web-based app written in Go which organizes and serves your porn.

Jan 2, 2023
An HTTP client for go-server-timing middleware. Enables automatic timing propagation through HTTP calls between servers.

client-timing An HTTP client for go-server-timing middleware. Features: An HTTP Client or RoundTripper, fully compatible with Go's standard library. A

Dec 24, 2022
Fiber middleware for server-timing

Server Timing This is a Fiber middleware for the [W3C Server-Timing API] based on mitchellh/go-server-timing

Feb 6, 2022
Simple GUI to convert Charles headers to golang's default http client (net/http)

Charles-to-Go Simple GUI to convert Charles headers to golang's default http client (net/http) Usage Compile code to a binary, go build -ldflags -H=wi

Dec 14, 2021
Go sqlite3 http vfs: query sqlite databases over http with range headers

sqlite3vfshttp: a Go sqlite VFS for querying databases over http(s) sqlite3vfshttp is a sqlite3 VFS for querying remote databases over http(s). This a

Dec 27, 2022
A http proxy server chaining a upstream which needs authentication headers.

Normalize HTTP Proxy A http proxy server chaining a upstream which needs authentication headers. local -> [np] -> upstream -> destination Usage Norma

Dec 14, 2022
franz-go contains a high performance, pure Go library for interacting with Kafka from 0.8.0 through 2.7.0+. Producing, consuming, transacting, administrating, etc.

franz-go - Apache Kafka client written in Go Franz-go is an all-encompassing Apache Kafka client fully written Go. This library aims to provide every

Dec 29, 2022