Microsoft.com 技术内幕ASP.NET 的管理与委派

Jeff Toews

Microsoft.com Web 基础结构几乎完全在 .NET Framework 2.0 上运行。Microsoft.com 运营小组的主要 Web 管理难题之一是如何妥善地配置 ASP.NET,在不断优化设置的尝试过程中,我们也学到了很多知识。若要

获得适当的配置设置,不仅需要熟悉 web.config 和 machine.config 文件中的各个配置节,还需要理解这些设置的含义。要掌握这些设置及其含义,研读示例是一种不错的方式。在本专栏中,我将介绍一些配置技巧,这些技巧是在配置运行有 Microsoft.com 服务器的过程中获得的,希望您能够从我们的经验中获益。

1. 适当设置编译开关

当在实际运行环境中部署基于 ASP.NET 的应用程序时,必须确保任何人都不能将任何应用程序的 web.config 文件中的编译调试属性设置为 True(无论是意外的还是特意的),即不能出现如下设置:

<compilation debug="true" />

在要管理很多 Web 应用程序的繁忙环境中,需要使用 ASP.NET 配置控制机制来防止发生这一情况。(我将对此再进行一些详细说明)。

此外,还必须确保不将单个 .aspx 中的页面专用的调试属性设置为 True,即不能出现如下设置:

<%@ Page debug="true" %>

同样,在需要发布大量内容的繁忙环境中,期望能够确保在发布 .aspx 页之前将所有这些页中的这一设置删除通常是不现实的。您需要一种全局的方法来防止发生此情况。

使用此设置来编译 Web 应用程序将导致调试二进制文件而不是发布二进制文件。而且,您的代码将不进行优化,最终将导致性能降低。此外,由于调试设置会阻止超时,因此 ASP.NET 请求将不会超时。在实际运行环境中使用调试版本无异于向黑客敞开大门!

幸运的是,Microsoft® .NET Framework 2.0 为 machine.config 提供了一个新的部署设置,该设置会指示 ASP.NET 禁用以下内容:调试功能、跟踪输出以及显示 ASP.NET 错误消息(包括在本机和远程计算机上),无论 web.config 文件或特定页属性的指令如何,均禁用这些内容。该设置形如:

<configuration>
    <system.web>
          <deployment retail="true"/>
    </system.web>
</configuration>

请注意,后两项优点(即禁用跟踪输出和远程禁用详细的 ASP.NET 错误消息)都是有关安全性的最佳做法,确实应予以采用。如果未采用这两项做法,则会向全世界暴露应用程序的内部工作机制,容易被人利用。

在这个问题上,还有另外一个重要事实:锁定 <location allowOverride="false"> 的根 web.config 文件中的 <system.web><compilation> 调试属性或使用 lockItems 属性会阻止应用程序配置层次结构中层次较低的 web.config 文件启用调试设置。但它不会阻止各个 .aspx 页在其页属性中启用调试模式。部署发布开关是在所有级别上完全禁用 ASP.NET 调试功能的唯一方式。

将部署发布开关设置为 True 可能是最佳做法,任何拥有正式运行的服务器的公司都应该遵循,以便确保应用程序始终以最优性能运行,且不会出现安全信息泄漏。如上所述,此开关是 ASP.NET 2.0 中新推出的;它是用户对 ASP.NET 工作组提供反馈的直接结果。

与之相反,在面向内部的预运行环境中,开发人员需要调试其 Web 应用程序,因此请勿使用部署发布设置。您只需在预运行的根 web.config 文件中设置 <compilation debug="false">,并允许此值可由各个应用程序的 web.config 文件或 .aspx 页属性替代即可。

2. 在 ASP.NET 2.0 中使用中等信任

如果您在将网站或应用程序迁移到 ASP.NET 2.0 之后仍然使用“完全”或“高”信任级别(很多 Microsoft.com 上的网站都是如此),现在请尝试将信任级别设置为中等信任,看看可能会出现什么情况。 例如,受限 WebPermission 会将应用程序的通信目标仅限于您在 <trust> 元素中定义的一个地址或一个地址范围。这样,您便可以控制和维护能够远程访问的许可外部网站和地址范围的列表。这是一个在安全性方面的重大改进。

FileIOPermission 也会受到限制。这意味着,应用程序的代码只能访问其虚拟目录层次结构中的文件。在中等信任下,默认情况是每个应用程序都被仅授予对其虚拟目录层次结构拥有 Read(读)、Write(写)、Append(追加)和 PathDiscovery(路径探寻)权限。这样便禁止了随机的文件 I/O 访问,而这在承载了大量应用程序的共享 Web 环境中则是非常重要的。

另一个优点是去掉了非托管代码特权。这意味着,您可以禁用原来的组件,它是迄今为止我们找到的禁用 Aspcompat 页属性的最简便的方法。(将 Aspcompat 设置为 True 可导致页性能下降)。

通过 ASP.NET 2.0 下的中等信任,管理员可以为每个以前的默认限制创建自定义的例外项,因此可获得极大的灵活性。而此灵活性是 ASP.NET 1.1 所不具备的。在 ASP.NET 2.0 中运行中等信任比在 ASP.NET 1.1 中运行中等信任更容易的另一个原因是,您可以访问 Microsoft SQL Server™ 数据库。

因此,如果您在同一台服务器上承载了多个应用程序,则可以使用代码访问安全性和中等信任级别来实现应用程序隔离。通过在根 web.config 文件中设置并锁定该信任级别(使用 <location allowOverride="false"> 标记),可以为服务器上的所有 Web 应用程序都建立安全策略。通过设置 allowOverride="false"(如图 1 所示),单个开发人员便无法在其应用程序的 web.config 文件中替代中等信任策略设置。

Figure 1 信任设置

<configuration>
    <location allowOverride="false">
        <system.web>
            <securityPolicy>
                <trustLevel name="Full" policyFile="internal" />
                <trustLevel name="High" policyFile="web_hightrust.config" />
                <trustLevel name="Medium" policyFile="web_mediumtrust.config" />
                <trustLevel name="Low"  policyFile="web_lowtrust.config" />
                <trustLevel name="Minimal" policyFile="web_minimaltrust.config" />
            </securityPolicy>
            <trust level="Medium" originUrl="" />
        </system.web>
    </location>
</configuration>

有关如何在 ASP.NET 2.0 中使用中等信任的详细信息,请参阅以下文章:“如何:在 ASP.NET 2.0 中使用中等信任”。

3. 限制下载指定的文件类型

服务器上无疑有一些类型的文件是您不想让其落入他手的。幸运的是,ASP.NET 的默认配置可截获并停止对 ASP.NET 应用程序中使用的多种文件的请求。这些文件类型包括存储应用程序源代码的 .config 文件和 .cs 文件。ASP.NET 可将这两个文件类型与 System.Web.HttpForbiddenHandler 关联起来,以此来确保这些文件不被公开。调用该处理程序会向请求文件的用户返回错误。实际上,您可以使用此方法来限制任何文件类型。

例如,在 Microsoft.com 网站上,通过向根 web.config 文件的 <system.web><httpHandlers> 一节添加以下项可以禁止 .asmx 文件类型:

<add path="*.asmx" verb="*" type=
    "System.Web.HttpForbiddenHandler" />

从中可以看出,您可以使用 <httpHandlers> 元素中的 <add> 子标记来指定要禁止的其他文件类型。将谓词属性设置为“*”。执行此操作时,需要指定阻止所有类型的对该文件类型的 HTTP 请求。将路径属性定义为与要阻止的文件类型匹配的通配符。例如,可以指定“*.mdb”。最后,将类型属性设置为“System.Web.HttpForbiddenHandler”。

4. 在添加程序集引用时应谨慎

每个运行 .NET Framework 的 Web 服务器都有一个计算机范围的代码缓存,称为全局程序集缓存 (GAC)。GAC 存储专门指定给计算机上的多个应用程序共享的程序集。如果有多个不同的应用程序需要引用程序集,则可以将这些程序集添加到 GAC 中,即使 Web 服务器上的大多数网站都不访问它们。这样不会造成大量的性能/资源损失,却具有可进行集中的版本控制的优点,而不是让程序集散布在服务器中单个应用程序的 /bin 文件夹中。

不过,判断应在何时将程序集引用添加到根 web.config 的条件必须比判断在何时将应用程序置于 GAC 中的条件更加严格。对于不是真正全局的组件来说,让各个应用程序将程序集引用添加到它们的应用程序 web.config 文件中比在根 web.config 文件中声明这些程序集具有高得多的性能。前者可以大大减少 Web 服务器上所有不使用这些程序集的应用程序的页加载时间,因为编译器不需要再花费时间来加载不需要的程序集。ASP.NET 编译器不为应用程序加载程序集只是因为程序集驻留在 GAC 中;它只会在其 appdomain 或较大的应用程序范围中存在程序集引用时才会加载该程序集。因此,在 Microsoft.com 上,我们允许应用程序开发人员对所有 Microsoft.com 环境替代其应用程序的 web.config 文件中的 <configuration><system.web><compilation><assemblies> 元素。

