草庐IT

javascript - 对外部生成的静态内容进行指纹识别(ASP.NET + browserify)

coder 2024-07-16 原文

Node browserify在构建模块化 js 应用程序时非常棒。如果 gulp 也是设置的一部分,工作流 is further enhanced管理和解决依赖关系,适本地捆绑,使用 sourcemaps 进行 uglify,auto-polyfill,jshint,测试...这对于 css 以及预处理,自动前缀,linting,嵌入非常方便资源和生成文档。

TL;DR:通过 npm/bower,您可以访问广泛的前端库生态系统,使 nodejs 非常适合构建(不一定是服务!)客户端代码。事实上,将它用于客户端代码非常棒,npmbowergrunt/gulp 将在 VS 2015 中开箱即用。与此同时,我们设置了一个 gulp 任务来运行预构建并编写 dist js/css(捆绑输出)。

使用指纹 url 引用外部静态内容的好方法是什么?从长远来看,理想情况下,我们能够完全分离客户端内容,这样它就可以独立构建并部署到 CDN,而无需还必须构建应用程序的其余部分。

最佳答案

CSS 问题

由于 CSS 引用图像的相对 url,这些 url 也可能会发生变化,因此您需要在启动应用程序之前计算大量哈希计算,这会减慢签名 url 的生成速度。事实证明,编写代码来跟踪上次修改日期不适用于 CSS 图像 url。因此,如果 css 中引用的任何图像发生更改,则 css 也必须更改。

像 jquery-1.11.1.js 这样的独立文件版本控制问题

首先它破坏了源代码版本控制,Git 或任何版本控制会将 app-script-1.11.js 和 app-script-1.12.js 识别为两个不同的文件,将难以维护历史。

对于 jquery,它会在构建库时正常工作,并且大多数情况下您不会在页面上包含资源时更改它,但是在构建应用程序时,我们将有许多 JavaScript 文件并且更改版本将需要更改每个页面,但是,单个包含文件可能会做到这一点,但要考虑大量的 css 和大量图像。

将上次更新日期缓存为 URL 前缀

所以我们不得不想出像/cached/lastupdate/这样的静态内容的版本控制,这只不过是静态 Assets 的url前缀。 lastupdate 只是请求文件的最后更新日期时间。如果文件在应用程序范围内被修改,还有一个观察器会刷新缓存键。

最简单的方法之一是在 URL 中使用版本 key 。

在应用设置中定义版本如下

 <appSettings>
     <add key="CDNHost" value="cdn1111.cloudfront.net"/>
 </appSettings>

 // Route configuration

 // set CDN if you have
 string cdnHost = WebConfigrationManager.AppSettings["CDNHost"];
 if(!string.IsEmpty(cdnHost)){
     CachedRoute.CDNHost = cdnHost;
 }

 // get assembly build information
 string version = typeof(RouteConfig).Assembly.GetName().Version.ToString();

 CachedRoute.CORSOrigins = "*";
 CachedRoute.Register(routes, TimeSpam.FromDays(30), version);

现在在每个页面上,引用您的静态内容,

 <script src="@CachedRoute.CachedUrl("/scripts/jquery-1.11.1.js")"></script>

在呈现时,您的页面将呈现为(无 CDN)

 <script src="/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js"></script>

以CDN为

 <script 
      src="//cdn111.cloudfront.net/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js">
 </script>

将版本放在 URL 路径而不是查询字符串中可以使 CDN 性能更好,因为查询字符串可以在 CDN 配置中忽略(通常是默认情况)。

CachedRoute 类来自 https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/CachedRoute.cs

public class CachedRoute : HttpTaskAsyncHandler, IRouteHandler
{

    private CachedRoute()
    {
        // only one per app..

    }

    private string Prefix { get; set; }

    public static string Version { get; private set; }

    private TimeSpan MaxAge { get; set; }

    public static string CORSOrigins { get; set; }
    //private static CachedRoute Instance;

