0

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

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

缺点是 IIS 不能缓存 SSIed 页面(或者更具体地说,它不允许浏览器缓存页面 - 没有ETag也没有Last-Modified标题)。由于这些页面很少更改——而包含文件也很少更改——从性能的角度来看这是不可取的。

我的整个站点都在 Subversion 存储库中。我很想建立一个部署过程,从 svn 导出我的站点,处理所有 *.html 文件中的 SSI 指令,并将处理后的文件放入我的生产服务器上。

此外,如果只有自上次部署以来在 svn 中更改过的文件可以导出、处理并移入到位,那将是非常棒的——当只有一个文件发生更改时,覆盖每个文件是没有意义的;这将大大加快这一进程。

所以:

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

4

2 回答 2

3

最后,我决定只使用我自己的 C# 控制台应用程序来自动化我的整个站点构建过程。就像这些事情一样,组装起来花费的时间比我希望的要长得多,但是现在一个命令将我的站点直接从 Subversion 带到生产,所以我很高兴。

首先,我使用了奇妙的Mono.Options类来处理抓取命令行参数。这是一个单独的 .cs 文件,您只需将其添加到您的项目中即可。

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

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将进行 HEAD 修订。

因为我有一小部分已知的包含文件,所以我决定只将它们加载到内存中,在需要的地方合并修订号,然后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在原文件上打开a,读入临时文件,替换已知<!--#include-->的s,删除临时文件。这个过程在一秒钟内完成。

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

HTML Agility Pack对于这项任务非常有用。我只是将我的页面模板加载到 aHtmlDocument中,然后提取需要缩小并组合到单个文件中的脚本的位置。(我的脚本目录中的 *.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 + ".");
}

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

最后,Microsoft.Web.Administration.ServerManager当我将临时文件夹中的所有文件移动到实际的生产站点文件夹中时,我会暂时停止 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();

我们完成了。呸!

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

显然,我在此处提供的一些代码仅适用于我为我的站点做出的特定设计决策,但我希望你们中的一些人会发现其中的 [部分] 如果您决定构建一个自动部署过程——作为记录,我强烈推荐。能够将我的更改提交到 svn、ssh 到我的生产服务器,运行它并完成,这真是太好了。

于 2010-07-22T05:45:27.583 回答
1

效用?不是我所知道的,但你可以很容易地在 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);
于 2010-07-20T23:24:11.823 回答