服务器端包含
大多数网站都包含大量静态 HTML 文件,并用一些图像进行美化。更高级的站点使用一个或多个 CGI 程序、HTTP Cookie、数据库后端以及我们在前几个月讨论过的其他主题。
所有这些技术都涉及到在 Web 服务器上创建至少一个新进程。如果我们有兴趣尽可能降低服务器的负载,我们应该避免创建不必要的进程。我并不是建议我们删除 CGI 程序,但我只是说,在某些时候,CGI 可能显得有些大材小用。
例如,有时您可以使用服务器端包含(也称为“SSI”)来完成您需要的一切。SSI 在效率和复杂性之间提供了良好的平衡。即使您从未见过它们,服务器端包含也很容易理解。实际上,它们通常非常适合让非程序员体验动态页面输出,而不会让他们因与实际编程相关的问题感到困惑。以下是它们外观的一个示例
<!--#printenv -->
是的,这看起来像 HTML 注释。但与 HTML 注释(它未经修改地传递到用户的浏览器)不同,SSI 在文件发送之前由服务器解析。将 SSI 视为由 Web 服务器扩展的宏可能更容易理解。
每个服务器端包含都以“打开注释”字符 ("<!--")、紧跟在两个破折号后面的井号 ("#")、您希望评估的命令名称、空格、零个或多个属性值对(后跟空格)以及最后的“关闭注释”字符 ("-->") 开头。因此,不带任何参数并返回环境变量列表的 SSI 命令 #printenv 可以包含在以下文件中
<HTML> <Head><Title>Testing</Title></Head> <Body> <H1>Testing</H1> <!--#printenv --> </Body> </HTML>
在将上述文档发送给用户之前,它将被扩展为类似于列表 1 的内容。请注意,我们简单的 SSI 如何被环境变量及其值的列表所取代。虽然服务器端包含没有标准,但服务器共享一个通用的 SSI 列表,每个服务器都定义新的 SSI。
本月,我们将研究服务器端包含——从配置您的服务器以允许它们,到您可能希望在您的网站上使用的一些不同的 SSI 命令,再到在您的网站上使用 SSI 的多种方式。
在您可以实际创建包含和使用服务器端包含的页面之前,您必须配置您的 Web 服务器以允许它们。如果您是从头开始编译 Apache,请确保将处理 SSI 的模块 mod_include 编译到服务器中。(默认情况下,应该已编译。)
即使 mod_include 存在,您也必须配置几个额外的项目。首先,您必须通过在服务器配置文件中使用 Options 指令来告诉 Apache 您希望允许 SSI。
在我的 Red Hat 4.2 系统上,包含此信息的文件名为 /etc/httpd/conf/access.conf,该行看起来像
Options Indexes FollowSymLinks Includes
这表明我已经决定激活 Apache 的三个选项——Indexes(如果用户请求目录而不是文件,则生成目录列表)、FollowSymLinks(告诉 Apache 跟随符号链接,而不是忽略它们)和 Includes(表示服务器端包含应该处于活动状态)。
如果您希望阻止用户使用 #exec(它允许他们运行任意外部程序),请将 Includes 替换为 IncludesNOEXEC,如下所示
Options Indexes FollowSymLinks IncludesNOEXEC
如果您希望仅在一个目录中允许 SSI,请修改配置文件,使 Options 行出现在 <Directory> 和 </Directory> 行之间。例如,如果我们只希望 /ssi 目录中的文件允许服务器端包含,我们可以给出这个
<Directory /home/httpd/html/ssi/> Options Indexes FollowSymLinks Includes </Directory>Apache 文档还描述了如何拥有多个 <Directory> 块。如果您的服务器托管多个子站点,您可以使用它们为不同的子站点定义不同的服务,具体取决于谁在运行它们、他们支付了多少费用或您希望实施什么策略。
我们通过在 srm.conf 配置文件中使用两个附加指令来指示哪些文件可能包含 SSI。第一个指令 AddType 指示当服务器返回带有 .shtml 后缀的文档时,应发送哪种内容类型标头。浏览器需要知道如何解释发送给它们的数据——毕竟,它可能是 JPEG 格式的图像、HTML 格式的文本或完全未格式化的数据。因此,我们将以下行添加到我们的配置文件中
AddType text/html .shtml
然而,这还不够;我们还希望文件在输出时由服务器解析。这通过指示 Apache 对所有以 “.shtml” 作为后缀的文件使用 “server-parsed” 处理程序来完成。我们可以通过将以下行添加到 srm.conf 文件中来做到这一点
AddHandler server-parsed .shtml然后重启服务器。
您可能想知道为什么我们必须使用 .shtml,而不是 .html。为什么不为 .html 添加一个 server-parsed 处理程序,并免除单独的扩展名?
答案与服务器效率有关。SSI 的计算开销比 CGI 程序小,但 小 并不等于 没有。如果我们告诉 Apache 所有 HTML 文件都可能包含 SSI,Apache 将不得不检查每个 .html 文件,这可能会显着降低速度。因此,在许多站点上,习惯于将文件分为两类——仅包含 HTML 的文件(带有 .html 后缀)和包含 HTML 加服务器端包含的文件(带有 .shtml 后缀)。对用户来说,唯一的区别是他们最终看到的文件扩展名,因为 SSI 在发送到用户的浏览器之前会被其结果替换。两者都以 “text/html” 的内容类型发送,因为我们的 AddHandler 指令指示 Apache 这样做。
现在我们已经告诉 Apache 如何处理 SSI,我们可以开始在我们的文件中使用它们了。再次强调,只有那些带有 .shtml 后缀的文件才会被 Apache 的服务器端包含机制解析,因此请确保将您的文件保存为 .shtml 后缀,而不是 .html 后缀。
正如我们上面看到的,服务器端包含看起来像 HTML 注释。这意味着任何未被 Apache 解析的服务器端包含对于最终用户来说都是不可见的。即使 SSI 未经修改地传递到用户的浏览器(由于错误或配置错误的服务器),用户看到的内容也不会有任何问题或异常。
SSI 最常见的用途之一是指示文档何时被修改。当页面定期更新时,这非常有用;一个典型的例子可能是新闻服务或活动日历。
这是一个指示其最新修改日期的文件。我们使用 SSI #echo 命令打印日期,该命令打印 SSI 变量的值。SSI 变量包括环境变量,以及 Apache 定义的其他几个变量。在本例中,我们查看 LAST_MODIFIED,它包含文件更改的日期和时间
<HTML> <Head><Title>I was modified</Title></Head> <Body> <H1>I was modified</H1> <P>I was last modified on <!--#echo var="LAST_MODIFIED" --> </P> </Body> </HTML>
如果您到目前为止都遵循了说明,那么检索此页面应该会指示它上次修改的时间。请记住将文件保存为 .shtml 扩展名——在编写本专栏时,我花了一些时间试图弄清楚为什么某个特定的 SSI 不起作用。事实证明,问题出在文件扩展名上,而不是我的服务器上。
#echo 打印的日期对于程序员来说可能看起来不错,但对于大多数人来说有点令人生畏。非程序员更喜欢稍微熟悉的日期和时间格式。
幸运的是,Apache 允许我们修改日期的显示方式。C 程序员可能熟悉 strftime 函数,该函数允许通过使用以百分号 (%) 开头的字符来创建许多不同的时间和日期字符串。例如,“%A” 给我们星期几的名称,“%B” 返回月份的名称,“%d” 给出月份中的日期,“%Y” 返回四位数的年份。因此,通过指定 “%A, %d %B %Y”,我们可以得到一个类似于 “Wednesday, February 22 1998” 的字符串。
以下是一个将日期设置为美国格式的示例,首先使用 “config” SSI,然后使用 “echo” SSI 以我们的新格式显示结果。
<HTML> <Head><Title>Testing</Title></Head> <Body> <H1>Testing</H1> <!--#config timefmt="%m/%d/%y" --> <P>In America, I was changed on <!--#echo var="LAST_MODIFIED"></P> </Body> </HTML>
我们已经可以看到服务器端包含是如何定义和使用的模式。它们由一个关键字和一个或多个属性值对组成,就像本例中一样
<HTML> <Head><Title>Testing</Title></Head> <Body> <H1>Testing</H1> <!--#config timefmt="%m/%d/%y" --> <P>In America, I was changed on <!--#echo var="LAST_MODIFIED" --></P> <!--#config timefmt="%d/%m/%y" --> <P>In Europe, I was changed on <!--#echo var="LAST_MODIFIED" --></P> </Body> </HTML>
如果您正在运行一个持续更新的新闻服务,打印文件的修改日期很好,但它没有太多其他应用程序。相比之下,我发现文件包含 SSI 函数在设计站点时非常有用。
语法非常简单,您可以从以下示例中看到
<HTML> <Head><Title>A basic template</Title></Head> <!--#include virtual="/fragments/header.htmlf" --> <P>This is the text of my page, sandwiched between two server-side includes.</P> <!--#include virtual="/fragments/footer.htmlf" --> </Body> </HTML>
在这里,我们使用 #include,带有一个名为 “virtual” 的参数。Apache 将此 SSI 的内容替换为命名文件的内容。这可能看起来不是很有用,但请考虑一下,这使得创建外观统一的站点变得多么容易。header.htmlf 片段可以包含标准的 <Body> 标签,定义文本和背景颜色,以及在页面顶部放置一个菜单栏。同样,footer.htmlf 片段可以包含版权声明、较小的菜单栏或有关服务器的信息。
有什么优势?当您决定向菜单栏添加一个新按钮或当网站的赞助商搬到新地址时,您只需要修改一个文件。更改会自动传播到站点的其余部分。这比更改每个单独的页面更容易,也比使用 CGI 程序创建页面更有效。正如您可以通过将重复指令放入子例程来避免编程错误一样,您也可以通过将重复信息放入使用 “include” SSI 导入的 HTML 片段中来避免错别字和其他潜在问题。
如果您的站点使用 CGI 程序来创建动态页面,您可能会想使用 #include 将标准标头和页脚包含在程序的输出中。不幸的是,由于 CGI 程序和 SSI 使用不同的处理程序,因此无法实现这一点。如果您决定使用 HTML 片段作为标头和页脚,您可能需要定义一些可以包含在 CGI 程序中的简短子例程。
此外,由于 SSI (“server-parsed”) 和 CGI 程序 (“cgi-script”) 使用不同的处理程序,您无法在 CGI 程序的输出中包含服务器端包含并期望它们被解释。如果您决定使用 HTML 片段(如下所述)为您的站点创建统一的外观,那么您编写的任何 CGI 程序都将能够包含这些片段。如果您用 Perl 编写 CGI 程序,这样的子例程可能看起来像 列表 2。您的 CGI 程序将看起来像 列表 3。现在,当您更改 header.htmlf 或 footer.htmlf 时,服务器上的所有输出(来自 HTML 文件和 CGI 程序)都将立即反映这些更改。
如果您想知道,片段是逐字导入的,它们可能包含的任何 SSI 都作为 HTML 注释传递。假设我们将 header.htmlf 定义为以下两行片段
<P>This is the header.</P> <!--#printenv -->
如果直接通过 Apache 检索此片段,则 #printenv SSI 将打印当前的环境变量列表。但是,由于 header.htmlf 是通过 #include SSI 导入的,因此 #printenv 函数未经解释地发送到用户的浏览器。这可能看起来没有必要,直到您考虑到允许在包含的文件中使用 SSI 可能会导致无限循环或其他意外结果。
服务器端包含最近更有趣的新增功能之一是有限的编程语言,允许设置和测试变量。
设置变量非常简单;您可以使用以下语法来完成
<!--#set var="varname" value="value" -->
您可以使用 #echo(用于特定变量列表)或 #printenv(用于所有已定义的变量)查看结果,如以下示例所示
<HTML> <Head><Title>Setting variables</Title></Head> <!--#set var="pi" value="3.14159" --> <pre><!--#printenv --></pre> <P>pi = <!--#echo var="pi" --></P> <HR> <!--#set var="e" value="2.71828" --> <pre><!--#printenv --></pre> <P>e = <!--#echo var="e" --></P> </Body> </HTML>上面的示例还演示了 SSI 如何按照它们在文件中出现的顺序进行解释。每次设置变量后,#printenv 的输出都会发生变化。
当与 if-then 语句结合使用时,设置变量非常有用。这些语句可用于在 HTML 文件中创建条件文本,而无需使用 CGI 程序。语法相当简单,例如
<!--#if expr="$SERVER_PORT=80" --> <P>You are using server port 80</P> <!--#else --> <P>You are using a non-standard server port</P> <!--#endif -->
请注意,#if 语句中的变量名称必须以美元符号开头,就像 shell 脚本一样。#else 语句是可选的,但 #endif 是强制性的,表示条件文本的结尾。
您甚至可以使用正则表达式在变量中执行模式匹配,如下所示
<HTML> <Head><Title>Browser check</Title></Head> <!--#if expr="$HTTP_USER_AGENT = /^Mozilla/" --> <P>You are using Netscape</P> <!--#else --> <P>You are using another browser</P> <!--#endif --> </Body> </HTML>
如果 HTTP_USER_AGENT 的值(通常设置为标识用户浏览器的字符串)设置为
Mozilla/4.04 [en] (X11; I; Linux 2.0.30 i586; Nav)就像我的系统的情况一样,上面将评估为 “true”,因此打印第一个字符串。否则,它将打印第二个字符串。通过这种方式,您可以创建为每个浏览器定制的菜单。例如,您可以通过为 Lynx(仅文本浏览器)的用户提供不依赖于图像的单独菜单结构来简化他们的生活。
服务器端包含并不能解决所有问题——但有什么软件可以做到呢?相反,创建 SSI 是为了让非程序员能够创建动态输出。随着时间的推移,它们已经扩展到可以包含条件语句的程度,这是迈向实际编程的第一步。正如我们所看到的,程序员可以从 SSI 的许多功能中受益,尤其是在将简单信息包含在 HTML 页面中时,例如标准标头或文件的上次修改日期。
SSI 中还有许多其他可用的命令,包括 #exec,它允许您运行程序并将程序的输出合并到 HTML 页面中。(您还可以使用 #include 引入 CGI 程序的输出,即使您在 Apache 配置中使用 IncludesNOEXEC 而不是 Includes。)
但在某些情况下,这种简单的服务器端包含可能还不够。在接下来的几个月中,我们将研究几个软件包,这些软件包使服务器端包含的思想更进一步,在 HTML 文件中提供完整的编程语言,而无需 CGI 程序。
