-
Notifications
You must be signed in to change notification settings - Fork 343
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added request tracking (flowId) filter
Closes #18
- Loading branch information
lmineiro
committed
Oct 15, 2015
1 parent
1b8d714
commit b6af8fa
Showing
6 changed files
with
247 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package flowid | ||
|
||
import ( | ||
"github.com/zalando/skipper/skipper" | ||
) | ||
|
||
const ( | ||
filterName = "FlowId" | ||
flowIdHeaderName = "X-Flow-Id" | ||
) | ||
|
||
type flowId struct { | ||
id string | ||
reuseExisting bool | ||
} | ||
|
||
func New(id string, allowOverride bool) skipper.Filter { | ||
return &flowId{id, allowOverride} | ||
} | ||
|
||
func (this *flowId) Id() string { return this.id } | ||
|
||
func (this *flowId) Name() string { return filterName } | ||
|
||
func (this *flowId) Request(fc skipper.FilterContext) { | ||
r := fc.Request() | ||
var flowId string | ||
|
||
if this.reuseExisting { | ||
flowId = r.Header.Get(flowIdHeaderName) | ||
} | ||
|
||
var err error | ||
if !isValid(flowId) { | ||
flowId, err = newFlowId(defaultLen) | ||
} | ||
|
||
if err == nil { | ||
fc.Request().Header.Set(flowIdHeaderName, flowId) | ||
} | ||
} | ||
|
||
func (this *flowId) Response(skipper.FilterContext) {} | ||
|
||
func (this *flowId) MakeFilter(id string, fc skipper.FilterConfig) (skipper.Filter, error) { | ||
reuseExisting, _ := fc[0].(bool) | ||
return New(id, reuseExisting), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package flowid | ||
|
||
import ( | ||
"github.com/zalando/skipper/mock" | ||
"net/http" | ||
"testing" | ||
) | ||
|
||
const testFlowId = "FLOW-ID-FOR-TESTING" | ||
|
||
func TestNewFlowIdGeneration(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "http://example.org", nil) | ||
f := New(filterName, true) | ||
fc := &mock.FilterContext{FRequest: r} | ||
f.Request(fc) | ||
|
||
flowId := fc.Request().Header.Get(flowIdHeaderName) | ||
if !isValid(flowId) { | ||
t.Errorf("'%s' is not a valid flow id", flowId) | ||
} | ||
} | ||
|
||
func TestFlowIdReuseExisting(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "http://example.org", nil) | ||
f := New(filterName, true) | ||
r.Header.Set(flowIdHeaderName, testFlowId) | ||
fc := &mock.FilterContext{FRequest: r} | ||
f.Request(fc) | ||
|
||
flowId := fc.Request().Header.Get(flowIdHeaderName) | ||
if flowId != testFlowId { | ||
t.Errorf("Got wrong flow id. Expected '%s' got '%s'", testFlowId, flowId) | ||
} | ||
} | ||
|
||
func TestFlowIdIgnoreReuseExisting(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "http://example.org", nil) | ||
f := New(filterName, false) | ||
r.Header.Set(flowIdHeaderName, testFlowId) | ||
fc := &mock.FilterContext{FRequest: r} | ||
f.Request(fc) | ||
|
||
flowId := fc.Request().Header.Get(flowIdHeaderName) | ||
if flowId == testFlowId { | ||
t.Errorf("Got wrong flow id. Expected a newly generated flowid but got the test flow id '%s'", flowId) | ||
} | ||
} | ||
|
||
func TestFlowIdRejectInvalidFlowId(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "http://example.org", nil) | ||
f := New(filterName, true) | ||
r.Header.Set(flowIdHeaderName, "[<>] (o) [<>]") | ||
fc := &mock.FilterContext{FRequest: r} | ||
f.Request(fc) | ||
|
||
flowId := fc.Request().Header.Get(flowIdHeaderName) | ||
if flowId == "[<>] (o) [<>]" { | ||
t.Errorf("Got wrong flow id. Expected a newly generated flowid but got the test flow id '%s'", flowId) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package flowid | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/hex" | ||
"errors" | ||
"regexp" | ||
"fmt" | ||
) | ||
|
||
const ( | ||
defaultLen = 16 | ||
maxLength = 255 | ||
minLength = 8 | ||
) | ||
|
||
var ( | ||
ErrInvalidLen = errors.New(fmt.Sprintf("Invalid length. len must be >= %d and < %d", minLength, maxLength)) | ||
flowIdRegex, _ = regexp.Compile(`^[\w+/=\-]+$`) | ||
) | ||
|
||
|
||
func newFlowId(len uint8) (string, error) { | ||
if len < minLength || len%2 != 0 { | ||
return "", ErrInvalidLen | ||
} | ||
|
||
u := make([]byte, hex.DecodedLen(int(len))) | ||
buf := make([]byte, len) | ||
|
||
rand.Read(u) | ||
hex.Encode(buf, u) | ||
return string(buf), nil | ||
} | ||
|
||
func isValid(flowId string) bool { | ||
return len(flowId) >= minLength && len(flowId) <= maxLength && flowIdRegex.MatchString(flowId) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package flowid | ||
|
||
import "testing" | ||
|
||
func TestFlowIdInvalidLength(t *testing.T) { | ||
_, err := newFlowId(0) | ||
if err == nil { | ||
t.Errorf("Request for an invalid flow id length (0) succeeded and it shouldn't") | ||
} | ||
|
||
_, err = newFlowId(15) | ||
if err == nil { | ||
t.Errorf("Request for an invalid flow id length (odd number) succeeded and it shouldn't") | ||
} | ||
} | ||
|
||
func TestFlowIdLength(t *testing.T) { | ||
for expected := minLength; expected <= maxLength; expected += 2 { | ||
flowId, err := newFlowId(uint8(expected)) | ||
if err != nil { | ||
t.Errorf("Failed to generate flowId with len %d", expected) | ||
} | ||
|
||
l := len(flowId) | ||
if l != expected { | ||
t.Errorf("Got wrong flowId len. Requested %d, got %d (%s)", expected, l, flowId) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkFlowIdLen8(b *testing.B) { | ||
testFlowIdWithLen(b.N, 8) | ||
} | ||
|
||
func BenchmarkFlowIdLen10(b *testing.B) { | ||
testFlowIdWithLen(b.N, 10) | ||
} | ||
|
||
func BenchmarkFlowIdLen12(b *testing.B) { | ||
testFlowIdWithLen(b.N, 12) | ||
} | ||
|
||
func BenchmarkFlowIdLen14(b *testing.B) { | ||
testFlowIdWithLen(b.N, 14) | ||
} | ||
|
||
func BenchmarkFlowIdLen16(b *testing.B) { | ||
testFlowIdWithLen(b.N, 16) | ||
} | ||
|
||
func BenchmarkFlowIdLen32(b *testing.B) { | ||
testFlowIdWithLen(b.N, 32) | ||
} | ||
|
||
func BenchmarkFlowIdLen64(b *testing.B) { | ||
testFlowIdWithLen(b.N, 64) | ||
} | ||
|
||
func testFlowIdWithLen(times int, len uint8) { | ||
for i := 0; i < times; i++ { | ||
newFlowId(len) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Flow ID Filter | ||
|
||
Flow IDs let you correlate router logs for a given request against the upstream application logs for that same request. | ||
If your upstream application makes other requests to other services it can provide the same Flow ID value so that all | ||
of those logs can be correlated. | ||
|
||
## How it works | ||
Skipper generates a unique Flow ID for every HTTP request that it receives. The Flow ID is then passed to your | ||
upstream application as an HTTP header called X-Flow-Id. | ||
|
||
## Some benchmarks | ||
|
||
To decide upon which hashing mechanism to use we tested some versions of UUID v1 - v4 and some other implementations. | ||
The results are as follow: | ||
|
||
Benchmark_uuidv1-4 5000000 281 ns/op | ||
Benchmark_uuidv2-4 5000000 284 ns/op | ||
Benchmark_uuidv3-4 2000000 605 ns/op | ||
Benchmark_uuidv4-4 1000000 1903 ns/op | ||
BenchmarkRndAndSprintf-4 500000 3312 ns/op | ||
BenchmarkSha1-4 1000000 2188 ns/op | ||
BenchmarkMd5-4 1000000 2076 ns/op | ||
BenchmarkFnv-4 500000 2223 ns/op | ||
|
||
The current implementation just gets len / 2 (hex.DecodedLen) bytes from the PRNG and hex encodes them. | ||
Its performance is only dependent on the length of the generated FlowId, according to the following benchmarks: | ||
|
||
BenchmarkFlowIdLen8-4 1000000 1157 ns/op | ||
BenchmarkFlowIdLen10-4 1000000 1162 ns/op | ||
BenchmarkFlowIdLen12-4 1000000 1163 ns/op | ||
BenchmarkFlowIdLen14-4 1000000 1171 ns/op | ||
BenchmarkFlowIdLen16-4 1000000 1180 ns/op | ||
BenchmarkFlowIdLen32-4 1000000 1957 ns/op | ||
BenchmarkFlowIdLen64-4 300000 3520 ns/op | ||
|
||
As you can see, starting at len = 32 (16 random bytes) the performance starts dropping dramatically. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters