Skip to content

Commit

Permalink
Adds repeatContent filter (#1616)
Browse files Browse the repository at this point in the history
`repeatContent` filter generates response of specified size from repeated text

Signed-off-by: Alexander Yastrebov <alexander.yastrebov@zalando.de>
  • Loading branch information
AlexanderYastrebov authored Dec 8, 2020
1 parent 6c3b994 commit 8a8334f
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
15 changes: 15 additions & 0 deletions docs/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,21 @@ Example:
* -> randomContent(42) -> <shunt>;
```

## repeatContent

Generate response of specified size from repeated text.

Parameters:

* text to repeat (string)
* size of response in bytes (int)

Example:

```
* -> repeatContent("I will not waste chalk. ", 1000) -> <shunt>;
```

## latency

Enable adding artificial latency
Expand Down
1 change: 1 addition & 0 deletions filters/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func MakeRegistry() filters.Registry {
NewSetDynamicBackendUrl(),
NewOriginMarkerSpec(),
diag.NewRandom(),
diag.NewRepeat(),
diag.NewLatency(),
diag.NewBandwidth(),
diag.NewChunks(),
Expand Down
74 changes: 74 additions & 0 deletions filters/diag/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"sync"
"time"

Expand All @@ -22,6 +23,7 @@ const defaultChunkSize = 512

const (
RandomName = "randomContent"
RepeatName = "repeatContent"
LatencyName = "latency"
ChunksName = "chunks"
BandwidthName = "bandwidth"
Expand All @@ -47,6 +49,16 @@ type random struct {
len int64
}

type repeat struct {
bytes []byte
len int64
}

type repeatReader struct {
bytes []byte
offset int
}

type throttle struct {
typ throttleType
chunkSize int
Expand All @@ -68,6 +80,15 @@ func kbps2bpms(kbps float64) float64 {
//
func NewRandom() filters.Spec { return &random{} }

// NewRepeat creates a filter specification whose filter instances can be used
// to respond to requests with a repeated text. It expects the text and
// the byte length of the response body to be generated as arguments.
// Eskip example:
//
// * -> repeatContent("x", 100) -> <shunt>;
//
func NewRepeat() filters.Spec { return &repeat{} }

// NewLatency creates a filter specification whose filter instances can be used
// to add additional latency to responses. It expects the latency in milliseconds
// as an argument. It always adds this value in addition to the natural latency,
Expand Down Expand Up @@ -146,6 +167,59 @@ func (r *random) Request(ctx filters.FilterContext) {

func (r *random) Response(ctx filters.FilterContext) {}

func (r *repeat) Name() string { return RepeatName }

func (r *repeat) CreateFilter(args []interface{}) (filters.Filter, error) {
if len(args) != 2 {
return nil, filters.ErrInvalidFilterParameters
}

text, ok := args[0].(string)
if !ok || text == "" {
return nil, filters.ErrInvalidFilterParameters
}

var len int64
switch v := args[1].(type) {
case float64:
len = int64(v)
case int:
len = int64(v)
case int64:
len = v
default:
return nil, filters.ErrInvalidFilterParameters
}
if len < 0 {
return nil, filters.ErrInvalidFilterParameters
}

return &repeat{[]byte(text), len}, nil
}

func (r *repeat) Request(ctx filters.FilterContext) {
ctx.Serve(&http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Length": []string{strconv.FormatInt(r.len, 10)}},
Body: ioutil.NopCloser(io.LimitReader(&repeatReader{r.bytes, 0}, r.len)),
})
}

func (r *repeatReader) Read(p []byte) (int, error) {
n := copy(p, r.bytes[r.offset:])
if n < len(p) {
n += copy(p[n:], r.bytes[:r.offset])
for n < len(p) {
copy(p[n:], p[:n])
n *= 2
}
}
r.offset = (r.offset + len(p)) % len(r.bytes)
return len(p), nil
}

func (r *repeat) Response(ctx filters.FilterContext) {}

func (t *throttle) Name() string {
switch t.typ {
case latency:
Expand Down
132 changes: 132 additions & 0 deletions filters/diag/diag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -212,6 +213,137 @@ func TestRandom(t *testing.T) {
}
}

func TestRepeat(t *testing.T) {
for _, ti := range []struct {
args []interface{}
err bool
expected string
}{{
args: []interface{}{},
err: true,
}, {
args: []interface{}{1, "wrong types"},
err: true,
}, {
args: []interface{}{"too few arguments"},
err: true,
}, {
args: []interface{}{"too many arguments", 10.0, "extra"},
err: true,
}, {
args: []interface{}{"length is not a number", "10"},
err: true,
}, {
args: []interface{}{"", 10},
err: true,
}, {
args: []interface{}{"negative length", -1},
err: true,
}, {
args: []interface{}{"zero length", 0},
expected: "",
}, {
args: []interface{}{"1", 1},
expected: "1",
}, {
args: []interface{}{"float", 2.0},
expected: "fl",
}, {
args: []interface{}{"0123456789", 3},
expected: "012",
}, {
args: []interface{}{"0123456789", 30},
expected: "012345678901234567890123456789",
}} {
repeat := NewRepeat()
_, err := repeat.CreateFilter(ti.args)
if ti.err {
if err == nil {
t.Errorf("expected error for %v", ti.args)
}
continue
} else {
if err != nil {
t.Errorf("unexpected error %v for %v", err, ti.args)
continue
}
}

p := proxytest.New(filters.Registry{RepeatName: repeat}, &eskip.Route{
Filters: []*eskip.Filter{{Name: RepeatName, Args: ti.args}},
Shunt: true})
defer p.Close()

rsp, err := http.Get(p.URL)
if err != nil {
t.Fatal(err)
}
defer rsp.Body.Close()

if rsp.StatusCode != http.StatusOK {
t.Fatalf("request failed: %d", rsp.StatusCode)
}

b, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}

got := string(b)
if got != ti.expected {
t.Errorf("result mismatch for %v", ti.args)
}

if rsp.Header.Get("Content-Length") != strconv.Itoa(len(ti.expected)) {
t.Error("content length mismatch")
}
}
}

func TestRepeatReader(t *testing.T) {
r := &repeatReader{[]byte("0123456789"), 0}

checkRead(t, r, 5, "01234")
checkRead(t, r, 5, "56789")
checkRead(t, r, 3, "012")
checkRead(t, r, 3, "345")
checkRead(t, r, 3, "678")
checkRead(t, r, 3, "901")
checkRead(t, r, 10, "2345678901")
checkRead(t, r, 8, "23456789")
checkRead(t, r, 15, "012345678901234")
checkRead(t, r, 25, "5678901234567890123456789")
checkRead(t, r, 1, "0")
checkRead(t, r, 2, "12")
checkRead(t, r, 3, "345")
checkRead(t, r, 4, "6789")
checkRead(t, r, 5, "01234")
checkRead(t, r, 6, "567890")
checkRead(t, r, 7, "1234567")
checkRead(t, r, 8, "89012345")
checkRead(t, r, 9, "678901234")
checkRead(t, r, 10, "5678901234")
checkRead(t, r, 11, "56789012345")
checkRead(t, r, 12, "678901234567")
}

func checkRead(t *testing.T, r io.Reader, n int, expected string) {
b := make([]byte, n)
m, err := r.Read(b)
if err != nil {
t.Error(err)
return
}
if m != n {
t.Errorf("expected to read %d bytes, got %d", n, m)
return
}
s := string(b)
if s != expected {
t.Errorf("expected %s, got %s", expected, s)
}
}

func TestThrottleArgs(t *testing.T) {
for _, ti := range []struct {
msg string
Expand Down

0 comments on commit 8a8334f

Please sign in to comment.