具体来说,在 Microsoft.com 运行环境和临时环境的根 web.config 文件中,<system.web><compilation> 节点的所有属性(包括 debug、explicit、defaultLanguage)及其所有元素(buildProviders、expressionBuilders 等)都将锁定,但 <assemblies> 元素除外:

<compilation debug="false" 
    explicit="true" defaultLanguage="vb" 
    numRecompilesBeforeAppRestart="500" 
    lockAttributes="*" lockAllElementsExcept=
        "assemblies" >

在面向内部的预运行环境中,根 web.config 文件的 <system.web><compilation> 一节是锁定的,但我们允许应用程序发布者在特殊情况下替代 <assemblies> 元素以及 debug="false"(用于启用故障排除/调试):

<compilation debug="false" explicit="true"
    defaultLanguage="vb" 
    numRecompilesBeforeAppRestart="500" 
    lockAllAttributesExcept="debug" 
    lockAllElementsExcept="assemblies" >

请注意此处使用的锁定属性,它们是 ASP.NET 2.0 中新增的。有关这些属性及其用法示例的详细信息,请参阅“节元素继承的常规属性”。

5. 删除手动设置的 MaxConnection 值

几乎所有 Microsoft.com 网站都有调用远程 Web 服务群集的 ASP.NET 应用程序。可以从单个 Web 服务器执行的最大并行远程 Web 服务器调用数由 machine.config 文件中 <connectionManagement> 元素的 maxConnection 属性决定。在 ASP.NET 1.1 中,maxConnection 的默认值设置为 2。对于像 Microsoft.com 这样有数百个不同应用程序调用远程 Web 服务的网站来说,这一旧的 maxConnection 默认值实在是过于缓慢。结果是 ASP.NET 请求将排队并等待远程 Web 服务调用的完成。(您可以通过 Perfmon 计数器“ASP.NET\排队的请求”来查看排队的 ASP.NET 请求数)。为了能够同时执行更多的远程 Web 服务调用(从而提高网站上应用程序的性能),我们将拥有四个处理器的 Web 服务器的 maxConnection 值增加到了 40(通常建议将 maxConnection 的值设置为 CPU 数的 12 倍,但您可以调整此值以满足您的特定需求)。

但是,在 ASP.NET 2.0 中,您不再需要手动配置 maxConnection,因为它现在是自动调整和设置的。这都要归因于 machine.config 中 processModel 标记的新增配置节。有关 processModel 元素的详细信息,请参阅“processModel 元素(ASP.NET 设置架构)”。

<system.web>
    <processModel autoConfig="true" />
</system.web>  

通过在 machine.config 中启用 autoConfig(此为默认设置),ASP.NET 可将 maxConnection 参数的值设置为 12n(其中 n 是 CPU 的数目)。启用 autoConfig 还将导致以下情况:maxWorkerThreads 参数和 maxIoThreads 参数设置为 100,minFreeThreads 参数设置为 88n,minLocalRequestFreeThreads 参数设置为 76n,minWorkerThreads 设置为 50。

在利用 ASP.NET 2.0 中的 autoConfig 自动调整和设置 maxConnection 及列表中的其他属性之前,应确保删除任何手动为这些参数设置的值,否则将使用这些值代替自动配置值。在从 ASP.NET 1.1(需要显式设置 maxConnection)迁移到 ASP.NET 2.0(存在该属性的默认值)时,应牢记这一点。

此外,maxConnection 以及前面列出的其他属性的 autoConfig 值是任意给定的,它们并不绝对适用于每一种情况,但我发现,这些限制几乎适用于所有的 Microsoft.com 应用程序。

如果您决定需要手动调整 maxConnection 值,请在增加其值时务必小心,因为该操作会导致 CPU 利用率提高。这种利用率的提高的情况是由以下事实导致的:ASP.NET 可以处理更多的传入请求,而不是让它们等待轮到自己调用 Web 服务。当然,还应记住 maxConnection 属性不影响本地 Web 服务调用,它只影响远程调用。

6. 注意未处理的异常

在将 ASP.NET 1.1 网站或应用程序导入 ASP.NET 2.0 时,应注意未处理异常的默认策略的主要变化,这一点极其有用。在 .NET Framework 1.1 和 1.0 中,托管线程上的未处理异常会被忽略,并且由于应用程序继续运行,这些异常通常处于隐藏状态。除非您附加调试器来捕获异常,否则不会意识到有任何错误发生。不过,在 ASP.NET 2.0 下,当引发未处理的异常时,基于 ASP.NET 的应用程序将意外退出。如果旧的默认异常处理策略以前掩盖了许多未处理的异常,则会严重影响网站或应用程序的可用性。

