forked from zalando/skipper
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adds `backendTimeout` filter to configure route backend timeout * Proxy sets up request context with configured timeout and responds with 504 status on timeout (note: if response streaming has already started it will be terminated, client will receive backend status and truncated response body). See zalando#1041 Signed-off-by: Alexander Yastrebov <[email protected]>
- Loading branch information
1 parent
8f32650
commit be61996
Showing
8 changed files
with
243 additions
and
19 deletions.
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
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
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,47 @@ | ||
package builtin | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/zalando/skipper/filters" | ||
) | ||
|
||
type timeout struct { | ||
timeout time.Duration | ||
} | ||
|
||
func NewBackendTimeout() filters.Spec { | ||
return &timeout{} | ||
} | ||
|
||
func (*timeout) Name() string { return BackendTimeoutName } | ||
|
||
func (*timeout) CreateFilter(args []interface{}) (filters.Filter, error) { | ||
if len(args) != 1 { | ||
return nil, filters.ErrInvalidFilterParameters | ||
} | ||
|
||
var tf timeout | ||
switch v := args[0].(type) { | ||
case string: | ||
d, err := time.ParseDuration(v) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tf.timeout = d | ||
case time.Duration: | ||
tf.timeout = v | ||
default: | ||
return nil, filters.ErrInvalidFilterParameters | ||
} | ||
return &tf, nil | ||
} | ||
|
||
func (t *timeout) Request(ctx filters.FilterContext) { | ||
sb := ctx.StateBag() | ||
if _, ok := sb[filters.BackendTimeout]; !ok { // once | ||
sb[filters.BackendTimeout] = t.timeout | ||
} | ||
} | ||
|
||
func (t *timeout) Response(filters.FilterContext) {} |
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,37 @@ | ||
package builtin | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
"time" | ||
|
||
"github.com/zalando/skipper/filters" | ||
"github.com/zalando/skipper/filters/filtertest" | ||
) | ||
|
||
func TestBackendTimeout(t *testing.T) { | ||
bt := NewBackendTimeout() | ||
if bt.Name() != BackendTimeoutName { | ||
t.Error("wrong name") | ||
} | ||
|
||
f, err := bt.CreateFilter([]interface{}{"2s"}) | ||
if err != nil { | ||
t.Error("wrong id") | ||
} | ||
|
||
c := &filtertest.Context{FRequest: &http.Request{}, FStateBag: make(map[string]interface{})} | ||
f.Request(c) | ||
|
||
if c.FStateBag[filters.BackendTimeout] != 2*time.Second { | ||
t.Error("wrong timeout") | ||
} | ||
|
||
// second filter | ||
f, err = bt.CreateFilter([]interface{}{"5s"}) | ||
f.Request(c) | ||
|
||
if c.FStateBag[filters.BackendTimeout] != 2*time.Second { | ||
t.Error("no change expected") | ||
} | ||
} |
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
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,111 @@ | ||
package proxy | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestBackendTimeoutBelow(t *testing.T) { | ||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
time.Sleep(2 * time.Millisecond) | ||
})) | ||
defer backend.Close() | ||
|
||
doc := fmt.Sprintf(`* -> backendTimeout("1ms") -> "%s"`, backend.URL) | ||
tp, err := newTestProxy(doc, FlagsNone) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer tp.close() | ||
|
||
ps := httptest.NewServer(tp.proxy) | ||
defer ps.Close() | ||
|
||
rsp, err := http.Get(ps.URL) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if rsp.StatusCode != http.StatusGatewayTimeout { | ||
t.Errorf("expected 504, got: %v", rsp) | ||
} | ||
} | ||
|
||
func TestBackendTimeoutAbove(t *testing.T) { | ||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
time.Sleep(2 * time.Millisecond) | ||
})) | ||
defer backend.Close() | ||
|
||
doc := fmt.Sprintf(`* -> backendTimeout("10ms") -> "%s"`, backend.URL) | ||
tp, err := newTestProxy(doc, FlagsNone) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer tp.close() | ||
|
||
ps := httptest.NewServer(tp.proxy) | ||
defer ps.Close() | ||
|
||
rsp, err := http.Get(ps.URL) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if rsp.StatusCode != http.StatusOK { | ||
t.Errorf("expected 200, got: %v", rsp) | ||
} | ||
} | ||
|
||
func TestBackendTimeoutInTheMiddleOfResponse(t *testing.T) { | ||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(200) | ||
w.Write([]byte("Wish You")) | ||
|
||
f := w.(http.Flusher) | ||
f.Flush() | ||
|
||
time.Sleep(20 * time.Millisecond) | ||
|
||
w.Write([]byte(" Were Here")) | ||
})) | ||
defer backend.Close() | ||
|
||
doc := fmt.Sprintf(`* -> backendTimeout("10ms") -> "%s"`, backend.URL) | ||
tp, err := newTestProxy(doc, FlagsNone) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer tp.close() | ||
|
||
ps := httptest.NewServer(tp.proxy) | ||
defer ps.Close() | ||
|
||
rsp, err := http.Get(ps.URL) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if rsp.StatusCode != http.StatusOK { | ||
t.Errorf("expected 200, got: %v", rsp) | ||
} | ||
|
||
body, err := ioutil.ReadAll(rsp.Body) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
content := string(body) | ||
if content != "Wish You" { | ||
t.Errorf("expected partial content, got %s", content) | ||
} | ||
|
||
const msg = "error while copying the response stream: context deadline exceeded" | ||
if err = tp.log.WaitFor(msg, 10*time.Millisecond); err != nil { | ||
t.Errorf("expected '%s' in logs", msg) | ||
} | ||
} |
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
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