diff --git a/NOTICES.txt b/NOTICES.txt index a0cf999a8..e02cc4836 100644 --- a/NOTICES.txt +++ b/NOTICES.txt @@ -146,3 +146,9 @@ golang.org/go https://github.com/golang/go License: BSD 3-clause (https://github.com/golang/go/LICENSE) Copyright (c) 2009 The Go Authors. All rights reserved. + + +github.com/rogpeppe/fastuuid +https://github.com/rogpeppe/fastuuid.git +License: BSD 3-clause (https://github.com/google/uuid/LICENSE) +Copyright © 2014, Roger Peppe All rights reserved. diff --git a/config/config.go b/config/config.go index 5050239f6..e75c026ea 100644 --- a/config/config.go +++ b/config/config.go @@ -57,6 +57,7 @@ type Proxy struct { TLSHeader string TLSHeaderValue string GZIPContentTypes *regexp.Regexp + RequestID string } type Runtime struct { diff --git a/config/load.go b/config/load.go index d1e696ce8..6cb4a4ed2 100644 --- a/config/load.go +++ b/config/load.go @@ -126,6 +126,7 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.StringVar(&cfg.Proxy.ClientIPHeader, "proxy.header.clientip", defaultConfig.Proxy.ClientIPHeader, "header for the request ip") f.StringVar(&cfg.Proxy.TLSHeader, "proxy.header.tls", defaultConfig.Proxy.TLSHeader, "header for TLS connections") f.StringVar(&cfg.Proxy.TLSHeaderValue, "proxy.header.tls.value", defaultConfig.Proxy.TLSHeaderValue, "value for TLS connection header") + f.StringVar(&cfg.Proxy.RequestID, "proxy.header.requestid", defaultConfig.Proxy.RequestID, "header for reqest id") f.StringVar(&gzipContentTypesValue, "proxy.gzip.contenttype", defaultValues.GZIPContentTypesValue, "regexp of content types to compress") f.StringSliceVar(&listenerValue, "proxy.addr", defaultValues.ListenerValue, "listener config") f.KVSliceVar(&certSourcesValue, "proxy.cs", defaultValues.CertSourcesValue, "certificate sources") diff --git a/config/load_test.go b/config/load_test.go index af70f30d5..53d246242 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -302,6 +302,13 @@ func TestLoad(t *testing.T) { return cfg }, }, + { + args: []string{"-proxy.header.requestid", "value"}, + cfg: func(cfg *Config) *Config { + cfg.Proxy.RequestID = "value" + return cfg + }, + }, { args: []string{"-proxy.gzip.contenttype", `^text/.*$`}, cfg: func(cfg *Config) *Config { diff --git a/fabio.properties b/fabio.properties index e35f74282..4bbf269d4 100644 --- a/fabio.properties +++ b/fabio.properties @@ -353,6 +353,15 @@ # proxy.header.tls.value = +# proxy.header.requestid configures the header for the adding a unique request id. +# When set non-empty value the proxy will set this header on every request to the +# unique UUID value. +# +# The default is +# +# proxy.header.requestid = + + # proxy.gzip.contenttype configures which responses should be compressed. # # By default, responses sent to the client are not compressed even if the diff --git a/proxy/http_integration_test.go b/proxy/http_integration_test.go index 22741c5a4..8a629cdbf 100644 --- a/proxy/http_integration_test.go +++ b/proxy/http_integration_test.go @@ -50,6 +50,31 @@ func TestProxyProducesCorrectXffHeader(t *testing.T) { } } +func TestProxyRequestIDHeader(t *testing.T) { + got := "not called" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + got = r.Header.Get("X-Request-ID") + })) + defer server.Close() + + proxy := httptest.NewServer(&HTTPProxy{ + Config: config.Proxy{RequestID: "X-Request-Id"}, + Transport: http.DefaultTransport, + UUID: func() string { return "f47ac10b-58cc-0372-8567-0e02b2c3d479" }, + Lookup: func(r *http.Request) *route.Target { + return &route.Target{URL: mustParse(server.URL)} + }, + }) + defer proxy.Close() + + req, _ := http.NewRequest("GET", proxy.URL, nil) + mustDo(req) + + if want := "f47ac10b-58cc-0372-8567-0e02b2c3d479"; got != want { + t.Errorf("got %v, but want %v", got, want) + } +} + func TestProxyNoRouteStaus(t *testing.T) { proxy := httptest.NewServer(&HTTPProxy{ Config: config.Proxy{NoRouteStatus: 999}, diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 7e84254c7..239c491e6 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -15,6 +15,7 @@ import ( "github.com/fabiolb/fabio/metrics" "github.com/fabiolb/fabio/proxy/gzip" "github.com/fabiolb/fabio/route" + "github.com/fabiolb/fabio/uuid" ) // HTTPProxy is a dynamic reverse proxy for HTTP and HTTPS protocols. @@ -48,6 +49,10 @@ type HTTPProxy struct { // Logger is the access logger for the requests. Logger logger.Logger + + // UUID returns a unique id in uuid format. + // If UUID is nil, uuid.NewUUID() is used. + UUID func() string } func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -66,6 +71,16 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + if p.Config.RequestID != "" { + var id string + if p.UUID == nil { + id = uuid.NewUUID() + } else { + id = p.UUID() + } + r.Header.Set(p.Config.RequestID, id) + } + // build the request url since r.URL will get modified // by the reverse proxy and contains only the RequestURI anyway requestURL := &url.URL{ diff --git a/uuid/format.go b/uuid/format.go new file mode 100644 index 000000000..542424139 --- /dev/null +++ b/uuid/format.go @@ -0,0 +1,62 @@ +package uuid + +// Fast UUID formatting adapted from +// https://github.com/m4rw3r/uuid/blob/master/uuid.go + +// hexchar2byte contains the integer byte-value represented by a hexadecimal character, +// 255 if it is an invalid character. +var hexchar2byte = []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// halfbyte2hexchar contains an array of character values corresponding to +// hexadecimal values for the position in the array, 0 to 15 (0x0-0xf, half-byte). +var halfbyte2hexchar = []byte{ + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, +} + +// ToString formats raw UUID bytes as a standard UUID string +func ToString(u [24]byte) string { + /* It is a lot (~10x) faster to allocate a byte slice of specific size and + then use a lookup table to write the characters to the byte-array and + finally cast to string instead of using fmt.Sprintf() */ + /* Slightly faster to not use make([]byte, 36), guessing either call + overhead or slice-header overhead is the cause */ + b := [36]byte{} + + for i, n := range []int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34, + } { + b[n] = halfbyte2hexchar[(u[i]>>4)&0x0f] + b[n+1] = halfbyte2hexchar[u[i]&0x0f] + } + + b[8] = '-' + b[13] = '-' + b[18] = '-' + b[23] = '-' + + /* Oddly does not seem to cause a memory allocation, + internal data-array is most likely just moved over + to the string-header: */ + return string(b[:]) +} diff --git a/uuid/uuid.go b/uuid/uuid.go new file mode 100644 index 000000000..68dcb1308 --- /dev/null +++ b/uuid/uuid.go @@ -0,0 +1,12 @@ +package uuid + +import ( + "github.com/rogpeppe/fastuuid" +) + +var generator = fastuuid.MustNewGenerator() + +// NewUUID return UUID in string fromat +func NewUUID() string { + return ToString(generator.Next()) +} diff --git a/vendor/github.com/rogpeppe/fastuuid/LICENSE b/vendor/github.com/rogpeppe/fastuuid/LICENSE new file mode 100644 index 000000000..9525fc825 --- /dev/null +++ b/vendor/github.com/rogpeppe/fastuuid/LICENSE @@ -0,0 +1,26 @@ +Copyright © 2014, Roger Peppe +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of this project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/rogpeppe/fastuuid/README.md b/vendor/github.com/rogpeppe/fastuuid/README.md new file mode 100644 index 000000000..6e8f134ae --- /dev/null +++ b/vendor/github.com/rogpeppe/fastuuid/README.md @@ -0,0 +1,50 @@ +# fastuuid +-- + import "github.com/rogpeppe/fastuuid" + +Package fastuuid provides fast UUID generation of 192 bit universally unique +identifiers. It does not provide formatting or parsing of the identifiers (it is +assumed that a simple hexadecimal or base64 representation is sufficient, for +which adequate functionality exists elsewhere). + +Note that the generated UUIDs are not unguessable - each UUID generated from a +Generator is adjacent to the previously generated UUID. + +It ignores RFC 4122. + +## Usage + +#### type Generator + +```go +type Generator struct { +} +``` + +Generator represents a UUID generator that generates UUIDs in sequence from a +random starting point. + +#### func MustNewGenerator + +```go +func MustNewGenerator() *Generator +``` +MustNewGenerator is like NewGenerator but panics on failure. + +#### func NewGenerator + +```go +func NewGenerator() (*Generator, error) +``` +NewGenerator returns a new Generator. It can fail if the crypto/rand read fails. + +#### func (*Generator) Next + +```go +func (g *Generator) Next() [24]byte +``` +Next returns the next UUID from the generator. Only the first 8 bytes can differ +from the previous UUID, so taking a slice of the first 16 bytes is sufficient to +provide a somewhat less secure 128 bit UUID. + +It is OK to call this method concurrently. diff --git a/vendor/github.com/rogpeppe/fastuuid/uuid.go b/vendor/github.com/rogpeppe/fastuuid/uuid.go new file mode 100644 index 000000000..c7cf6b2e4 --- /dev/null +++ b/vendor/github.com/rogpeppe/fastuuid/uuid.go @@ -0,0 +1,66 @@ +// Package fastuuid provides fast UUID generation of 192 bit +// universally unique identifiers. It does not provide +// formatting or parsing of the identifiers (it is assumed +// that a simple hexadecimal or base64 representation +// is sufficient, for which adequate functionality exists elsewhere). +// +// Note that the generated UUIDs are not unguessable - each +// UUID generated from a Generator is adjacent to the +// previously generated UUID. +// +// It ignores RFC 4122. +package fastuuid + +import ( + "crypto/rand" + "encoding/binary" + "errors" + "sync/atomic" +) + +// Generator represents a UUID generator that +// generates UUIDs in sequence from a random starting +// point. +type Generator struct { + seed [24]byte + counter uint64 +} + +// NewGenerator returns a new Generator. +// It can fail if the crypto/rand read fails. +func NewGenerator() (*Generator, error) { + var g Generator + _, err := rand.Read(g.seed[:]) + if err != nil { + return nil, errors.New("cannot generate random seed: " + err.Error()) + } + return &g, nil +} + +// MustNewGenerator is like NewGenerator +// but panics on failure. +func MustNewGenerator() *Generator { + g, err := NewGenerator() + if err != nil { + panic(err) + } + return g +} + +// Next returns the next UUID from the generator. +// Only the first 8 bytes can differ from the previous +// UUID, so taking a slice of the first 16 bytes +// is sufficient to provide a somewhat less secure 128 bit UUID. +// +// It is OK to call this method concurrently. +func (g *Generator) Next() [24]byte { + x := atomic.AddUint64(&g.counter, 1) + var counterBytes [8]byte + binary.LittleEndian.PutUint64(counterBytes[:], x) + + uuid := g.seed + for i, b := range counterBytes { + uuid[i] ^= b + } + return uuid +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 431b188cf..84771ee74 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -188,6 +188,12 @@ "revision": "3e5e593311103d49927c8d2b0fd93ccdfe4a525c", "revisionTime": "2015-07-19T09:56:14-07:00" }, + { + "checksumSHA1": "ehRkDJisGCCSYdNgyvs1gSywSPE=", + "path": "github.com/rogpeppe/fastuuid", + "revision": "6724a57986aff9bff1a1770e9347036def7c89f6", + "revisionTime": "2015-01-06T09:31:45Z" + }, { "checksumSHA1": "EYkx4sXDLSEd1xUtGoXRsfd5cpw=", "path": "github.com/ryanuber/go-glob",