tpl/data: Misc header improvements, tests, allow multiple headers of same key
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 5 Jun 2021 10:44:45 +0000 (12:44 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 6 Jun 2021 11:32:12 +0000 (13:32 +0200)
Closes #5617

common/types/convert.go
common/types/convert_test.go
docs/content/en/templates/data-templates.md
tpl/data/data.go
tpl/data/data_test.go
tpl/data/resources.go

index 137029a0e0cdb3f76312e09b1b169ee6e160b6a3..7beb3404e36813a926d1a9180fb2dc381b14c088 100644 (file)
@@ -15,21 +15,52 @@ package types
 
 import (
        "encoding/json"
+       "fmt"
        "html/template"
+       "reflect"
 
        "github.com/spf13/cast"
 )
 
-// ToStringSlicePreserveString converts v to a string slice.
-// If v is a string, it will be wrapped in a string slice.
+// ToStringSlicePreserveString is the same as ToStringSlicePreserveStringE,
+// but it never fails.
 func ToStringSlicePreserveString(v interface{}) []string {
+       vv, _ := ToStringSlicePreserveStringE(v)
+       return vv
+}
+
+// ToStringSlicePreserveStringE converts v to a string slice.
+// If v is a string, it will be wrapped in a string slice.
+func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
        if v == nil {
-               return nil
+               return nil, nil
        }
        if sds, ok := v.(string); ok {
-               return []string{sds}
+               return []string{sds}, nil
+       }
+       result, err := cast.ToStringSliceE(v)
+       if err == nil {
+               return result, nil
        }
-       return cast.ToStringSlice(v)
+
+       // Probably []int or similar. Fall back to reflect.
+       vv := reflect.ValueOf(v)
+
+       switch vv.Kind() {
+       case reflect.Slice, reflect.Array:
+               result = make([]string, vv.Len())
+               for i := 0; i < vv.Len(); i++ {
+                       s, err := cast.ToStringE(vv.Index(i).Interface())
+                       if err != nil {
+                               return nil, err
+                       }
+                       result[i] = s
+               }
+               return result, nil
+       default:
+               return nil, fmt.Errorf("failed to convert %T to a string slice", v)
+       }
+
 }
 
 // TypeToString converts v to a string if it's a valid string type.