解决此问题的最佳方式是执行全面测试并消除未处理的异常(应用程序中确实不应出现这些未处理的异常)。但是,当要迁移的应用程序非常大,可能难以确定发生异常的位置,或者当要迁移大量的旧式应用程序而无法进行全面的单项测试时,还有几种方案可选。在将 Microsoft.com 网站迁移到 ASP.NET 2.0 时,我们将未处理的异常策略更改回 ASP.NET 1.1 和 ASP.NET 1.0 中的默认行为。

要启用此旧式默认异常处理行为,请将以下代码添加到 aspnet.config 文件中:

<configuration>
    <runtime>
        <legacyUnhandledExceptionPolicy 
            enabled="true" />
    </runtime>
</configuration>

此代码位于以下两个文件夹中:

%WINDIR%\Microsoft.NET\Framework\v2.0.50727(在 x86 或 SYSWOW64 系统上)和 %WINDIR%\Microsoft.NET\Framework64\v2.0.50727(在 x64 系统上)。

此更改将从根本上将 .NET Framework 还原为旧的 1.1 和 1.0 行为。在使用此短期修复方法时应三思后行,因为它终究是掩盖了应用程序中存在的问题,它们确实是一些 Bug。不过,要避免因意外终止工作进程而导致的可用性问题,它不失为一种极其简便的方法。有关这种更改行为的详细信息,请参阅“在 .NET Framework 2.0 中未处理的异常会导致基于 ASP.NET 的应用程序意外退出”。

7. 确保代理服务器配置得当

Web 服务器管理员可以通过配置 machine.config 文件中的 <configuration><system.net><defaultProxy> 元素来指定使用代理服务器向 Internet 发送 HTTP 请求。

在 Microsoft.com 运行环境中,我们将 <defaultProxy> 值配置为使用系统默认代理(在未安装防火墙客户端时)并使用 <location allowOverride="false"> 标记来防止 <defaultProxy> 元素被应用程序开发人员替代(他们可能意外发布设置了内部代理服务器的 web.config 文件):

<configuration>
    <location allowOverride="false">
       <system.net>
          <defaultProxy>
              <proxy usesystemdefault="true" />
          </defaultProxy>
       </system.net>
    </location>
</configuration>

在 Microsoft.com 内部预运行和临时环境中,我们将 usesystemdefault 属性设置为 False,将 bypassonlocal 设置为 True 并添加一个代理 bypasslist(参见图 2)。bypasslist 列出了表示不使用指定代理的地址的正则表达式。同样,此节包含在 <location allowOverride="false"> 标记中,旨在防止开发人员在其 web.config 文件中指定自己的代理服务器。(这些代理服务器通常是内部服务器,如果将页面发布到运行环境中,则对这些服务器的调用将中断。)任何在预运行或临时环境中指定代理服务器的尝试都将导致在运行时出现 ASP.NET 错误,这将强制开发人员在发布到运行环境之前删除此配置。

Figure 2 设置 Bypasslist

<configuration>
    <location allowOverride="false">
        <system.net>
             <defaultProxy>
                <proxy
usesystemdefault="false"
proxyaddress = "http://proxy.server.foo.com:80"
bypassonlocal = "true" />

                <bypasslist>
<add address="10\.*"/>
<add address="dns\.foo\.com" />
<add address="name1\.name2\.foo\.com" />
                </bypasslist>
            </defaultProxy>
        </system.net>
    </location>
</configuration>

8. 请勿向任何人显示自定义错误

我前面提到,不应允许运行环境中的 Web 服务器从远程返回详细的 ASP.NET 错误消息。

在 Microsoft.com 运行环境和临时环境的根 web.config 文件中,应将 <configuration><system.web><customErrors> 模式属性设置为 RemoteOnly,以便向远程客户端显示自定义错误,而向本机显示 ASP.NET 错误(从而允许 Web 服务器管理员进行故障排除)。请注意,<customErrors> 元素包含在 allowOverride="false" 的 <location> 标签中(参见图 3)。这是为了防止各个应用程序所有者意外(或有意)设置 mode="Off" 并将详细的 ASP.NET 错误消息散布到 Internet。

Figure 3 禁止显示错误消息

