Plugins: Automatically forward plugin request HTTP headers in outgoing HTTP requests (#60417)

Automatically forward core plugin request HTTP headers in outgoing HTTP requests. 
Core datasource plugin authors don't have to specifically handle forwarding of HTTP 
headers, e.g. do not have to "hardcode" the header-names in the datasource plugin, 
if not having custom needs.

Fixes #57065
This commit is contained in:
Marcus Efraimsson
2022-12-21 13:25:58 +01:00
committed by GitHub
parent aaab477594
commit c35c689a96
32 changed files with 816 additions and 1194 deletions

View File

@@ -1,27 +0,0 @@
package httpclientprovider
import (
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
)
const DeleteHeadersMiddlewareName = "delete-headers"
// DeleteHeadersMiddleware middleware that delete headers on the outgoing
// request if header names provided.
func DeleteHeadersMiddleware(headerNames ...string) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(DeleteHeadersMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
if len(headerNames) == 0 {
return next
}
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
for _, k := range headerNames {
req.Header.Del(k)
}
return next.RoundTrip(req)
})
})
}

View File

@@ -1,66 +0,0 @@
package httpclientprovider
import (
"net/http"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/stretchr/testify/require"
)
func TestDeleteHeadersMiddleware(t *testing.T) {
t.Run("Without headerNames should return next http.RoundTripper", func(t *testing.T) {
ctx := &testContext{}
finalRoundTripper := ctx.createRoundTripper("finalrt")
var headerNames []string
mw := DeleteHeadersMiddleware(headerNames...)
rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper)
require.NotNil(t, rt)
middlewareName, ok := mw.(httpclient.MiddlewareName)
require.True(t, ok)
require.Equal(t, DeleteHeadersMiddlewareName, middlewareName.MiddlewareName())
req, err := http.NewRequest(http.MethodGet, "http://", nil)
require.NoError(t, err)
res, err := rt.RoundTrip(req)
require.NoError(t, err)
require.NotNil(t, res)
if res.Body != nil {
require.NoError(t, res.Body.Close())
}
require.Len(t, ctx.callChain, 1)
require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain)
})
t.Run("With headers set should apply HTTP headers to the request", func(t *testing.T) {
ctx := &testContext{}
finalRoundTripper := ctx.createRoundTripper("final")
headerNames := []string{"X-Header-B", "X-Header-C"}
mw := DeleteHeadersMiddleware(headerNames...)
rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper)
require.NotNil(t, rt)
middlewareName, ok := mw.(httpclient.MiddlewareName)
require.True(t, ok)
require.Equal(t, DeleteHeadersMiddlewareName, middlewareName.MiddlewareName())
req, err := http.NewRequest(http.MethodGet, "http://", nil)
require.NoError(t, err)
req.Header.Set("X-Header-A", "a")
req.Header.Set("X-Header-B", "b")
req.Header.Set("X-Header-C", "c")
req.Header.Set("X-Header-D", "d")
res, err := rt.RoundTrip(req)
require.NoError(t, err)
require.NotNil(t, res)
if res.Body != nil {
require.NoError(t, res.Body.Close())
}
require.Len(t, ctx.callChain, 1)
require.ElementsMatch(t, []string{"final"}, ctx.callChain)
require.Equal(t, "a", req.Header.Get("X-Header-A"))
require.Empty(t, req.Header.Get("X-Header-B"))
require.Empty(t, req.Header.Get("X-Header-C"))
require.Equal(t, "d", req.Header.Get("X-Header-D"))
})
}

View File

@@ -1,31 +0,0 @@
package httpclientprovider
import (
"net/http"
"net/textproto"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
)
const SetHeadersMiddlewareName = "set-headers"
// SetHeadersMiddleware middleware that sets headers on the outgoing
// request if headers provided.
// If the request already contains any of the headers provided, they
// will be overwritten.
func SetHeadersMiddleware(headers http.Header) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(SetHeadersMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
if len(headers) == 0 {
return next
}
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
for k, v := range headers {
canonicalKey := textproto.CanonicalMIMEHeaderKey(k)
req.Header[canonicalKey] = v
}
return next.RoundTrip(req)
})
})
}

View File

@@ -1,66 +0,0 @@
package httpclientprovider
import (
"net/http"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/stretchr/testify/require"
)
func TestSetHeadersMiddleware(t *testing.T) {
t.Run("Without headers set should return next http.RoundTripper", func(t *testing.T) {
ctx := &testContext{}
finalRoundTripper := ctx.createRoundTripper("finalrt")
var headers http.Header
mw := SetHeadersMiddleware(headers)
rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper)
require.NotNil(t, rt)
middlewareName, ok := mw.(httpclient.MiddlewareName)
require.True(t, ok)
require.Equal(t, SetHeadersMiddlewareName, middlewareName.MiddlewareName())
req, err := http.NewRequest(http.MethodGet, "http://", nil)
require.NoError(t, err)
res, err := rt.RoundTrip(req)
require.NoError(t, err)
require.NotNil(t, res)
if res.Body != nil {
require.NoError(t, res.Body.Close())
}
require.Len(t, ctx.callChain, 1)
require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain)
})
t.Run("With headers set should apply HTTP headers to the request", func(t *testing.T) {
ctx := &testContext{}
finalRoundTripper := ctx.createRoundTripper("final")
headers := http.Header{
"X-Header-A": []string{"value a"},
"X-Header-B": []string{"value b"},
"X-HEader-C": []string{"value c"},
}
mw := SetHeadersMiddleware(headers)
rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper)
require.NotNil(t, rt)
middlewareName, ok := mw.(httpclient.MiddlewareName)
require.True(t, ok)
require.Equal(t, SetHeadersMiddlewareName, middlewareName.MiddlewareName())
req, err := http.NewRequest(http.MethodGet, "http://", nil)
require.NoError(t, err)
req.Header.Set("X-Header-B", "d")
res, err := rt.RoundTrip(req)
require.NoError(t, err)
require.NotNil(t, res)
if res.Body != nil {
require.NoError(t, res.Body.Close())
}
require.Len(t, ctx.callChain, 1)
require.ElementsMatch(t, []string{"final"}, ctx.callChain)
require.Equal(t, "value a", req.Header.Get("X-Header-A"))
require.Equal(t, "value b", req.Header.Get("X-Header-B"))
require.Equal(t, "value c", req.Header.Get("X-Header-C"))
})
}