index d053ede60c1a1926e07a640c0a5a14e07a4bc24d..364228f4116d8875ab6bfe4cd45276132026111d 100644 (file)
@@ -24,7 +24,9 @@ func TestToStringSlicePreserveString(t *testing.T) {
        c := qt.New(t)
 
        c.Assert(ToStringSlicePreserveString("Hugo"), qt.DeepEquals, []string{"Hugo"})
+       c.Assert(ToStringSlicePreserveString(qt.Commentf("Hugo")), qt.DeepEquals, []string{"Hugo"})
        c.Assert(ToStringSlicePreserveString([]interface{}{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
+       c.Assert(ToStringSlicePreserveString([]int{1, 3}), qt.DeepEquals, []string{"1", "3"})
        c.Assert(ToStringSlicePreserveString(nil), qt.IsNil)
 }
 
index 661c0bdfaec63d89df84520064e97b13338098b7..b3edf8f447d6e23aec9256944464fb4b9e767505 100644 (file)
@@ -114,19 +114,10 @@ You can use the following code to render the `Short Description` in your layout:
 
 Note the use of the [`markdownify` template function][markdownify]. This will send the description through the Blackfriday Markdown rendering engine.
 
-<!-- begin "Data-drive Content" page -->
 
-## Data-Driven Content
+## Get Remote Data
 
-In addition to the [data files](/extras/datafiles/) feature, Hugo also has a "data-driven content" feature, which lets you load any [JSON](https://www.json.org/) or [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) file from nearly any resource.
-
-Data-driven content currently consists of two functions, `getJSON` and `getCSV`, which are available in all template files.
-
-## Implementation details
-
-### Call the Functions with a URL
-
-In your template, call the functions like this:
+Use `getJSON` or `getCSV` to get remote data:
 
 ```
 {{ $dataJ := getJSON "url" }}
@@ -155,19 +146,18 @@ This will resolve internally to the following:
 {{ $gistJ := getJSON "https://api.github.com/users/GITHUB_USERNAME/gists" }}
 ```
 
-Finally, you can range over an array. This example will output the
-first 5 gists for a GitHub user:
+### Add HTTP headers
+
+{{< new-in "0.84.0" >}} Both `getJSON` and `getCSV` takes an optional map as the last argument, e.g.:
 
 ```
-<ul>
-  {{ $urlPre := "https://api.github.com" }}
-  {{ $gistJ := getJSON $urlPre "/users/GITHUB_USERNAME/gists" }}
-  {{ range first 5 $gistJ }}
-    {{ if .public }}
-      <li><a href="{{ .html_url }}" target="_blank">{{ .description }}</a></li>
-    {{ end }}
-  {{ end }}
-</ul>
+{{ $data := getJSON "https://example.org/api" (dict "Authorization" "Bearer abcd")  }}
+```
+
+If you need multiple values for the same header key, use a slice:
+
+```
+{{ $data := getJSON "https://example.org/api" (dict "X-List" (slice "a" "b" "c"))  }}
 ```
 
 ### Example for CSV files
index 4cb8b5e785250c4194de805be254e3a40b1ecf80..e993ed140da7328b25d9865f77b91188eb52b83a 100644 (file)
@@ -23,6 +23,10 @@ import (
        "net/http"
        "strings"
 
+       "github.com/gohugoio/hugo/common/maps"
+
+       "github.com/gohugoio/hugo/common/types"
+
        "github.com/gohugoio/hugo/common/constants"
        "github.com/gohugoio/hugo/common/loggers"
 
@@ -59,14 +63,10 @@ type Namespace struct {
 // If you provide multiple parts for the URL they will be joined together to the final URL.
 // GetCSV returns nil or a slice slice to use in a short code.
 func (ns *Namespace) GetCSV(sep string, args ...interface{}) (d [][]string, err error) {
-       url := joinURL(args)
+       url, headers := toURLAndHeaders(args)
        cache := ns.cacheGetCSV
 
        unmarshal := func(b []byte) (bool, error) {
-               if !bytes.Contains(b, []byte(sep)) {
-                       return false, _errors.Errorf("cannot find separator %s in CSV for %s", sep, url)
-               }
-
                if d, err = parseCSV(b, sep); err != nil {
                        err = _errors.Wrapf(err, "failed to parse CSV file %s", url)
 
@@ -82,17 +82,9 @@ func (ns *Namespace) GetCSV(sep string, args ...interface{}) (d [][]string, err
                return nil, _errors.Wrapf(err, "failed to create request for getCSV for resource %s", url)
        }
 
-       req.Header.Add("Accept", "text/csv")
-       req.Header.Add("Accept", "text/plain")
-
-       // Add custom user headers to the get request
-       finalArg := args[len(args)-1]
-
-       if userHeaders, ok := finalArg.(map[string]interface{}); ok {
-               for key, val := range userHeaders {
-                       req.Header.Add(key, val.(string))
-               }
-       }
+       // Add custom user headers.
+       addUserProvidedHeaders(headers, req)
+       addDefaultHeaders(req, "text/csv", "text/plain")
 
        err = ns.getResource(cache, unmarshal, req)
        if err != nil {
@@ -108,7 +100,7 @@ func (ns *Namespace) GetCSV(sep string, args ...interface{}) (d [][]string, err
 // GetJSON returns nil or parsed JSON to use in a short code.
 func (ns *Namespace) GetJSON(args ...interface{}) (interface{}, error) {
        var v interface{}
-       url := joinURL(args)
+       url, headers := toURLAndHeaders(args)
        cache := ns.cacheGetJSON
 
        req, err := http.NewRequest("GET", url, nil)
@@ -124,17 +116,8 @@ func (ns *Namespace) GetJSON(args ...interface{}) (interface{}, error) {
                return false, nil
        }
 
-       req.Header.Add("Accept", "application/json")
-       req.Header.Add("User-Agent", "Hugo Static Site Generator")
-
-       // Add custom user headers to the get request
-       finalArg := args[len(args)-1]
-
-       if userHeaders, ok := finalArg.(map[string]interface{}); ok {
-               for key, val := range userHeaders {
-                       req.Header.Add(key, val.(string))
-               }
-       }
+       addUserProvidedHeaders(headers, req)
+       addDefaultHeaders(req, "application/json")
 
        err = ns.getResource(cache, unmarshal, req)
        if err != nil {
@@ -145,8 +128,64 @@ func (ns *Namespace) GetJSON(args ...interface{}) (interface{}, error) {
        return v, nil
 }
 
-func joinURL(urlParts []interface{}) string {
-       return strings.Join(cast.ToStringSlice(urlParts), "")
+func addDefaultHeaders(req *http.Request, accepts ...string) {
+       for _, accept := range accepts {
+               if !hasHeaderValue(req.Header, "Accept", accept) {
+                       req.Header.Add("Accept", accept)
+               }
+       }
+       if !hasHeaderKey(req.Header, "User-Agent") {
+               req.Header.Add("User-Agent", "Hugo Static Site Generator")
+       }
+}
+
+func addUserProvidedHeaders(headers map[string]interface{}, req *http.Request) {
+       if headers == nil {
+               return
+       }
+       for key, val := range headers {
+               vals := types.ToStringSlicePreserveString(val)
+               for _, s := range vals {
+                       req.Header.Add(key, s)
+               }
+       }
+}
+
+func hasHeaderValue(m http.Header, key, value string) bool {
+       var s []string
+       var ok bool
+
+       if s, ok = m[key]; !ok {
+               return false
+       }
+
+       for _, v := range s {
+               if v == value {
+                       return true
+               }
+       }
+       return false
+}
+
+func hasHeaderKey(m http.Header, key string) bool {
+       _, ok := m[key]
+       return ok
+}
+
+func toURLAndHeaders(urlParts []interface{}) (string, map[string]interface{}) {
+       if len(urlParts) == 0 {
+               return "", nil
+       }
+
+       // The last argument may be a map.
+       headers, err := maps.ToStringMapE(urlParts[len(urlParts)-1])
+       if err == nil {
+               urlParts = urlParts[:len(urlParts)-1]
+       } else {
+               headers = nil
+       }
+
+       return strings.Join(cast.ToStringSlice(urlParts), ""), headers
 }
 
 // parseCSV parses bytes of CSV data into a slice slice string or an error
index 6b62a2b0d40aeae3fa23bdda715f2b5129f9a64e..8a18a19e47cc5e82ea0d66daa8f1dfd3583c490e 100644 (file)
 package data
 
 import (
+       "bytes"
+       "html/template"
        "net/http"
        "net/http/httptest"
        "path/filepath"
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/common/maps"
+
        qt "github.com/frankban/quicktest"
 )
 
@@ -46,12 +50,6 @@ func TestGetCSV(t *testing.T) {
                        "gomeetup,city\nyes,Sydney\nyes,San Francisco\nyes,Stockholm,EXTRA\n",
                        false,
                },
-               {
-                       ",",
-                       `http://error.no.sep/`,
-                       "gomeetup;city\nyes;Sydney\nyes;San Francisco\nyes;Stockholm\n",
-                       false,
-               },
                {
                        ",",
                        `http://nofound/404`,
@@ -73,66 +71,54 @@ func TestGetCSV(t *testing.T) {
                        false,
                },
        } {
-               msg := qt.Commentf("Test %d", i)
 
-               ns := newTestNs()
-
-               // Setup HTTP test server
-               var srv *httptest.Server
-               srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
-                       if !haveHeader(r.Header, "Accept", "text/csv") && !haveHeader(r.Header, "Accept", "text/plain") {
-                               http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
-                               return
-                       }
+               c.Run(test.url, func(c *qt.C) {
+                       msg := qt.Commentf("Test %d", i)
 
-                       if r.URL.Path == "/404" {
-                               http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
-                               return
-                       }
+                       ns := newTestNs()
 
-                       w.Header().Add("Content-type", "text/csv")
+                       // Setup HTTP test server
+                       var srv *httptest.Server
+                       srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
+                               if !hasHeaderValue(r.Header, "Accept", "text/csv") && !hasHeaderValue(r.Header, "Accept", "text/plain") {
+                                       http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+                                       return
+                               }
 
-                       w.Write([]byte(test.content))
-               })
-               defer func() { srv.Close() }()
+                               if r.URL.Path == "/404" {
+                                       http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+                                       return
+                               }
 
-               // Setup local test file for schema-less URLs
-               if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
-                       f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url))
-                       c.Assert(err, qt.IsNil, msg)
-                       f.WriteString(test.content)
-                       f.Close()
-               }
+                               w.Header().Add("Content-type", "text/csv")
 
-               // Get on with it
-               got, err := ns.GetCSV(test.sep, test.url)
+                               w.Write([]byte(test.content))
+                       })
+                       defer func() { srv.Close() }()
 
-               if _, ok := test.expect.(bool); ok {
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
-                       // c.Assert(err, msg, qt.Not(qt.IsNil))
-                       c.Assert(got, qt.IsNil)
-                       continue
-               }
+                       // Setup local test file for schema-less URLs
+                       if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
+                               f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url))
+                               c.Assert(err, qt.IsNil, msg)
+                               f.WriteString(test.content)
+                               f.Close()
+                       }
 
-               c.Assert(err, qt.IsNil, msg)
-               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
-               c.Assert(got, qt.Not(qt.IsNil), msg)
-               c.Assert(got, qt.DeepEquals, test.expect, msg)
+                       // Get on with it
+                       got, err := ns.GetCSV(test.sep, test.url)
 
-               // Test user-defined headers as well
-               gotHeader, _ := ns.GetCSV(test.sep, test.url, map[string]interface{}{"Accept-Charset": "utf-8", "Max-Forwards": "10"})
+                       if _, ok := test.expect.(bool); ok {
+                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
+                               c.Assert(got, qt.IsNil)
+                               return
+                       }
 
-               if _, ok := test.expect.(bool); ok {
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
-                       // c.Assert(err, msg, qt.Not(qt.IsNil))
-                       c.Assert(got, qt.IsNil)
-                       continue
-               }
+                       c.Assert(err, qt.IsNil, msg)
+                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
+                       c.Assert(got, qt.Not(qt.IsNil), msg)
+                       c.Assert(got, qt.DeepEquals, test.expect, msg)
+               })
 
-               c.Assert(err, qt.IsNil, msg)
-               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
-               c.Assert(gotHeader, qt.Not(qt.IsNil), msg)
-               c.Assert(gotHeader, qt.DeepEquals, test.expect, msg)
        }
 }
 
@@ -178,68 +164,153 @@ func TestGetJSON(t *testing.T) {
                },
        } {
 
-               msg := qt.Commentf("Test %d", i)
-               ns := newTestNs()
+               c.Run(test.url, func(c *qt.C) {
 
-               // Setup HTTP test server
-               var srv *httptest.Server
-               srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
-                       if !haveHeader(r.Header, "Accept", "application/json") {
-                               http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
-                               return
+                       msg := qt.Commentf("Test %d", i)
+                       ns := newTestNs()
+
+                       // Setup HTTP test server
+                       var srv *httptest.Server
+                       srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
+                               if !hasHeaderValue(r.Header, "Accept", "application/json") {
+                                       http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+                                       return
+                               }
+
+                               if r.URL.Path == "/404" {
+                                       http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+                                       return
+                               }
+
+                               w.Header().Add("Content-type", "application/json")
+
+                               w.Write([]byte(test.content))
+                       })
+                       defer func() { srv.Close() }()
+
+                       // Setup local test file for schema-less URLs
+                       if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
+                               f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url))
+                               c.Assert(err, qt.IsNil, msg)
+                               f.WriteString(test.content)
+                               f.Close()
                        }
 
-                       if r.URL.Path == "/404" {
-                               http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+                       // Get on with it
+                       got, _ := ns.GetJSON(test.url)
+
+                       if _, ok := test.expect.(bool); ok {
+                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
                                return
                        }
 
-                       w.Header().Add("Content-type", "application/json")
+                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0, msg)
+                       c.Assert(got, qt.Not(qt.IsNil), msg)
+                       c.Assert(got, qt.DeepEquals, test.expect)
 
-                       w.Write([]byte(test.content))
                })