    public static void Register(
        RouteCollection routes,
        TimeSpan? maxAge = null,
        string version = null)
    {
        CachedRoute sc = new CachedRoute();
        sc.MaxAge = maxAge == null ? TimeSpan.FromDays(30) : maxAge.Value;

        if (string.IsNullOrWhiteSpace(version))
        {
            version = WebConfigurationManager.AppSettings["Static-Content-Version"];
            if (string.IsNullOrWhiteSpace(version))
            {
                version = Assembly.GetCallingAssembly().GetName().Version.ToString();
            }
        }

        Version = version;

        var route = new Route("cached/{version}/{*name}", sc);
        route.Defaults = new RouteValueDictionary();
        route.Defaults["version"] = "1";
        routes.Add(route);
    }

    public override bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public static string CDNHost { get; set; }

    public override bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public class CachedFileInfo
    {

        public string Version { get; set; }

        public string FilePath { get; set; }

        public CachedFileInfo(string path)
        {
            path = HttpContext.Current.Server.MapPath(path);

            FilePath = path;

            //Watch();

            Update(null, null);
        }

        private void Watch()
        {
            System.IO.FileSystemWatcher fs = new FileSystemWatcher(FilePath);
            fs.Changed += Update;
            fs.Deleted += Update;
            fs.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName;
        }

        private void Update(object sender, FileSystemEventArgs e)
        {
            FileInfo f = new FileInfo(FilePath);
            if (f.Exists)
            {
                Version = f.LastWriteTimeUtc.ToString("yyyy-MM-dd-hh-mm-ss-FFFF");
            }
            else
            {
                Version = "null";
            }
        }


    }

    private static ConcurrentDictionary<string, CachedFileInfo> CacheItems = new ConcurrentDictionary<string, CachedFileInfo>();

    public static HtmlString CachedUrl(string p)
    {
        //if (!Enabled)
        //    return new HtmlString(p);
        if (!p.StartsWith("/"))
            throw new InvalidOperationException("Please provide full path starting with /");

        string v = Version;

        var cv = CacheItems.GetOrAdd(p, k => new CachedFileInfo(k));
        v = cv.Version;

        if (CDNHost != null)
        {
            return new HtmlString("//" + CDNHost + "/cached/" + v + p);
        }
        return new HtmlString("/cached/" + v + p);
    }

    public override async Task ProcessRequestAsync(HttpContext context)
    {
        var Response = context.Response;
        Response.Cache.SetCacheability(HttpCacheability.Public);
        Response.Cache.SetMaxAge(MaxAge);
        Response.Cache.SetExpires(DateTime.Now.Add(MaxAge));

        if (CORSOrigins != null)
        {
            Response.Headers.Add("Access-Control-Allow-Origin", CORSOrigins);
        }


        string FilePath = context.Items["FilePath"] as string;

        var file = new FileInfo(context.Server.MapPath("/" + FilePath));
        if (!file.Exists)
        {
            throw new FileNotFoundException(file.FullName);
        }

        Response.ContentType = MimeMapping.GetMimeMapping(file.FullName);

        using (var fs = file.OpenRead())
        {
            await fs.CopyToAsync(Response.OutputStream);
        }
    }

    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        //FilePath = requestContext.RouteData.GetRequiredString("name");
        requestContext.HttpContext.Items["FilePath"] = requestContext.RouteData.GetRequiredString("name");
        return (IHttpHandler)this;
    }
}

使用文件修改时间而不是版本

    public static HtmlString CachedUrl(string p)
    {
        if (!p.StartsWith("/"))
            throw new InvalidOperationException("Please provide full path starting with /");
        var ft = (new System.IO.FileInfo(Server.MapPath(p)).LastModified;
        return new HtmlString(cdnPrefix + "/cached/" + ft.Ticks + p);
    }

这会保留基于上次修改的版本,但这会增加每次请求时对 System.IO.FileInfo 的调用,但是您可以创建另一个字典来缓存此信息并观察更改,但它是很多工作。

关于javascript - 对外部生成的静态内容进行指纹识别(ASP.NET + browserify),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29725764/

有关javascript - 对外部生成的静态内容进行指纹识别(ASP.NET + browserify)的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  5. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  6. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