&lt;configuration&gt;
    &lt;location allowOverride='false'&gt;
        &lt;system.web&gt;
            &lt;customErrors mode='RemoteOnly' defaultRedirect=
                   '/errorpages/generic_customerror.aspx'&gt;
                &lt;error statusCode='404' redirect='/errorpages/filenotfound_customerror.aspx' /&gt;
            &lt;/customErrors&gt;
        &lt;/system.web&gt;
    &lt;/location&gt;
&lt;configuration&gt;

另外还要记住,在 machine.config 文件中使用 <deployment retail="true"/> 开关会关闭向远程和本地客户端显示 ASP.NET 详细错误消息的功能,这一点我前面已经提过。如果您运行的是 ASP.NET 2.0,则仍应将此部署发布开关作为关闭这些错误消息的主要方法。(要获得有关 ASP.NET 异常的详细信息,请使用应用程序事件日志。)

在 Microsoft.com 面向内部的预运行环境根 web.config 文件中,应将 <customErrors> 模式属性设置为关闭,以便始终向本机和远程客户端都显示 ASP.NET 错误。这是为了启用调试和故障排除功能。此外,不应配置任何自定义错误页:

<configuration>
    <location allowOverride="false">
        <system.web>
            <customErrors mode="Off" />
        </system.web>
    </location>
<configuration>

9. 了解启用跟踪的时机

ASP.NET 跟踪在执行 ASP.NET 页时生成,它捕获有关 Web 请求、页控制树以及页生命周期和控制的各个阶段的执行等感兴趣的详细信息。此外,还可以显示页面开发人员为跟踪编写的自定义消息。跟踪可以附加在被跟踪和检查的页的响应输出中,作为应用程序跟踪查看器中被跟踪请求列表的一部分。此功能主要用于内部预运行环境中的开发调试,不应用于实际的运行部署。

在 Microsoft.com 运行环境和临时环境的根 web.config 文件中,将启用 <configuration><system.web><trace> 的属性设置为“false”,以便禁用网页中的跟踪信息输出功能。请注意,<trace> 元素包含在 allowOverride="false" 的 <location> 标记中。这是为了防止各个应用程序所有者意外(或有意)设置 enabled="true" 并将详细的 ASP.NET 跟踪信息输出到 Internet 上:

<configuration>
    <location allowOverride="false">
        <system.web>
              <trace enabled="false" localOnly="true"
              pageOutput="false" requestLimit="10" traceMode="SortByTime" />
        </system.web>
    </location>
<configuration>

<system.web><trace>

正如前面提到的,在 machine.config 文件中使用 <deployment retail="true"/> 开关也会关闭网页中的 ASP.NET 跟踪输出功能。如果您运行的是 .NET Framework 2.0,则仍应将此开关用作关闭跟踪输出的主要方法。

为完全确保跟踪不会在面向外部的运行环境中意外地变为启用,在 Microsoft.com 上,我们将实际的 trace.axd 处理程序从根 web.config 文件中删除或将其注释掉,如下所示:

<!--
<add path="trace.axd" verb="*" type=
    "System.Web.Handlers.TraceHandler"
    validate="True" />
-->

10. 禁用会话状态 Web 场

由于 Microsoft.com 上的所有网站当前都使用网络负载平衡 (NLB) 组成不关联的群集(以允许跨群集中的所有服务器分发请求),因而无法保证同一台服务器将处理给定应用程序的所有请求。为此,应禁用 ASP.NET 会话状态模块以防止应用程序开发人员使用 Session 属性。如果应用程序开发人员需要维护状态,我们向他们提供的准则是使用“查看状态”命令(它将在页代码的结构中维护状态,从而不会使用任何服务器资源)。

要禁用 Web 服务器上的会话状态,只需从 <httpModules> 节点删除以下子节点即可:

<add name="Session" type=
    "System.Web.SessionState.
    SessionStateModule"/>

在这里,我假定您已经见过甚至使用过 ASP.NET 配置文件(machine.config、根 web.config 和各个应用程序的 web.config 文件),但如果您没有,请学习以下 ASP.NET 快速入门教程以获得帮助:asp.net/QuickStart/aspnet/doc/management/fileformat.aspx。

该文档提到 ASP.NET 2.0 配置系统现在还包含大量有用的功能,它们允许管理员使用 lockItems 和 lockCollections 属性锁定配置的各个元素和属性。这些属性及其用法的信息已归档到以下文章中:“节元素继承的常规属性”。

Jeff Toews 是一位系统工程经理,他是总部位于雷蒙德的 Microsoft.com 运营小组的成员,任职已有六年。如果您有任何技术问题或意见,可以发送邮件到 mscomblg@microsoft.com,与他联系。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.