我有一个网站,它使用简单的服务器端包含来在一些静态HTML页面上引入页眉和页脚:
<!--#include virtual="/_top.html"-->
...
<!--#include virtual="/_bot.html"-->
缺点是IIS无法缓存SSIed页面(或者更具体地说,它不允许浏览器缓存页面 - 没有ETag
或Last-Modified
标头。由于这些页面不经常更改 - 并且包含文件很少更改 - 从性能角度来看,这是不可取的。
我的整个网站都在Subversion存储库中。我想设置一个部署过程,我的站点从svn导出,所有* .html文件中的SSI指令都被处理,处理过的文件被放入我的生产服务器上。
此外,如果只有自上次部署以来svn中已更改的文件可以导出,处理并移动到位,那将是非常好的 - 当只有一个文件发生更改时,覆盖每个文件都没有意义;这将大大加快这一进程。
所以:
是否有一个实用程序可以处理文件中的SSI指令并将结果写回来?
答案 0 :(得分:2)
最后,我决定推出自己的C#控制台应用程序来自动完成整个网站构建过程。由于这些事情总是如此,所以整理起来比我想要的要花费更长的时间,但是现在有一个命令将我的网站从Subversion直接带到生产中,所以我很高兴。
首先,我使用了奇妙的Mono.Options类来处理抓取命令行参数。这是a single .cs file你可以添加到你的项目中并且好好去。
我想要命令行参数 - 例如 - 我可以指定部署哪个版本(如果我不想要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修订。
由于我有一小组已知的包含文件,我决定只将它们加载到内存中一次,在需要时合并修订版号,然后在每个* .html文件中执行一个简单的string.Replace
临时文件夹。
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 + ".");
}
在我的构建过程中,缩小步骤实际上发生在处理包含之前(因为我将<script>
标记的数量减少到模板中的一个)。
最后,我将Microsoft.Web.Administration.ServerManager
暂时停止IIS,同时将temp文件夹中的所有文件移动到实际的prouction站点文件夹中。 (我希望在网站处于半部署状态时防止任何怪异。)
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的更改提交到我的生产服务器,运行它并完成它是非常好的。
答案 1 :(得分:1)
实用程序?不是我所知道的,但是你可以很容易地将其中的一个打起来。
对我来说这很快就很脏。
#!/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);