Skip to content

Commit acc06c5

Browse files
committed
pkg/tool/http: allow configuration of following redirects
The default net/http.Client in Go follows redirects. This is a sensible default, both in Go and in the pkg/tool/http Do wrappers. However there are times when it is useful to be able to assert the actual status code returned by an endpoint _is_ a redirect, which otherwise cannot be determined when redirects are automatically followed. We fix this by introducing a new top-level config field followRedirects on http.Do, which defaults to true, but can explicitly be set to false in which case the status code, body etc of the first response is used to populate the response field. Fixes #3896. Signed-off-by: Paul Jolly <[email protected]> Change-Id: I7788414b60c50831e25b24453e5a85d290f477f1 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1213859 Reviewed-by: Roger Peppe <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 4b5dd6b commit acc06c5

File tree

4 files changed

+139
-6
lines changed

4 files changed

+139
-6
lines changed

pkg/tool/http/http.cue

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright 2018 The CUE Authors
2-
//
2+
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
6-
//
6+
//
77
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
8+
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,6 +25,10 @@ Do: {
2525
method: string
2626
url: string // TODO: make url.URL type
2727

28+
// followRedirects controls whether the http client follows redirects
29+
// or not. Defaults to true, like the default net/http client in Go.
30+
followRedirects: *true | bool
31+
2832
tls: {
2933
// Whether the server certificate must be validated.
3034
verify: *true | bool

pkg/tool/http/http.go

+23
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,29 @@ func (c *httpCmd) Run(ctx *task.Context) (res interface{}, err error) {
116116
// TODO: timeout
117117
}
118118

119+
// Rather clumsily, we need to also default followRedirects here because
120+
// it's still valid for tasks to be specified via the special $id field, in
121+
// which case we cannot be clear that the documented CUE-based defaults have
122+
// been applied.
123+
//
124+
// This is noted as something to fix, more precisely a mistake not to make
125+
// again, in https://cuelang.org/issue/1325
126+
followRedirects := true
127+
followRedirectsValue := ctx.Obj.LookupPath(cue.MakePath(cue.Str("followRedirects")))
128+
if followRedirectsValue.Exists() {
129+
var err error
130+
followRedirects, err = followRedirectsValue.Bool()
131+
if err != nil {
132+
return nil, err
133+
}
134+
}
135+
136+
if !followRedirects {
137+
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
138+
return http.ErrUseLastResponse
139+
}
140+
}
141+
119142
req, err := http.NewRequest(method, u, r)
120143
if err != nil {
121144
return nil, err

pkg/tool/http/http_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,104 @@ func TestParseHeaders(t *testing.T) {
163163
})
164164
}
165165
}
166+
167+
// TestRedirect exercises the followRedirects configuration on an http.Do request
168+
func TestRedirect(t *testing.T) {
169+
mux := http.NewServeMux()
170+
171+
// In this test server, /a redirects to /b. /b serves "hello"
172+
mux.Handle("/a", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
173+
http.Redirect(w, r, "/b", http.StatusFound)
174+
}))
175+
mux.Handle("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
176+
fmt.Fprint(w, "hello")
177+
}))
178+
179+
server := httptest.NewUnstartedServer(mux)
180+
server.Start()
181+
t.Cleanup(server.Close)
182+
183+
testCases := []struct {
184+
name string
185+
path string
186+
statusCode int
187+
followRedirects *bool
188+
body *string
189+
}{
190+
{
191+
name: "/a silent on redirects",
192+
path: "/a",
193+
statusCode: 200,
194+
body: ref("hello"),
195+
},
196+
{
197+
name: "/a with explicit followRedirects: true",
198+
path: "/a",
199+
statusCode: 200,
200+
followRedirects: ref(true),
201+
body: ref("hello"),
202+
},
203+
{
204+
name: "/a with explicit followRedirects: false",
205+
path: "/a",
206+
statusCode: 302,
207+
followRedirects: ref(false),
208+
},
209+
{
210+
name: "/b silent on redirects",
211+
path: "/b",
212+
statusCode: 200,
213+
body: ref("hello"),
214+
},
215+
{
216+
name: "/b with explicit followRedirects: true",
217+
path: "/b",
218+
statusCode: 200,
219+
followRedirects: ref(true),
220+
body: ref("hello"),
221+
},
222+
{
223+
name: "/b with explicit followRedirects: false",
224+
path: "/b",
225+
statusCode: 200,
226+
followRedirects: ref(true),
227+
body: ref("hello"),
228+
},
229+
}
230+
231+
for _, tc := range testCases {
232+
t.Run(tc.name, func(t *testing.T) {
233+
v3 := parse(t, "tool/http.Get", fmt.Sprintf(`
234+
{
235+
url: "%s%s"
236+
}`, server.URL, tc.path))
237+
238+
if tc.followRedirects != nil {
239+
v3 = v3.FillPath(cue.ParsePath("followRedirects"), *tc.followRedirects)
240+
}
241+
242+
resp, err := (*httpCmd).Run(nil, &task.Context{Obj: v3})
243+
if err != nil {
244+
t.Fatal(err)
245+
}
246+
247+
// grab the response
248+
response := resp.(map[string]any)["response"].(map[string]any)
249+
250+
if got := response["statusCode"]; got != tc.statusCode {
251+
t.Fatalf("status not as expected: wanted %d, got %d", got, tc.statusCode)
252+
}
253+
254+
if tc.body != nil {
255+
want := *tc.body
256+
if got := response["body"]; got != want {
257+
t.Fatalf("body not as expected; wanted %q, got %q", got, want)
258+
}
259+
}
260+
})
261+
}
262+
}
263+
264+
func ref[T any](v T) *T {
265+
return &v
266+
}

pkg/tool/http/pkg.go

+8-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)