Apache HTTP服务器 2.0版本
本文描述了一种简单的方法,
借助内容协商和mod_include
,
使Apache HTTP服务器能以客户端的本地语言返回一套自定义的出错信息。
使用SSI,可以让所有ErrorDocument
信息具有一致的风格和布局,
同时,由于所有布局信息都集中在单个文件中,使(改变图片、链接等)维护的工作量降到最低。
出错页面可以被不同的服务器甚至主机所共享,其原因在于, 所有相异的信息是在请求发生错误并返回出错页面时插入的。
其后,内容协商会根据客户端请求所传递的语言首选项, 对特定的出错信息选择恰当的语言(当今的浏览器通常都会在首选项菜单中指定其优先语言)。 如果对应于浏览器基本语言的出错信息版本不存在,则会选用第二语言或者默认的(权宜的)版本。
你可以完全自主地根据你个人口味(或者你的公司的规范)设计出错页面。 为作演示,我们提供一个简单而通用的出错页面草案,对一个假想的服务器, 我们假设所有的出错信息...
这是一个"document not found"信息的例子,在德语客户端上可能是这样的:
页面中诸如服务器管理员的信箱地址等链接,甚至提供服务的虚拟主机的名称和端口, 都是在"运行时刻"也就是在错误实际发生的时候插入的。
充分利用服务器的支持,可以使这个任务尽可能地简单化:
Options
,
可以启用最接近语言的选择功能(内容协商).LanguagePriority
指令,
可以定义一组默认的权宜的语言,以应对客户端浏览器根本没有指定首选语言的情况。mod_include
(并且由于安全原因而禁止CGI脚本的运行),
可以允许服务器阻塞出错信息,或者,以某些环境变量生成的页面(动态HTML)中的值,
甚至可以有条件地包含或剔除文本中的一些内容。AddHandler
和AddType
指令可以用于对
text/html类型的带有.shtml
后缀的所有文件自动地做SSI扩展。Alias
指令,可以使出错页面目录位于文档树以外,
虽然它也可以算文档树的一部分,但可以更合理地被视为服务器的一部分。<Directory>
配置段可以使对出错页面的"特殊"设置局部化,以避免于标准文档树的任何设置相冲突。src/main/http_protocol.c
包含了Apache标准信息),
必须在别名化的/errordocs
目录中有一个相应的出错页面
。注意,只需要定义页面的基准名称即可,
因为MultiViews选项会根据语言后缀和客户端首选项来选择最佳候选者。
对任何自定义页面处理范围以外的错误代码,
服务器会以标准方式进行处理(即, 一条英文的简单出错信息).AllowOverride
指令告诉apache无须在/errordocs
目录中寻找.htaccess文件: 可以换取些微速度提升。最终,httpd.conf
的配置形如:
LanguagePriority en fr de
Alias /errordocs /usr/local/apache/errordocs
<Directory /usr/local/apache/errordocs>
AllowOverride none
Options MultiViews IncludesNoExec FollowSymLinks
AddType text/html .shtml
<FilesMatch "\.shtml[.$]">
SetOutputFilter INCLUDES
</FilesMatch>
</Directory>
# "400 Bad Request",
ErrorDocument 400 /errordocs/400
# "401 Authorization Required",
ErrorDocument 401 /errordocs/401
# "403 Forbidden",
ErrorDocument 403 /errordocs/403
# "404 Not Found",
ErrorDocument 404 /errordocs/404
# "500 Internal Server Error",
ErrorDocument 500 /errordocs/500
出错信息所在的目录(上述的/usr/local/apache/errordocs/
)必须存在,
而且设置了适当的权限(对服务器的uid或gid是可读的且可执行的,而仅对管理员是可写的)。
通过定义MultiViews
选项,可以使服务器在被请求页面没有找到时,
自动在目录中搜索可以匹配的变种(参照语言和内容类型后缀)。
在配置中,我们以简单的(没有后缀的)出错数值来定义出错页面的名称。
这样,独立出错页面的名称是这样判断的(这里的403是一个例子, 可以把它看作任何出错页面的占位符):
errordocs/403.shtml.lang
,
并填入该语言的出错信息说明(见下述)。errordocs/403.shtml
,
通常是建立一个指向默认语言变种的符号连接(见下述)。把许多布局信息放在两个特殊的"包含文件"中, 可以使出错页面极端简单化。
布局文件之一定义了HTML页面的页眉和一个指向最终出错页面中图标的可改变的路径列表。
这些路径会被输出为一组SSI环境变量,并在其后用于特殊的"页脚"文件。
当前错误的标题(将嵌入在TITLE和H1标记中)是从主出错页面中通过变量title
传递来的。
通过修改这个文件,可以迅速改变所有生成的出错信息的布局 (利用SSI功能,可以根据当前虚拟主机甚至客户端域名的不同简单地定义不同的布局).
第二个布局文件决定了每个出错信息的页脚。在本例中,它显示了一个apache标志, 当前服务器时间,服务器版本信息和站点管理员的邮件地址。
为了明确其含义,页眉文件命名为head.shtml
,
因为它包含了服务器解析的内容,而没有特定语言的信息。
对每个语种,都存在一个页脚文件,并有一个指向默认语言的符号连接。
for English, French and German versions (default english)
foot.shtml.en,
foot.shtml.fr,
foot.shtml.de,
foot.shtml symlink to
foot.shtml.en
这两个文件分别使用<!--#include virtual="head" -->
和<!--#include virtual="foot" -->
包含到出错页面之中,
其余的处理工作则由mod_negotiation和mod_include负责。
该例的真正HTML实现见下表
所有准备工作完毕,现在要真正实现这些页面了,它们共享了一个简单的通用结构:
<!--#set var="title" value="error description title" -->
<!--#include virtual="head" -->
explanatory error text
<!--#include virtual="foot" -->
在列表一节中,有一个[400 Bad Request]的出错页面。 页面简单到这种程度使翻译或扩展不会有太多问题。
我已经设置了LanguagePriority,对尚无译本的语言还需要一个特殊的处理吗?
LanguagePriority指令是用于客户端没有指示任何语言优先级的情况的。 那么,如果客户端指定了一种我们尚未提供的语言, 或者它指定的若干种语言我们一个也不能提供,会怎样呢?
如果不做任何处理,Apache服务器通常会返回一个[406 no acceptable variant]错误, 并把可能的选择列举到客户端。但是,这时已经显示了出错信息, 而且,由于客户端必须先选择一个语种,则重要出错信息可能会丢失。
在这种情况下,定义一种缺省的语言似乎更容易处理 (比如, 把英语版本拷贝或者连接到一个缺省语言版本)。 由于,内容协商算法会倾向于选择"更专有的"变种,而不是"更通用的"变种, 因此,在标准内容协商不成功时,就会选择这些通用化的替代品。
这是完成这个动作的shell脚本(在errordocs/ 目录中执行):
for f in *.shtml.en
do
ln -s $f `basename $f .en`
done
在Apache-1.3中,还可以将ErrorDocument
机制用于代理出错信息
(在早期版本中,返回的是固定的预定义的出错信息).
大多数代理错误返回的是错误代码[500 Internal Server Error].
要了解一个特定的错误是代理错误还是某种其他服务器错误,以及其原因,
可以检查新的CGI环境变量ERROR_NOTES
的值:
如果是由代理错误引起的,则此变量中含有HTML格式的实际代理错误信息。
以下片断演示了如何在出错页面中使用变量ERROR_NOTES
:
<!--#if expr="$REDIRECT_ERROR_NOTES = ''" -->
<p>
The server encountered an unexpected condition
which prevented it from fulfilling the request.
</p>
<p>
<a href="mailto:<!--#echo var="SERVER_ADMIN" -->"
SUBJECT="Error message [<!--#echo var="REDIRECT_STATUS" -->] <!--#echo var="title" --> for <!--#echo var="REQUEST_URI" -->">
Please forward this error screen to <!--#echo var="SERVER_NAME" -->'s
WebMaster</a>; it includes useful debugging information about
the Request which caused the error.
<pre><!--#printenv --></pre>
</p>
<!--#else -->
<!--#echo var="REDIRECT_ERROR_NOTES" -->
<!--#endif -->
总结上述例子,可以得出400.shtml.en
页面的完整实现。
你会发现,其中除了出错说明(和一些有条件的附加物)几乎没有其它东西。
以这个例子为起点,可以容易地增加更多的出错页面,或者把出错页面翻译为不同语言。
<!--#set var="title" value="Bad Request"-->
<!--#include virtual="head" -->
<p>
Your browser sent a request that this server could not understand:
<blockquote>
<strong><!--#echo var="REQUEST_URI" --></strong>
</blockquote>
The request could not be understood by the server due to malformed
syntax. The client should not repeat the request without
modifications.
</p>
<p>
<!--#if expr="$HTTP_REFERER != ''" -->
Please inform the owner of
<a href="<!--#echo var="HTTP_REFERER" -->">the referring page</a> about
the malformed link.
<!--#else -->
Please check your request for typing errors and retry.
<!--#endif -->
</p>
<!--#include virtual="foot" -->
以下是完整的head.shtml.en
文件(其中的断行可以避免在SSI处理后产生空行).
注意位于头部的配置段,这里可以修改图片、标志以及apache文档的目录。
研究一下,这个文件是如何根据虚拟主机名称($SERVER_NAME)显示不同标志的,
又是如何在浏览器支持的情况下显示一个apache的动画标志的。
(这需要有服务器级的相应配置
BrowserMatch "^Mozilla/[2-4]" anigif
以指示支持动画GIF的浏览器类型)
<!--#if expr="$SERVER_NAME = /.*\.mycompany\.com/" -->
<!--#set var="IMG_CorpLogo" value="http://$SERVER_NAME:$SERVER_PORT/errordocs/CorpLogo.gif" -->
<!--#set var="ALT_CorpLogo" value="Powered by Linux!" -->
<!--#else -->
<!--#set var="IMG_CorpLogo" value="http://$SERVER_NAME:$SERVER_PORT/errordocs/PrivLogo.gif" -->
<!--#set var="ALT_CorpLogo" value="Powered by Linux!" -->
<!--#endif-->
<!--#set var="IMG_BgImage" value="http://$SERVER_NAME:$SERVER_PORT/errordocs/BgImage.gif" -->
<!--#set var="DOC_Apache" value="http://$SERVER_NAME:$SERVER_PORT/Apache/" -->
<!--#if expr="$anigif" -->
<!--#set var="IMG_Apache" value="http://$SERVER_NAME:$SERVER_PORT/icons/apache_anim.gif" -->
<!--#else-->
<!--#set var="IMG_Apache" value="http://$SERVER_NAME:$SERVER_PORT/icons/apache_pb.gif" -->
<!--#endif-->
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>
[<!--#echo var="REDIRECT_STATUS" -->] <!--#echo var="title" -->
</title>
</head>
<body bgcolor="white" background="<!--#echo var="IMG_BgImage" -->">
<h1 align="center">
[<!--#echo var="REDIRECT_STATUS" -->]
<!--#echo var="title" -->
<img src="<!--#echo var="IMG_CorpLogo" -->"
alt="<!--#echo var="ALT_CorpLogo" -->" align="right">
</h1>
<hr />
<!-- ======================================================== -->
<div>
最后,是foot.shtml.en
文件:
</div>
<hr />
<div align="right">
<small>Local Server time:
<!--#echo var="DATE_LOCAL" --></small>
</div>
<div align="center">
<a href="<!--#echo var="DOC_Apache" -->">
<img src="<!--#echo var="IMG_Apache" -->" border="0" align="bottom"
alt="Powered by <!--#echo var="SERVER_SOFTWARE" -->"></a>
<br />
<small><!--#set var="var" value="Powered by $SERVER_SOFTWARE --
File last modified on $LAST_MODIFIED" -->
<!--#echo var="var" --></small>
</div>
<p>If the indicated error looks like a misconfiguration, please inform
<a href="mailto:<!--#echo var="SERVER_ADMIN" -->"
subject="Feedback about Error message [<!--#echo var="REDIRECT_STATUS" -->]
<!--#echo var="title" -->, req=<!--#echo var="REQUEST_URI" -->">
<!--#echo var="SERVER_NAME" -->'s WebMaster</a>.
</p>
</body>
</html>
如果您愿意贡献您的技巧,请发信给martin@apache.org。