-               defer func() { srv.Close() }()
+       }
+}
 
-               // Setup local test file for schema-less URLs
-               if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
-                       f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url))
-                       c.Assert(err, qt.IsNil, msg)
-                       f.WriteString(test.content)
-                       f.Close()
-               }
+func TestHeaders(t *testing.T) {
+       t.Parallel()
+       c := qt.New(t)
 
-               // Get on with it
-               got, _ := ns.GetJSON(test.url)
+       for _, test := range []struct {
+               name    string
+               headers interface{}
+               assert  func(c *qt.C, headers string)
+       }{
+               {
+                       `Misc header variants`,
+                       map[string]interface{}{
+                               "Accept-Charset": "utf-8",
+                               "Max-forwards":   "10",
+                               "X-Int":          32,
+                               "X-Templ":        template.HTML("a"),
+                               "X-Multiple":     []string{"a", "b"},
+                               "X-MultipleInt":  []int{3, 4},
+                       },
+                       func(c *qt.C, headers string) {
+                               c.Assert(headers, qt.Contains, "Accept-Charset: utf-8")
+                               c.Assert(headers, qt.Contains, "Max-Forwards: 10")
+                               c.Assert(headers, qt.Contains, "X-Int: 32")
+                               c.Assert(headers, qt.Contains, "X-Templ: a")
+                               c.Assert(headers, qt.Contains, "X-Multiple: a")
+                               c.Assert(headers, qt.Contains, "X-Multiple: b")
+                               c.Assert(headers, qt.Contains, "X-Multipleint: 3")
+                               c.Assert(headers, qt.Contains, "X-Multipleint: 4")
+                               c.Assert(headers, qt.Contains, "User-Agent: Hugo Static Site Generator")
+                       },
+               },
+               {
+                       `Params`,
+                       maps.Params{
+                               "Accept-Charset": "utf-8",
+                       },
+                       func(c *qt.C, headers string) {
+                               c.Assert(headers, qt.Contains, "Accept-Charset: utf-8")
+                       },
+               },
+               {
+                       `Override User-Agent`,
+                       map[string]interface{}{
+                               "User-Agent": "007",
+                       },
+                       func(c *qt.C, headers string) {
+                               c.Assert(headers, qt.Contains, "User-Agent: 007")
+                       },
+               },
+       } {
 
-               if _, ok := test.expect.(bool); ok {
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
-                       // c.Assert(err, msg, qt.Not(qt.IsNil))
-                       continue
-               }
+               c.Run(test.name, func(c *qt.C) {
 
-               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0, msg)
-               c.Assert(got, qt.Not(qt.IsNil), msg)
-               c.Assert(got, qt.DeepEquals, test.expect)
+                       ns := newTestNs()
 
-               // Test user-defined headers as well
-               gotHeader, _ := ns.GetJSON(test.url, map[string]interface{}{"Accept-Charset": "utf-8", "Max-Forwards": "10"})
+                       // Setup HTTP test server
+                       var srv *httptest.Server
+                       var headers bytes.Buffer
+                       srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
+                               c.Assert(r.URL.String(), qt.Equals, "http://gohugo.io/api?foo")
+                               w.Write([]byte("{}"))
+                               r.Header.Write(&headers)
 
-               if _, ok := test.expect.(bool); ok {
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
-                       // c.Assert(err, msg, qt.Not(qt.IsNil))
-                       continue
-               }
+                       })
+                       defer func() { srv.Close() }()
+
+                       testFunc := func(fn func(args ...interface{}) error) {
+                               defer headers.Reset()
+                               err := fn("http://example.org/api", "?foo", test.headers)
+
+                               c.Assert(err, qt.IsNil)
+                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
+                               test.assert(c, headers.String())
+                       }
+
+                       testFunc(func(args ...interface{}) error {
+                               _, err := ns.GetJSON(args...)
+                               return err
+                       })
+                       testFunc(func(args ...interface{}) error {
+                               _, err := ns.GetCSV(",", args...)
+                               return err
+                       })
+
+               })
 
