Предварительная обработка SSI включает в себя процесс сборки сайта, экспорт из SVN - PullRequest
0 голосов
/ 21 июля 2010

У меня есть сайт, который использует простые включения на стороне сервера для добавления верхнего и нижнего колонтитула на некоторые статические HTML-страницы:

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

Недостатком является то, что IIS не может кэшировать страницы с SSI (или, более конкретно, он не позволяет браузерам кэшировать страницы - нет ETag или Last-Modified заголовков). Поскольку эти страницы изменяются нечасто, а включаемые файлы изменяются редко - это нежелательно с точки зрения производительности.

Весь мой сайт находится в хранилище Subversion. Я бы хотел настроить процесс развертывания, в котором мой сайт экспортируется из svn, обрабатываются директивы SSI во всех * .html-файлах, а обработанные файлы помещаются на мой рабочий сервер.

Кроме того, было бы очень здорово, если бы только файлы, которые были изменены в svn с момента последнего развертывания, могли быть экспортированы, обработаны и перемещены на место & ndash; нет смысла перезаписывать каждый файл, когда изменился только один; это значительно ускорит процесс.

Итак:

Существует ли утилита, которая будет обрабатывать директивы SSI в файле и записывать результат обратно?

Ответы [ 2 ]

2 голосов
/ 22 июля 2010

В итоге я решил просто развернуть свое собственное консольное приложение на C #, чтобы автоматизировать весь процесс сборки моего сайта. Как это всегда происходит, сборка заняла гораздо больше времени, чем мне бы хотелось, но теперь одна команда переводит мой сайт прямо из Subversion в производство, поэтому я очень счастлив.

Во-первых, я использовал фантастический класс Mono.Options для обработки аргументов командной строки. Это единственный файл .cs , который вы можете просто добавить в свой проект и быть готовым к работе.

Я хотел получить аргументы командной строки, чтобы & mdash; например, & mdash; я мог указать, какую ревизию развернуть (если я не хотел 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 для каждого файла * .html в папке temp. .

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--> s и удалите временный файл. Этот процесс завершается менее чем за секунду.

Еще одно, что я делаю, - минимизирую все мои скрипты и собираю их в один файл .js. Это позволяет мне поддерживать управляемость в процессе разработки (классы логически организованы в файлы), но оптимизировать все для производства. (Поскольку двадцать <script src="..."> тегов очень плохо .)

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 , который является jar-файлом Java. Вы можете заменить свой компрессор по вашему выбору здесь.

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, в то время как я перемещаю все файлы в моей временной папке в фактическую папку сайта 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();

И мы закончили. Уф!

Есть несколько деталей, которые я пропустил в приведенном выше коде - например, я удаляю все файлы * .psd в моей папке с изображениями и записываю сообщение об авторских правах в мой скомпилированный файл js - но заполнение пробелов - это наполовину удовольствие , верно!?

Очевидно, что часть кода, который я представил здесь, применима только к конкретным проектным решениям, которые я принял для своего сайта, но я надеюсь, что некоторые из вас найдут [части] этого полезными, если вы решите создать автоматизированный процесс развертывания - который, для протокола, я настоятельно рекомендую. Очень приятно иметь возможность зафиксировать мои изменения в svn, ssh на моем производственном сервере, запустить это и все готово.

1 голос
/ 21 июля 2010

Utility?не то, чтобы я знал, но вы могли бы довольно легко закинуть один в 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);
...