mirror of
https://github.com/kubevela/kubevela.git
synced 2026-07-28 18:01:24 +00:00
feat: support configurable timeout via request.timeout in http builtin (#7239)
Signed-off-by: Paranitharan Kalaiselvan <paranitharan_kalaiselvan@gmail.com> Co-authored-by: Paranitharan Kalaiselvan <paranitharan_kalaiselvan@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6fd0e71e50
commit
56e5cd5ff5
@@ -59,6 +59,16 @@ func (c *HTTPCmd) Run(meta *registry.Meta) (res interface{}, err error) {
|
||||
}
|
||||
)
|
||||
if obj := meta.Obj.LookupPath(value.FieldPath("request")); obj.Exists() {
|
||||
// Allow CUE authors to override the default 3s timeout via request.timeout
|
||||
// (e.g., timeout: "30s"). Expects a string duration parseable by time.ParseDuration.
|
||||
// Only positive durations are accepted; zero or negative values are ignored.
|
||||
if v := obj.LookupPath(value.FieldPath("timeout")); v.Exists() {
|
||||
if timeoutStr, parseErr := v.String(); parseErr == nil {
|
||||
if d, parseErr := time.ParseDuration(timeoutStr); parseErr == nil && d > 0 {
|
||||
client.Timeout = d
|
||||
}
|
||||
}
|
||||
}
|
||||
if v := obj.LookupPath(value.FieldPath("body")); v.Exists() {
|
||||
r, err = v.Reader()
|
||||
if err != nil {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
@@ -99,6 +100,110 @@ func TestHTTPCmdRun(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestHTTPCmdRunWithCustomTimeout(t *testing.T) {
|
||||
// Start a slow server that takes 200ms to respond
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"ok"}`))
|
||||
}))
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen on ephemeral port: %v", err)
|
||||
}
|
||||
ts.Listener.Close()
|
||||
ts.Listener = l
|
||||
ts.Start()
|
||||
defer ts.Close()
|
||||
|
||||
// Derive the server URL from the listener address
|
||||
serverURL := fmt.Sprintf("http://%s/api/v1/slow", l.Addr().String())
|
||||
|
||||
// Without custom timeout (default 3s) — should succeed since server responds in 200ms
|
||||
reqDefault := cuecontext.New().CompileString(fmt.Sprintf(`{
|
||||
method: "GET"
|
||||
url: "%s"
|
||||
}`, serverURL))
|
||||
runner, _ := newHTTPCmd(cue.Value{})
|
||||
got, err := runner.Run(®istry.Meta{Obj: reqDefault.Value()})
|
||||
assert.NoError(t, err)
|
||||
body := (got.(map[string]interface{}))["body"].(string)
|
||||
assert.Equal(t, `{"status":"ok"}`, body)
|
||||
|
||||
// With explicit timeout of 1s — should also succeed
|
||||
reqCustom := cuecontext.New().CompileString(fmt.Sprintf(`{
|
||||
method: "GET"
|
||||
url: "%s"
|
||||
request: {
|
||||
timeout: "1s"
|
||||
}
|
||||
}`, serverURL))
|
||||
got, err = runner.Run(®istry.Meta{Obj: reqCustom.Value()})
|
||||
assert.NoError(t, err)
|
||||
body = (got.(map[string]interface{}))["body"].(string)
|
||||
assert.Equal(t, `{"status":"ok"}`, body)
|
||||
|
||||
// With a very short timeout of 50ms — should fail
|
||||
reqShort := cuecontext.New().CompileString(fmt.Sprintf(`{
|
||||
method: "GET"
|
||||
url: "%s"
|
||||
request: {
|
||||
timeout: "50ms"
|
||||
}
|
||||
}`, serverURL))
|
||||
_, err = runner.Run(®istry.Meta{Obj: reqShort.Value()})
|
||||
assert.Error(t, err, "expected timeout error with 50ms deadline on a 200ms server")
|
||||
}
|
||||
|
||||
func TestHTTPCmdRunWithInvalidTimeout(t *testing.T) {
|
||||
s := NewMock()
|
||||
defer s.Close()
|
||||
|
||||
runner, _ := newHTTPCmd(cue.Value{})
|
||||
|
||||
// Invalid timeout value should be silently ignored and use the default 3s
|
||||
reqInvalid := cuecontext.New().CompileString(`{
|
||||
method: "GET"
|
||||
url: "http://127.0.0.1:8090/api/v1/token?val=test-token"
|
||||
request: {
|
||||
timeout: "not-a-duration"
|
||||
header: {
|
||||
"Accept-Language": "en,nl"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
got, err := runner.Run(®istry.Meta{Obj: reqInvalid.Value()})
|
||||
assert.NoError(t, err)
|
||||
body := (got.(map[string]interface{}))["body"].(string)
|
||||
assert.Equal(t, `{"token":"test-token"}`, body)
|
||||
|
||||
// Zero timeout should be rejected and fall back to default 3s
|
||||
reqZero := cuecontext.New().CompileString(`{
|
||||
method: "GET"
|
||||
url: "http://127.0.0.1:8090/api/v1/token?val=test-zero"
|
||||
request: {
|
||||
timeout: "0s"
|
||||
}
|
||||
}`)
|
||||
got, err = runner.Run(®istry.Meta{Obj: reqZero.Value()})
|
||||
assert.NoError(t, err)
|
||||
body = (got.(map[string]interface{}))["body"].(string)
|
||||
assert.Equal(t, `{"token":"test-zero"}`, body)
|
||||
|
||||
// Negative timeout should be rejected and fall back to default 3s
|
||||
reqNegative := cuecontext.New().CompileString(`{
|
||||
method: "GET"
|
||||
url: "http://127.0.0.1:8090/api/v1/token?val=test-negative"
|
||||
request: {
|
||||
timeout: "-5s"
|
||||
}
|
||||
}`)
|
||||
got, err = runner.Run(®istry.Meta{Obj: reqNegative.Value()})
|
||||
assert.NoError(t, err)
|
||||
body = (got.(map[string]interface{}))["body"].(string)
|
||||
assert.Equal(t, `{"token":"test-negative"}`, body)
|
||||
}
|
||||
|
||||
func TestHTTPSRun(t *testing.T) {
|
||||
s := newMockHttpsServer()
|
||||
defer s.Close()
|
||||
|
||||
Reference in New Issue
Block a user