-               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0, msg)
-               c.Assert(gotHeader, qt.Not(qt.IsNil), msg)
-               c.Assert(gotHeader, qt.DeepEquals, test.expect)
        }
 }
 
-func TestJoinURL(t *testing.T) {
+func TestToURLAndHeaders(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
-       c.Assert(joinURL([]interface{}{"https://foo?id=", 32}), qt.Equals, "https://foo?id=32")
+       url, headers := toURLAndHeaders([]interface{}{"https://foo?id=", 32})
+       c.Assert(url, qt.Equals, "https://foo?id=32")
+       c.Assert(headers, qt.IsNil)
+
+       url, headers = toURLAndHeaders([]interface{}{"https://foo?id=", 32, map[string]interface{}{"a": "b"}})
+       c.Assert(url, qt.Equals, "https://foo?id=32")
+       c.Assert(headers, qt.DeepEquals, map[string]interface{}{"a": "b"})
 }
 
 func TestParseCSV(t *testing.T) {
@@ -276,19 +347,3 @@ func TestParseCSV(t *testing.T) {
                c.Assert(act, qt.Equals, test.exp, msg)
        }
 }
-
-func haveHeader(m http.Header, key, needle string) bool {
-       var s []string
-       var ok bool
-
-       if s, ok = m[key]; !ok {
-               return false
-       }
-
-       for _, v := range s {
-               if v == needle {
-                       return true
-               }
-       }
-       return false
-}
index ba98f12b4d4ecc4875274dad447f9cb91c112d11..68f18c48e09b6539e34f2fab4f7b8f2620a7c51d 100644 (file)
@@ -14,6 +14,7 @@
 package data
 
 import (
+       "bytes"
        "io/ioutil"
        "net/http"
        "net/url"
@@ -37,7 +38,9 @@ var (
 // getRemote loads the content of a remote file. This method is thread safe.
 func (ns *Namespace) getRemote(cache *filecache.Cache, unmarshal func([]byte) (bool, error), req *http.Request) error {
        url := req.URL.String()
-       id := helpers.MD5String(url)
+       var headers bytes.Buffer
+       req.Header.Write(&headers)
+       id := helpers.MD5String(url + headers.String())
        var handled bool
        var retry bool
 
@@ -94,10 +97,6 @@ func (ns *Namespace) getRemote(cache *filecache.Cache, unmarshal func([]byte) (b
 // getLocal loads the content of a local file
 func getLocal(url string, fs afero.Fs, cfg config.Provider) ([]byte, error) {
        filename := filepath.Join(cfg.GetString("workingDir"), url)
-       if e, err := helpers.Exists(filename, fs); !e {
-               return nil, err
-       }
-
        return afero.ReadFile(fs, filename)
 }