代码之家  ›  专栏  ›  技术社区  ›  josh3736

预处理ssi包括站点内构建过程、从SVN导出

  •  0
  • josh3736  · 技术社区  · 15 年前

    我有一个网站,它使用简单的服务器端includes在一些静态HTML页面上拉入页眉和页脚:

     <!--#include virtual="/_top.html"-->
     ...
     <!--#include virtual="/_bot.html"-->
    

    缺点是,IIS无法缓存ssied页(或者更具体地说,它不允许浏览器缓存该页–否 ETag 也不 Last-Modified 标头)。由于这些页面很少更改,并且包含文件很少更改,因此从性能角度来看,这是不可取的。

    我的整个站点都在一个Subversion存储库中。我想建立一个部署过程,在这个过程中,我的站点从SVN导出,所有*.html文件中的ssi指令都会被处理,处理后的文件会放到我的生产服务器上的相应位置。

    此外,如果只导出、处理和移动自上次部署以来在SVN中已更改的文件,那将是非常棒的——当只有一个文件发生更改时,没有必要覆盖每个文件;这将大大加快进程。

    所以:

    是否有一个实用程序可以处理文件中的ssi指令并将结果写回?

    2 回复  |  直到 15 年前
        1
  •  2
  •   josh3736    15 年前

    最后,我决定推出自己的C控制台应用程序来自动化整个站点构建过程。正如这些事情总是发生的那样,整合起来要比我希望的时间长得多,但是现在有一个命令直接把我的站点从颠覆变成了生产,所以我很高兴。

    首先,我用了 Mono.Options 类来处理抓取命令行参数。它是 a single .cs file 你可以添加到你的项目中去,并且很好的去做。

    我需要命令行参数,例如,我可以指定要部署的修订版(如果我不需要头部的话)。

    using Mono.Options;
    
    int rev = 0;
    OptionSet opt = new OptionSet();
    opt.Add("r=|revison=", "Revision to deploy (defaults to HEAD).", v => rev = int.Parse(v));
    

    一旦你的选择都设置好了,你甚至可以 opt.WriteOptionDescriptions(Console.Out); 打印出使用帮助消息。

    我抓起 SharpSvn 处理SVN导出;实际上比预期的要容易得多。

    using SharpSvn;
    
    SvnClient svn = new SvnClient();
    svn.Authentication.DefaultCredentials = new System.Net.NetworkCredential("account", "password");
    // Since this is an internal-only tool, I'm not too worried about just
    // hardcoding the credentials of an account with read-only access.
    SvnExportArgs arg = new SvnExportArgs();
    arg.Revision = rev > 0 ? new SvnRevision(rev) : new SvnRevision(SvnRevisionType.Head);
    svn.Export(new SvnUriTarget("<repository URL>"), workDir, arg);
    

    …并将整个站点导出到临时文件夹( workDir )因为我还想将SVN修订版打印到站点,所以我抓取了当前的存储库修订版(如果没有指定修订版)。

    SvnInfoEventArgs ifo;
    svn.GetInfo(new SvnUriTarget("<repo URL>"), out ifo);
    

    现在 ifo.Revision 将进行头部修正。

    因为我有一组已知的include文件,所以我决定只将它们加载到内存中一次,在需要时合并到修订号中,然后做一个简单的 string.Replace 在temp文件夹中的每个*.html文件上。

    string[] files = Directory.GetFiles(workDir, "*.html", SearchOption.AllDirectories);
    foreach (string ff in files)
    {
        File.Move(ff, workDir + "_.tmp");
        using (StreamReader reader = new StreamReader(workDir + "_.tmp"))
        {
            using (StreamWriter writer = new StreamWriter(ff))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    line = line.Replace("<!--#include virtual=\"/top.html\" -->", top);
                    // <etc..>
                    writer.WriteLine(line);
                }
            }
        }
        File.Delete(workDir + "_.tmp");
    }
    

    将未处理的文件移到临时位置,打开 StreamWriter 在原始文件中,读取临时文件,替换已知的 <!--#include--> 然后删除临时文件。这个过程在一秒钟内完成。

    我做的另一件事是缩小所有脚本并将它们编译成一个.js文件。这允许我在开发中保持事情的可管理性(类在逻辑上组织成文件),但是为生产优化一切。(因为有二十个 <script src="..."> 标签是 Very Bad )

    这个 HTML Agility Pack 对这项任务非常有用。我只是将页面模板加载到 HtmlDocument 并将需要缩小的脚本位置提取出来,合并成一个单独的文件。(脚本目录中其余的*.js文件只加载到某些页中,因此我不希望它们组合到主文件中。)

    using HtmlAgilityPack;
    
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(top);
    
    using (StreamWriter writer = new StreamWriter(workDir + "js\\compiled.js"))
    {
    foreach (HtmlNode script in doc.DocumentNode.SelectNodes("//script"))
    {
        string js = script.Attributes["src"].Value;
        script.Remove();
    
        js = js.Replace("/js/", workDir + "js/"); // In my site, all scripts are located in the /js folder.
        js = js.Replace("/", "\\");
        string mini;
        if (js.IndexOf(".min.") > 0) // It's already minified.
        {
            mini = js;
        }
        else
        {
            mini = workDir + "_.tmp";
            MinifyScript(js, mini);
        }
    
        using (StreamReader sr = new StreamReader(mini)) writer.WriteLine(sr.ReadToEnd());
    
        File.Delete(js);
        File.Delete(workDir + "_.tmp");
    
    }
    }
    

    然后查找要缩小的其余脚本:

    string[] jsfolder = Directory.GetFiles(workDir + "js\\", "*.js");
    foreach (string js in jsfolder)
    {
        if (js.IndexOf("\\compiled.js") > 0) continue; // The compiled js file from above will be in the folder; we want to ignore it.
        MinifyScript(js, js);
    }
    

    对于实际的缩小,我只是使用 YUI Compressor ,这是一个Java jar。你可以在这里替换你选择的压缩机。

    static void MinifyScript(string input, string output)
    {
        System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo(@"C:\Program Files (x86)\Java\jre6\bin\java.exe", "-jar mini.jar -o " + output + " " + input);
        si.RedirectStandardOutput = true;
        si.UseShellExecute = false;
        System.Diagnostics.Process proc = System.Diagnostics.Process.Start(si);
        proc.WaitForExit();
        if (proc.ExitCode != 0) throw new Exception("Error compiling " + input + ".");
    }
    

    在我的构建过程中,缩小步骤实际上发生在处理include之前(因为我减少了 <script> 标记到模板中的一个)。

    最后,我用 Microsoft.Web.Administration.ServerManager 在我将temp文件夹中的所有文件移动到实际的production站点文件夹时临时停止IIS。(我想防止站点处于半部署状态时出现任何奇怪的情况。)

    using Microsoft.Web.Administration; // Assembly is found in %windir%\System32\inetsrv\Microsoft.Web.Administration.dll
    
    ServerManager iis = new ServerManager();
    if (stopIIS) iis.Sites[site].Stop(); // bool stopIIS and string site are set by command line option (and have hardcoded defaults).
    
    string[] files = Directory.GetFiles(workDir, "*");
    foreach (string file in files)
    {
        string name = file.Substring(file.LastIndexOf("\\") + 1);
        if (name == "web.config") continue; // The web.config for production is different from that used in development and kept in svn.
        try
        {
            File.Delete(dest + name); // string dest is a command line option (and has a hard-coded default).
        }
        catch (Exception ex) { }
        File.Move(file, dest + name);
    }
    string[] dirs = Directory.GetDirectories(workDir);
    foreach (string dir in dirs)
    {
        string name = dir.Substring(dir.LastIndexOf("\\") + 1);
        if (name == "dyn") continue; // A folder I want to ignore.
        try
        {
            Directory.Delete(dest + name, true);
        }
        catch (DirectoryNotFoundException ex) { }
        Directory.Move(dir, dest + name);
    }
    
    if (stopIIS) iis.Sites[site].Start();
    

    我们结束了。唷!

    在上面的代码中我省略了一些细节,例如,我删除了我的图像目录中的所有*.psd文件,并将版权信息写入我编译的JS文件中,但是填充空白部分是乐趣的一半,对吧!?

    显然,我在这里介绍的一些代码只适用于我为我的站点所做的特定设计决策,但我希望你们中的一些人会发现,如果你决定构建一个自动部署过程,这将有帮助[部分],我强烈推荐。很高兴能够将我的更改提交到SVN、SSH到我的生产服务器,运行这个,然后完成。

        2
  •  1
  •   IanNorton    15 年前

    效用?我不知道,但是你可以很容易地用Perl把它敲出来。

    这对我来说既快又脏。

    #!/usr/bin/env perl
    
    my $inputfile = $ARGV[0];
    my $outputfile = $ARGV[1];
    
    open(IN,"<$inputfile") or die ("$! $inputfile");
    open(OUT,">$outputfile") or die ("$! $outputfile");
    
    while (my $line = <IN> ){
            if ( $line =~/(<!--#include virtual="([\/a-zA-Z0-9_\-\.]+)"-->)/ ){
                    my $all = $1;
                    my $file = $2;
    
                    my $sep = "\\";
                    if ( $^O =~/linux|bsd/ ){
                            $sep = "/";
                    }
                    my @path = split("/",$file);
                    $file = join($sep,@path);
    
                    open(GET,"<.$file") or die "$! $file";
                    my $content = "";
                    while( my $cline = <GET> ){
                            $content .= $cline;
                    }
                    close(GET);
                    $line =~ s/$all/$content/;
            }
            print OUT $line;
    }
    
    close(OUT);
    close(IN);