revel 框架中的缓存失效

Cache invalidation in revel framework

我正在寻找一种在版本更改时使缓存的静态内容无效的方法。最好使用提交 ID 使之无效。反正有没有在狂欢框架中做到这一点?

如果它是自动的我会更喜欢,但如果它是一个我必须编辑的地方,我可以忍受每次更新它。

我目前的策略是更改静态内容路由的名称以包含版本,但这需要进行多项更改。感觉不自然的地方,比如路由文件中。

您可以通过配置变量和拦截方法手动完成。

resourceversion.go

在您的控制器文件夹中创建此文件:

package controllers

import (
    "github.com/revel/revel"
)

// interceptor method, called before every request. 
// Sets a template variable to the resourceVersion read from app.conf
func SetVersion(c *revel.Controller) revel.Result {
    c.RenderArgs["resourceVersion"] = revel.Config.StringDefault("resourceVersion", "1")
    return nil
}

init.go

在 init() 方法中,附加此行:

revel.InterceptMethod(controllers.SetVersion, revel.BEFORE)

模板

在您的模板中,您要使用资源版本的位置:

<link rel="stylesheet" type="text/css" href="/public/css/style.css?{{.resourceVersion}}">

app.conf

最后,您要更新它的地方 - 在开发部分上方添加此行以应用于开发和生产,或者每个都有不同的部分,无论适合什么。

resourceVersion=20150716

我猜你可以创建一个脚本作为构建和发布过程的一部分,它会自动编辑这个配置变量。

我做了 Colin Nicholson 建议的事情,但我还创建了一个名为 staticversionbasedcacheinvalidator 的控制器并将其放在控制器文件夹中。你可以在下面找到它。它允许您忽略请求字符串的第一部分,允许您使用 public 文件夹的通配符路径。例如我使用这个路由配置行

GET     /public/*filepath                             StaticVersionbasedCacheInvalidator.Serve("public")

然后我在我的路线中添加了 {{.resourceVersion}} 而不是作为查询参数。

package controllers

import (
    "github.com/revel/revel"
    "os"
    fpath "path/filepath"
    "strings"
    "syscall"
)

type StaticVersionbasedCacheInvalidator struct {
    *revel.Controller
}

// This method handles requests for files. The supplied prefix may be absolute
// or relative. If the prefix is relative it is assumed to be relative to the
// application directory. The filepath may either be just a file or an
// additional filepath to search for the given file. This response may return
// the following responses in the event of an error or invalid request;
//   403(Forbidden): If the prefix filepath combination results in a directory.
//   404(Not found): If the prefix and filepath combination results in a non-existent file.
//   500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
//
// Note that when defining routes in routes/conf the parameters must not have
// spaces around the comma.
//   Bad:  StaticVersionbasedCacheInvalidator.Serve("public/img", "favicon.png")
//   Good: StaticVersionbasedCacheInvalidator.Serve("public/img","favicon.png")
//
// Examples:
// Serving a directory
//   Route (conf/routes):
//     GET /public/{<.*>filepath} StaticVersionbasedCacheInvalidator.Serve("public")
//   Request:
//     public/js/sessvars.js
//   Calls
//     StaticVersionbasedCacheInvalidator.Serve("public","js/sessvars.js")
//
// Serving a file
//   Route (conf/routes):
//     GET /favicon.ico StaticVersionbasedCacheInvalidator.Serve("public/img","favicon.png")
//   Request:
//     favicon.ico
//   Calls:
//     StaticVersionbasedCacheInvalidator.Serve("public/img", "favicon.png")
func (c StaticVersionbasedCacheInvalidator) Serve(prefix, filepath string) revel.Result {

    firstSplice := strings.Index(filepath,"/")
    if(firstSplice != -1) {
            filepath = filepath[firstSplice:len(filepath)];
    }

    // Fix for #503.
    prefix = c.Params.Fixed.Get("prefix")
    if prefix == "" {
        return c.NotFound("")
    }

    return serve(c, prefix, filepath)
}

// This method allows modules to serve binary files. The parameters are the same
// as StaticVersionbasedCacheInvalidator.Serve with the additional module name pre-pended to the list of
// arguments.
func (c StaticVersionbasedCacheInvalidator) ServeModule(moduleName, prefix, filepath string) revel.Result {
    // Fix for #503.
    prefix = c.Params.Fixed.Get("prefix")
    if prefix == "" {
        return c.NotFound("")
    }

    var basePath string
    for _, module := range revel.Modules {
        if module.Name == moduleName {
            basePath = module.Path
        }
    }

    absPath := fpath.Join(basePath, fpath.FromSlash(prefix))

    return serve(c, absPath, filepath)
}


// This method allows StaticVersionbasedCacheInvalidator serving of application files in a verified manner.
func serve(c StaticVersionbasedCacheInvalidator, prefix, filepath string) revel.Result {
    var basePath string
    if !fpath.IsAbs(prefix) {
        basePath = revel.BasePath
    }

    basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
    fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
    // Verify the request file path is within the application's scope of access
    if !strings.HasPrefix(fname, basePathPrefix) {
        revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
        return c.NotFound("")
    }

    // Verify file path is accessible
    finfo, err := os.Stat(fname)
    if err != nil {
        if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
            revel.WARN.Printf("File not found (%s): %s ", fname, err)
            return c.NotFound("File not found")
        }
        revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
        return c.RenderError(err)
    }

    // Disallow directory listing
    if finfo.Mode().IsDir() {
        revel.WARN.Printf("Attempted directory listing of %s", fname)
        return c.Forbidden("Directory listing not allowed")
    }

    // Open request file path
    file, err := os.Open(fname)
    if err != nil {
        if os.IsNotExist(err) {
            revel.WARN.Printf("File not found (%s): %s ", fname, err)
            return c.NotFound("File not found")
        }
        revel.ERROR.Printf("Error opening '%s': %s", fname, err)
        return c.RenderError(err)
    }
    return c.RenderFile(file, revel.Inline)
}