mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Added custom cache control headers for static content
This commit is contained in:
parent
98c0209976
commit
5f0e7cd52a
@ -1,10 +1,9 @@
|
||||
app_name = Grafana
|
||||
app_mode = production
|
||||
|
||||
# Once every 1 hour Grafana will report anonymous data to
|
||||
# stats.grafana.org (https). No ip addresses are being tracked.
|
||||
# only simple counters to track running instances, dashboard
|
||||
# count and errors. It is very helpful to us.
|
||||
# Report anonymous usage counters to stats.grafana.org (https).
|
||||
# No ip addresses are being tracked, only simple counters to track
|
||||
# running instances, dashboard count and errors. It is very helpful to us.
|
||||
# Change this option to false to disable reporting.
|
||||
reporting-enabled = true
|
||||
|
||||
|
@ -35,5 +35,6 @@ func GetDashboardSnapshot(c *middleware.Context) {
|
||||
Meta: dtos.DashboardMeta{IsSnapshot: true},
|
||||
}
|
||||
|
||||
c.Resp.Header().Set("Cache-Control", "public max-age: 31536000")
|
||||
c.JSON(200, dto)
|
||||
}
|
||||
|
218
pkg/api/static/static.go
Normal file
218
pkg/api/static/static.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package httpstatic
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
)
|
||||
|
||||
var Root string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
Root, err = os.Getwd()
|
||||
if err != nil {
|
||||
panic("error getting work directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// StaticOptions is a struct for specifying configuration options for the macaron.Static middleware.
|
||||
type StaticOptions struct {
|
||||
// Prefix is the optional prefix used to serve the static directory content
|
||||
Prefix string
|
||||
// SkipLogging will disable [Static] log messages when a static file is served.
|
||||
SkipLogging bool
|
||||
// IndexFile defines which file to serve as index if it exists.
|
||||
IndexFile string
|
||||
// Expires defines which user-defined function to use for producing a HTTP Expires Header
|
||||
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
|
||||
AddHeaders func(ctx *macaron.Context)
|
||||
// FileSystem is the interface for supporting any implmentation of file system.
|
||||
FileSystem http.FileSystem
|
||||
}
|
||||
|
||||
// FIXME: to be deleted.
|
||||
type staticMap struct {
|
||||
lock sync.RWMutex
|
||||
data map[string]*http.Dir
|
||||
}
|
||||
|
||||
func (sm *staticMap) Set(dir *http.Dir) {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
|
||||
sm.data[string(*dir)] = dir
|
||||
}
|
||||
|
||||
func (sm *staticMap) Get(name string) *http.Dir {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
|
||||
return sm.data[name]
|
||||
}
|
||||
|
||||
func (sm *staticMap) Delete(name string) {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
|
||||
delete(sm.data, name)
|
||||
}
|
||||
|
||||
var statics = staticMap{sync.RWMutex{}, map[string]*http.Dir{}}
|
||||
|
||||
// staticFileSystem implements http.FileSystem interface.
|
||||
type staticFileSystem struct {
|
||||
dir *http.Dir
|
||||
}
|
||||
|
||||
func newStaticFileSystem(directory string) staticFileSystem {
|
||||
if !filepath.IsAbs(directory) {
|
||||
directory = filepath.Join(Root, directory)
|
||||
}
|
||||
dir := http.Dir(directory)
|
||||
statics.Set(&dir)
|
||||
return staticFileSystem{&dir}
|
||||
}
|
||||
|
||||
func (fs staticFileSystem) Open(name string) (http.File, error) {
|
||||
return fs.dir.Open(name)
|
||||
}
|
||||
|
||||
func prepareStaticOption(dir string, opt StaticOptions) StaticOptions {
|
||||
// Defaults
|
||||
if len(opt.IndexFile) == 0 {
|
||||
opt.IndexFile = "index.html"
|
||||
}
|
||||
// Normalize the prefix if provided
|
||||
if opt.Prefix != "" {
|
||||
// Ensure we have a leading '/'
|
||||
if opt.Prefix[0] != '/' {
|
||||
opt.Prefix = "/" + opt.Prefix
|
||||
}
|
||||
// Remove any trailing '/'
|
||||
opt.Prefix = strings.TrimRight(opt.Prefix, "/")
|
||||
}
|
||||
if opt.FileSystem == nil {
|
||||
opt.FileSystem = newStaticFileSystem(dir)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions {
|
||||
var opt StaticOptions
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
return prepareStaticOption(dir, opt)
|
||||
}
|
||||
|
||||
func staticHandler(ctx *macaron.Context, log *log.Logger, opt StaticOptions) bool {
|
||||
if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" {
|
||||
return false
|
||||
}
|
||||
|
||||
file := ctx.Req.URL.Path
|
||||
// if we have a prefix, filter requests by stripping the prefix
|
||||
if opt.Prefix != "" {
|
||||
if !strings.HasPrefix(file, opt.Prefix) {
|
||||
return false
|
||||
}
|
||||
file = file[len(opt.Prefix):]
|
||||
if file != "" && file[0] != '/' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
f, err := opt.FileSystem.Open(file)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return true // File exists but fail to open.
|
||||
}
|
||||
|
||||
// Try to serve index file
|
||||
if fi.IsDir() {
|
||||
// Redirect if missing trailing slash.
|
||||
if !strings.HasSuffix(ctx.Req.URL.Path, "/") {
|
||||
http.Redirect(ctx.Resp, ctx.Req.Request, ctx.Req.URL.Path+"/", http.StatusFound)
|
||||
return true
|
||||
}
|
||||
|
||||
file = path.Join(file, opt.IndexFile)
|
||||
f, err = opt.FileSystem.Open(file)
|
||||
if err != nil {
|
||||
return false // Discard error.
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err = f.Stat()
|
||||
if err != nil || fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !opt.SkipLogging {
|
||||
log.Println("[Static] Serving " + file)
|
||||
}
|
||||
|
||||
// Add an Expires header to the static content
|
||||
if opt.AddHeaders != nil {
|
||||
opt.AddHeaders(ctx)
|
||||
}
|
||||
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
|
||||
return true
|
||||
}
|
||||
|
||||
// Static returns a middleware handler that serves static files in the given directory.
|
||||
func Static(directory string, staticOpt ...StaticOptions) macaron.Handler {
|
||||
opt := prepareStaticOptions(directory, staticOpt)
|
||||
|
||||
return func(ctx *macaron.Context, log *log.Logger) {
|
||||
staticHandler(ctx, log, opt)
|
||||
}
|
||||
}
|
||||
|
||||
// Statics registers multiple static middleware handlers all at once.
|
||||
func Statics(opt StaticOptions, dirs ...string) macaron.Handler {
|
||||
if len(dirs) == 0 {
|
||||
panic("no static directory is given")
|
||||
}
|
||||
opts := make([]StaticOptions, len(dirs))
|
||||
for i := range dirs {
|
||||
opts[i] = prepareStaticOption(dirs[i], opt)
|
||||
}
|
||||
|
||||
return func(ctx *macaron.Context, log *log.Logger) {
|
||||
for i := range opts {
|
||||
if staticHandler(ctx, log, opts[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/codegangsta/cli"
|
||||
@ -20,6 +19,7 @@ import (
|
||||
_ "github.com/macaron-contrib/session/postgres"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/static"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
@ -65,14 +65,22 @@ func newMacaron() *macaron.Macaron {
|
||||
}
|
||||
|
||||
func mapStatic(m *macaron.Macaron, dir string, prefix string) {
|
||||
m.Use(macaron.Static(
|
||||
headers := func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "public max-age: 3600")
|
||||
}
|
||||
|
||||
if setting.Env == setting.DEV {
|
||||
headers = func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "max-age: 0")
|
||||
}
|
||||
}
|
||||
|
||||
m.Use(httpstatic.Static(
|
||||
path.Join(setting.StaticRootPath, dir),
|
||||
macaron.StaticOptions{
|
||||
httpstatic.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: prefix,
|
||||
Expires: func() string {
|
||||
return time.Now().UTC().Format(http.TimeFormat)
|
||||
},
|
||||
AddHeaders: headers,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -18,42 +18,44 @@ function (angular) {
|
||||
$rootScope.$broadcast('refresh');
|
||||
|
||||
$timeout(function() {
|
||||
var dash = angular.copy($scope.dashboard);
|
||||
dash.title = $scope.snapshot.name;
|
||||
$scope.saveSnapshot(makePublic);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
dash.forEachPanel(function(panel) {
|
||||
panel.targets = [];
|
||||
panel.links = [];
|
||||
});
|
||||
$scope.saveSnapshot = function(makePublic) {
|
||||
var dash = angular.copy($scope.dashboard);
|
||||
dash.title = $scope.snapshot.name;
|
||||
|
||||
// cleanup snapshotData
|
||||
$scope.dashboard.snapshot = false;
|
||||
$scope.dashboard.forEachPanel(function(panel) {
|
||||
delete panel.snapshotData;
|
||||
});
|
||||
dash.forEachPanel(function(panel) {
|
||||
panel.targets = [];
|
||||
panel.links = [];
|
||||
});
|
||||
|
||||
var apiUrl = '/api/snapshots';
|
||||
// cleanup snapshotData
|
||||
$scope.dashboard.snapshot = false;
|
||||
$scope.dashboard.forEachPanel(function(panel) {
|
||||
delete panel.snapshotData;
|
||||
});
|
||||
|
||||
var apiUrl = '/api/snapshots';
|
||||
|
||||
if (makePublic) {
|
||||
apiUrl = 'http://snapshots.raintank.io/api/snapshots';
|
||||
}
|
||||
|
||||
backendSrv.post(apiUrl, {dashboard: dash}).then(function(results) {
|
||||
$scope.loading = false;
|
||||
|
||||
var baseUrl = $location.absUrl().replace($location.url(), "");
|
||||
if (makePublic) {
|
||||
apiUrl = 'http://snapshots.raintank.io/api/snapshots';
|
||||
baseUrl = 'http://snapshots.raintank.io';
|
||||
}
|
||||
|
||||
backendSrv.post(apiUrl, {dashboard: dash}).then(function(results) {
|
||||
$scope.loading = false;
|
||||
$scope.snapshotUrl = baseUrl + '/dashboard/snapshots/' + results.key;
|
||||
|
||||
var baseUrl = $location.absUrl().replace($location.url(), "");
|
||||
if (makePublic) {
|
||||
baseUrl = 'http://snapshots.raintank.io';
|
||||
}
|
||||
|
||||
$scope.snapshotUrl = baseUrl + '/dashboard/snapshots/' + results.key;
|
||||
|
||||
}, function() {
|
||||
$scope.loading = false;
|
||||
});
|
||||
|
||||
|
||||
}, 2000);
|
||||
}, function() {
|
||||
$scope.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -61,6 +61,7 @@ module.exports = function(config,grunt) {
|
||||
'controllers/all',
|
||||
'routes/all',
|
||||
'components/partials',
|
||||
'plugins/datasource/grafana/datasource',
|
||||
]
|
||||
}
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user