// The per-request timeout of 200ms should fire well before the global 30s timeout.
b.AssertFileContent("public/index.html", "Err:")
}
+
+// Issue 14611.
+func TestGetRemotePerRequestTimeoutBodyRead(t *testing.T) {
+ t.Parallel()
+
+ // Server sends headers immediately but streams body with a delay.
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "text/plain")
+ w.WriteHeader(200)
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ time.Sleep(300 * time.Millisecond)
+ w.Write([]byte("Hello from remote."))
+ }))
+ t.Cleanup(func() { srv.Close() })
+
+ files := `
+-- hugo.toml --
+timeout = "30s"
+[security]
+[security.http]
+urls = ['.*']
+mediaTypes = ['text/plain']
+-- layouts/home.html --
+{{ $url := "URL" }}
+{{ $opts := dict "timeout" "5s" }}
+{{ with try (resources.GetRemote $url $opts) }}
+ {{ with .Err }}
+ Err: {{ . }}
+ {{ else with .Value }}
+ Content: {{ .Content }}
+ {{ end }}
+{{ end }}
+`
+ files = strings.ReplaceAll(files, "URL", srv.URL)
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ },
+ )
+ b.Build()
+
+ b.AssertFileContent("public/index.html", "Content: Hello from remote.")
+}
504: true,
}
-func (c *Client) configurePollingIfEnabled(uri, optionsKey string, getRes func() (*http.Response, error)) {
+func (c *Client) configurePollingIfEnabled(uri, optionsKey string, getRes func() (*http.Response, context.CancelFunc, error)) {
if c.remoteResourceChecker == nil {
return
}
c.rs.Logger.Debugf("Polled remote resource for changes in %13s. Interval: %4s (low: %4s high: %4s) resource: %q ", duration, interval, pollingConfig.Config.Low, pollingConfig.Config.High, uri)
}()
// TODO(bep) figure out a ways to remove unused tasks.
- res, err := getRes()
+ res, cancel, err := getRes()
+ if cancel != nil {
+ defer cancel()
+ }
if err != nil {
return pollingConfig.Config.High, err
}
return nil, err
}
- getRes := func() (*http.Response, error) {
+ getRes := func() (*http.Response, context.CancelFunc, error) {
ctx := context.Background()
+ var cancel context.CancelFunc
if perRequestTimeout > 0 {
- var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, perRequestTimeout)
- defer cancel()
}
ctx = c.resourceIDDispatcher.Set(ctx, filecacheKey)
req, err := options.NewRequest(uri)
if err != nil {
- return nil, fmt.Errorf("failed to create request for resource %s: %w", uri, err)
+ if cancel != nil {
+ cancel()
+ }
+ return nil, nil, fmt.Errorf("failed to create request for resource %s: %w", uri, err)
}
req = req.WithContext(ctx)
- return c.httpClient.Do(req)
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ if cancel != nil {
+ cancel()
+ }
+ return nil, nil, err
+ }
+ return resp, cancel, nil
}
- res, err := getRes()
+ res, cancel, err := getRes()
+ if cancel != nil {
+ defer cancel()
+ }
if err != nil {
return nil, err
}