mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 17:43:35 -06:00
For a proxied request, e.g. Grafana's datasource or plugin proxy: If the request is cancelled, e.g. from the browser, the HTTP status code is now 499 Client closed request instead of 502 Bad gateway. If the request times out, e.g. takes longer time than allowed, the HTTP status code is now 504 Gateway timeout instead of 502 Bad gateway. This also means that request metrics and logs will get their status codes adjusted according to above. Fixes #46337 Fixes #46338
126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package pluginproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/services/pluginsettings"
|
|
"github.com/grafana/grafana/pkg/services/secrets"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
"github.com/grafana/grafana/pkg/util/proxyutil"
|
|
)
|
|
|
|
type templateData struct {
|
|
JsonData map[string]interface{}
|
|
SecureJsonData map[string]string
|
|
}
|
|
|
|
// NewApiPluginProxy create a plugin proxy
|
|
func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.Route,
|
|
appID string, cfg *setting.Cfg, pluginSettingsService pluginsettings.Service,
|
|
secretsService secrets.Service) *httputil.ReverseProxy {
|
|
appProxyLogger := logger.New(
|
|
"userId", ctx.UserId,
|
|
"orgId", ctx.OrgId,
|
|
"uname", ctx.Login,
|
|
"app", appID,
|
|
"path", ctx.Req.URL.Path,
|
|
"remote_addr", ctx.RemoteAddr(),
|
|
"referer", ctx.Req.Referer(),
|
|
)
|
|
|
|
director := func(req *http.Request) {
|
|
query := pluginsettings.GetByPluginIDArgs{OrgID: ctx.OrgId, PluginID: appID}
|
|
ps, err := pluginSettingsService.GetPluginSettingByPluginID(ctx.Req.Context(), &query)
|
|
if err != nil {
|
|
ctx.JsonApiErr(500, "Failed to fetch plugin settings", err)
|
|
return
|
|
}
|
|
|
|
secureJsonData, err := secretsService.DecryptJsonData(ctx.Req.Context(), ps.SecureJSONData)
|
|
if err != nil {
|
|
ctx.JsonApiErr(500, "Failed to decrypt plugin settings", err)
|
|
return
|
|
}
|
|
|
|
data := templateData{
|
|
JsonData: ps.JSONData,
|
|
SecureJsonData: secureJsonData,
|
|
}
|
|
|
|
interpolatedURL, err := interpolateString(route.URL, data)
|
|
if err != nil {
|
|
ctx.JsonApiErr(500, "Could not interpolate plugin route url", err)
|
|
return
|
|
}
|
|
targetURL, err := url.Parse(interpolatedURL)
|
|
if err != nil {
|
|
ctx.JsonApiErr(500, "Could not parse url", err)
|
|
return
|
|
}
|
|
req.URL.Scheme = targetURL.Scheme
|
|
req.URL.Host = targetURL.Host
|
|
req.Host = targetURL.Host
|
|
req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath)
|
|
|
|
// clear cookie headers
|
|
req.Header.Del("Cookie")
|
|
req.Header.Del("Set-Cookie")
|
|
|
|
// Create a HTTP header with the context in it.
|
|
ctxJSON, err := json.Marshal(ctx.SignedInUser)
|
|
if err != nil {
|
|
ctx.JsonApiErr(500, "failed to marshal context to json.", err)
|
|
return
|
|
}
|
|
|
|
req.Header.Set("X-Grafana-Context", string(ctxJSON))
|
|
|
|
applyUserHeader(cfg.SendUserHeader, req, ctx.SignedInUser)
|
|
|
|
if err := addHeaders(&req.Header, route, data); err != nil {
|
|
ctx.JsonApiErr(500, "Failed to render plugin headers", err)
|
|
return
|
|
}
|
|
|
|
if err := setBodyContent(req, route, data); err != nil {
|
|
appProxyLogger.Error("Failed to set plugin route body content", "error", err)
|
|
}
|
|
}
|
|
|
|
logAppPluginProxyRequest(appID, cfg, ctx)
|
|
|
|
return proxyutil.NewReverseProxy(appProxyLogger, director)
|
|
}
|
|
|
|
func logAppPluginProxyRequest(appID string, cfg *setting.Cfg, c *models.ReqContext) {
|
|
if !cfg.DataProxyLogging {
|
|
return
|
|
}
|
|
|
|
var body string
|
|
if c.Req.Body != nil {
|
|
buffer, err := ioutil.ReadAll(c.Req.Body)
|
|
if err == nil {
|
|
c.Req.Body = ioutil.NopCloser(bytes.NewBuffer(buffer))
|
|
body = string(buffer)
|
|
}
|
|
}
|
|
|
|
logger.Info("Proxying incoming request",
|
|
"userid", c.UserId,
|
|
"orgid", c.OrgId,
|
|
"username", c.Login,
|
|
"app", appID,
|
|
"uri", c.Req.RequestURI,
|
|
"method", c.Req.Method,
|
|
"body", body)
|
|
}
|