英文:
Cache invalidation in revel framework
问题
我正在寻找一种在版本更改时使缓存的静态内容失效的方法。最好是使用提交 ID 进行失效。在 revel 框架中有没有办法做到这一点?
如果可以自动完成,我更喜欢,但如果只需要在一个地方进行更新,我也可以接受。
我目前的策略是将静态内容路由的名称更改为包含版本信息,但这需要进行多个更改。在某些地方感觉不自然,比如在路由文件中。
英文:
I'm looking for a way to invalidate cached static content upon version change. Preferably using commit id to invalidate. Is there anyway to do this in revel framework ?
I would prefer if its automatic but I could live with updating it each time if its a single place I have to edit.
The current strategy I have is changing the name of the static content route to include version but this requires several changes. In places that feel unnatural, for instance in the routing file.
答案1
得分: 1
你可以通过配置变量和拦截方法来手动完成。
resourceversion.go
在你的controllers文件夹中创建这个文件:
package controllers
import (
"github.com/revel/revel"
)
// 拦截方法,在每个请求之前调用。
// 将一个模板变量设置为从app.conf中读取的resourceVersion
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)
templates
在你的模板中,你想要使用资源版本的地方:
<link rel="stylesheet" type="text/css" href="/public/css/style.css?{{.resourceVersion}}">
app.conf
最后,在你将要更新的地方 - 在dev部分之前添加这一行,以应用于开发和生产环境,或者在每个环境中都有一个不同的配置。
resourceVersion=20150716
我猜你可以创建一个脚本作为构建和发布过程的一部分,自动编辑这个配置变量。
英文:
You could do it manually via a config variable and an intercept method.
resourceversion.go
Create this file in your controllers folder:
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
In the init() method, append this line:
revel.InterceptMethod(controllers.SetVersion, revel.BEFORE)
templates
In your templates, where you want to use the resource version:
<link rel="stylesheet" type="text/css" href="/public/css/style.css?{{.resourceVersion}}">
app.conf
And finally, the place you will update it - add this line above the dev section to apply to dev and prod, or have a different one in each, whatever suits.
resourceVersion=20150716
I guess you could create a script as part of your build and release process that would automatically edit this config variable.
答案2
得分: 0
我按照Colin Nicholson的建议进行了操作,但我还创建了一个名为staticversionbasedcacheinvalidator的控制器,并将其放置在控制器文件夹中。你可以在下面找到它。它允许你忽略请求字符串的前半部分,从而可以使用通配符路径来访问你的公共文件夹。例如,我在路由配置中使用了以下行:
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
}
// 此方法处理文件请求。提供的前缀可以是绝对路径或相对路径。如果前缀是相对路径,则假定它是相对于应用程序目录的。文件路径可以是文件本身,也可以是要搜索给定文件的附加文件路径。在错误或无效请求的情况下,此响应可能返回以下响应:
// 403(Forbidden):如果前缀和文件路径组合导致目录。
// 404(Not found):如果前缀和文件路径组合导致不存在的文件。
// 500(Internal Server Error):在某些边缘情况下,这可能表明一些配置错误超出了revel的范围。
//
// 注意,在routes/conf中定义路由时,参数周围不能有空格。
// 错误的写法:StaticVersionbasedCacheInvalidator.Serve("public/img", "favicon.png")
// 正确的写法:StaticVersionbasedCacheInvalidator.Serve("public/img","favicon.png")
//
// 示例:
// 服务于目录
// 路由 (conf/routes):
// GET /public/{<.*>filepath} StaticVersionbasedCacheInvalidator.Serve("public")
// 请求:
// public/js/sessvars.js
// 调用:
// StaticVersionbasedCacheInvalidator.Serve("public","js/sessvars.js")
//
// 服务于文件
// 路由 (conf/routes):
// GET /favicon.ico StaticVersionbasedCacheInvalidator.Serve("public/img","favicon.png")
// 请求:
// favicon.ico
// 调用:
// 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)];
}
// 修复#503。
prefix = c.Params.Fixed.Get("prefix")
if prefix == "" {
return c.NotFound("")
}
return serve(c, prefix, filepath)
}
// 此方法允许模块以验证的方式提供二进制文件。参数与StaticVersionbasedCacheInvalidator.Serve相同,只是在参数列表前面加上了模块名称。
func (c StaticVersionbasedCacheInvalidator) ServeModule(moduleName, prefix, filepath string) revel.Result {
// 修复#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)
}
// 此方法允许StaticVersionbasedCacheInvalidator以经过验证的方式提供应用程序文件。
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))
// 验证请求文件路径是否在应用程序的访问范围内
if !strings.HasPrefix(fname, basePathPrefix) {
revel.WARN.Printf("尝试读取基本路径之外的文件:%s", fname)
return c.NotFound("")
}
// 验证文件路径是否可访问
finfo, err := os.Stat(fname)
if err != nil {
if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
revel.WARN.Printf("文件未找到(%s):%s ", fname, err)
return c.NotFound("文件未找到")
}
revel.ERROR.Printf("尝试获取文件信息时出错'%s':%s", fname, err)
return c.RenderError(err)
}
// 不允许目录列表
if finfo.Mode().IsDir() {
revel.WARN.Printf("尝试列出目录:%s", fname)
return c.Forbidden("不允许列出目录")
}
// 打开请求文件路径
file, err := os.Open(fname)
if err != nil {
if os.IsNotExist(err) {
revel.WARN.Printf("文件未找到(%s):%s ", fname, err)
return c.NotFound("文件未找到")
}
revel.ERROR.Printf("打开'%s'时出错:%s", fname, err)
return c.RenderError(err)
}
return c.RenderFile(file, revel.Inline)
}
英文:
I did the things that Colin Nicholson suggested but I also created a controller called staticversionbasedcacheinvalidator and placed it in the controller folder. You can find it below. It allows you to ignore the first part of the request string allowing you to use a wildcard path to your public folder. For instance I use this route config row
GET /public/*filepath StaticVersionbasedCacheInvalidator.Serve("public")
I then added the {{.resourceVersion}} in my route instead of as a query param.
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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论