mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-13 20:24:00 +02:00
resources: Add responseHeaders option to resources.GetRemote
* These response headers will be included in `.Data.Headers` if found. * The header name matching is case insensitive. * `Data.Headers` is of type `map[string][]string` * In most cases there will be only one value per header key, but e.g. `Set-Cookie` commonly has multiple values. Fixes #12521
This commit is contained in:
@@ -56,7 +56,42 @@ func TestGetRemoteHead(t *testing.T) {
|
|||||||
|
|
||||||
b.AssertFileContent("public/index.html",
|
b.AssertFileContent("public/index.html",
|
||||||
"Head Content: .",
|
"Head Content: .",
|
||||||
"Head Data: map[ContentLength:18210 ContentType:image/png Status:200 OK StatusCode:200 TransferEncoding:[]]",
|
"Head Data: map[ContentLength:18210 ContentType:image/png Headers:map[] Status:200 OK StatusCode:200 TransferEncoding:[]]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRemoteResponseHeaders(t *testing.T) {
|
||||||
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
[security]
|
||||||
|
[security.http]
|
||||||
|
methods = ['(?i)GET|POST|HEAD']
|
||||||
|
urls = ['.*gohugo\.io.*']
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ $url := "https://gohugo.io/img/hugo.png" }}
|
||||||
|
{{ $opts := dict "method" "head" "responseHeaders" (slice "X-Frame-Options" "Server") }}
|
||||||
|
{{ with try (resources.GetRemote $url $opts) }}
|
||||||
|
{{ with .Err }}
|
||||||
|
{{ errorf "Unable to get remote resource: %s" . }}
|
||||||
|
{{ else with .Value }}
|
||||||
|
Response Headers: {{ .Data.Headers }}
|
||||||
|
{{ else }}
|
||||||
|
{{ errorf "Unable to get remote resource: %s" $url }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
b.Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html",
|
||||||
|
"Response Headers: map[Server:[Netlify] X-Frame-Options:[DENY]]",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gohugoio/httpcache"
|
"github.com/gohugoio/httpcache"
|
||||||
"github.com/gohugoio/hugo/common/hashing"
|
"github.com/gohugoio/hugo/common/hashing"
|
||||||
|
"github.com/gohugoio/hugo/common/hstrings"
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
@@ -51,18 +52,28 @@ type HTTPError struct {
|
|||||||
Body string
|
Body string
|
||||||
}
|
}
|
||||||
|
|
||||||
func responseToData(res *http.Response, readBody bool) map[string]any {
|
func responseToData(res *http.Response, readBody bool, includeHeaders []string) map[string]any {
|
||||||
var body []byte
|
var body []byte
|
||||||
if readBody {
|
if readBody {
|
||||||
body, _ = io.ReadAll(res.Body)
|
body, _ = io.ReadAll(res.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responseHeaders := make(map[string][]string)
|
||||||
|
if true || len(includeHeaders) > 0 {
|
||||||
|
for k, v := range res.Header {
|
||||||
|
if hstrings.InSlicEqualFold(includeHeaders, k) {
|
||||||
|
responseHeaders[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m := map[string]any{
|
m := map[string]any{
|
||||||
"StatusCode": res.StatusCode,
|
"StatusCode": res.StatusCode,
|
||||||
"Status": res.Status,
|
"Status": res.Status,
|
||||||
"TransferEncoding": res.TransferEncoding,
|
"TransferEncoding": res.TransferEncoding,
|
||||||
"ContentLength": res.ContentLength,
|
"ContentLength": res.ContentLength,
|
||||||
"ContentType": res.Header.Get("Content-Type"),
|
"ContentType": res.Header.Get("Content-Type"),
|
||||||
|
"Headers": responseHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
if readBody {
|
if readBody {
|
||||||
@@ -72,7 +83,7 @@ func responseToData(res *http.Response, readBody bool) map[string]any {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func toHTTPError(err error, res *http.Response, readBody bool) *HTTPError {
|
func toHTTPError(err error, res *http.Response, readBody bool, responseHeaders []string) *HTTPError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic("err is nil")
|
panic("err is nil")
|
||||||
}
|
}
|
||||||
@@ -85,7 +96,7 @@ func toHTTPError(err error, res *http.Response, readBody bool) *HTTPError {
|
|||||||
|
|
||||||
return &HTTPError{
|
return &HTTPError{
|
||||||
error: err,
|
error: err,
|
||||||
Data: responseToData(res, readBody),
|
Data: responseToData(res, readBody, responseHeaders),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +224,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
|
|||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||||
return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod)
|
return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod, options.ResponseHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -280,7 +291,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
|
|||||||
}
|
}
|
||||||
|
|
||||||
userKey = filename[:len(filename)-len(path.Ext(filename))] + "_" + userKey + mediaType.FirstSuffix.FullSuffix
|
userKey = filename[:len(filename)-len(path.Ext(filename))] + "_" + userKey + mediaType.FirstSuffix.FullSuffix
|
||||||
data := responseToData(res, false)
|
data := responseToData(res, false, options.ResponseHeaders)
|
||||||
|
|
||||||
return c.rs.NewResource(
|
return c.rs.NewResource(
|
||||||
resources.ResourceSourceDescriptor{
|
resources.ResourceSourceDescriptor{
|
||||||
@@ -348,6 +359,7 @@ type fromRemoteOptions struct {
|
|||||||
Method string
|
Method string
|
||||||
Headers map[string]any
|
Headers map[string]any
|
||||||
Body []byte
|
Body []byte
|
||||||
|
ResponseHeaders []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o fromRemoteOptions) BodyReader() io.Reader {
|
func (o fromRemoteOptions) BodyReader() io.Reader {
|
||||||
@@ -432,7 +444,7 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error
|
|||||||
if resp != nil {
|
if resp != nil {
|
||||||
msg = resp.Status
|
msg = resp.Status
|
||||||
}
|
}
|
||||||
err := toHTTPError(fmt.Errorf("retry timeout (configured to %s) fetching remote resource: %s", t.Cfg.Timeout(), msg), resp, req.Method != "HEAD")
|
err := toHTTPError(fmt.Errorf("retry timeout (configured to %s) fetching remote resource: %s", t.Cfg.Timeout(), msg), resp, req.Method != "HEAD", nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
time.Sleep(nextSleep)
|
time.Sleep(nextSleep)
|
||||||
|
Reference in New Issue
Block a user