WebGame(.NET网页游戏)开发者必读:IIS和ASP.NET技术内幕

本帖最后由 海洋 于 2009-3-28 11:02 编辑

介绍 9 m5 U6 Z0 T& m# B3 q6 Y& z
   像ASP.NET这样的工具大大简化了复杂的web应用的 开发。虽然这对一般的开发而言,了解ASP.NET已经基本足够了,但是我们仍然有必要理解ASP.NET应用程序代码和web服务器之间的通信机理。另外,当您需要插手或者截获HTTP请求的时候,更需要您对ASP.NET和您选择的web服务器之间的通信过程进行深入了解了。 * z4 `4 I% B; b3 S   M+ ~
本文阐述了ASP.NET和IIS如何通信,描述了一些截获通信的技术。我将先回顾ASP.NET怎样配置才能处理请求,默认情况下应用和web服务如何被处理。
然后我将探讨如何干涉http请求,操纵工厂和模块,您将能够通过一系列的例子看到它们是怎么独立或者协同工作的。
; ~# n* x# ?( E0 m5 t6 c6 H: C
笔者总结) x$ r9 E& P4 Q
   Michele的文章深入浅出的展示了.NET HTTP Handler,Handlers Factories以及模块(Model)的技术,知其然,还要知其所以然。在看了这篇文章以后,虽然以前也看过一些类似的文章,也应用过,仍然收获良多。比如,很久以前我们曾经在ASP.NET多线程处理的时候出现过异常,当时并未捕获这个异常,以至于发生异常以后,ASP.NET频繁重新启动,大家都莫名其妙,后来在msdn上查到了一篇文章,通过一个HTTP Model捕获所有未处理的异常,终于找到了问题的根源。
   我也在网上查找了这篇文章的中文译本,不过没有找到,索性自己抽空翻译出来(如有不当的地方,还请指正),与所有的有志于ASP.NET的同志们共享。

从IIS到ASP.NET 9 {/ n5 \4 ?2 D   j2 O* B
   IIS和.NET Framework通过非托管的ISAPI通信:aspnet_isapi.dll和aspnet_filter.dll。aspnet_isapi.dll为请求路由提供服务,aspnet_filter.dll主要用于为ASP.NET处理无Cookie的会话状态。这两个非托管动态链接库和windows状态服务(aspnet_state.exe)、ASP.NET工作进程(aspnet_wp.exe)一起,是ASP.NET处理的核心。
   当ASP.NET安装在了一台已经装了IIS的机器上的时候,IIS被配置以便一些特殊扩展名的请求能够被aspnet_isapi.dll处理,有一点很有趣,过滤器同时也被配置好了。 ! A1 z8 r' m$ @$ W* x. F$ t
0 n" }: S, e* Z' ?& |
   对ASP.NET的请求将通过这些配置好的扩展,从IIS转发到ASP.NET,这些扩展在非托管和托管代码之间架起了一座桥梁。在控制权被传递到您的应用之前,某个ASP.NET应用程序对象必须被初始化(通过.NET运行时)并通过配置信息决定请求改如何被处理,Machine.config和可收集的 web.config用来支持这个处理过程。 % F; I" e* E5 O" ^# K/ g+ d

   对这篇文章来说,我们对<httpHandlers>配置节特别感兴趣,在此配置节中的配置指明哪种.NET类型用于处理请求。在安装完毕.NET以后,Machine.config中<httpHandlers>配置节的配置信息如下:

<httpHandlers> 1 y+ w/ G; |& ?; m+ g
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/> & M/ M: R9 ~) \! s
<add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.3300.0, Culture=neutral,
   PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
<add verb="*" path="*.rem"   type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral,   PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.soap" 1 {: x% j2 q/ N$ t' e+ P, k+ J; m) p
   type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral,   PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler"/> 3 g9 f( s: }5 O
<add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler"/> 8 u# O   ]- B% G4 e
<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler"/> 8 ^$ y) Q, q% s/ `0 o
<add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler"/> ! W7 {/ [3 i- k. T* u- A
<add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler"/> / Q   K# w. u, F+ C- P! V( U
<add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler"/>
<add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/> & N- d4 S0 ?4 U$ @, U* c- P7 L
<add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler"/>
</httpHandlers> . a1 Q7 P! O; G. L+ c1 L

   <httpHandlers>配置节说明哪个处理工厂或者Handler来处理请求,总之,从宏观的角度来看,从IIS到ASP.NET应用的处理流程如下:
 1. IIS接到一个对某资源的请求 3 c3 \, s4 c: h0 ]2 j
 2.如果资源被映射到ASP.NET ISAPI扩展(如:*.aspx, *.asmx),请求将被传递给与HTTP运行时对象通信的非托管ASP.NET 动态库。 ; x. B% {4 Z2 e% s1 a
 3.HTTP运行时对象负责产生HTTP应用对象(如果必要的话)、检查配置文件,然后把控制权交给处理请求的合适的Handler。
 4.Handler处理请求,最终以应答的方式发出回应。
下面的图表描述了这个过程:
# w: [# B" T% k
   <httpHandlers>配置节能够在全局(machine.config)级别修改,也能在应用(web.config)级别重载。换句话说,您可以指定其他的工厂或者Handler来处理对特定资源的请求,例如,如果拒绝对*.rem对象的请求,您可以像这样修改machine.config或者web.config:
; o" {6 g8 s8 A1 {3 {$ d
   <add verb="*" path="*.rem" type="System.Web.HttpForbiddenHandler"/
/ v7 V% g: ^% q# c2 d   k# f% j8 r/ V
   把*.rem与HttpForbiddenHandler关联替换了默认的行为,原来它是与HttpRemotingHandlerFactory关联的。如果您是在machine.config做的修改,这将影响所有在这个web服务器上运行的应用。 5 T3 z( ~   x/ o: w% a6 _

HTTPHandler和HTTPHandler工厂
   上文已经提到,ASP.NET运行时依赖于Handler和Handler工厂工作。配置文件把资源和Handler或者Handler的工厂类联系起来,让我们再进一步看看这些设置。 , S2 G. G( {, \* \
下面的配置入口把System.Web.UI.PageHandlerFactory类和*.aspx资源联系起来,对所有的HTTP行为都适用(包括:GET, POST)。
# p% r4 r% n7 x/ q$ _- b
   <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>

   下面的配置入口把System.Web.HttpForbiddenHandler和*.config资源联系起来:

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

   基础类库已经提供了先前的这些类,我们也可以通过分别实现IHttpHandler工厂 和 IHttpHandler接口,建立定制的Handler和Handler工厂。在我们探讨如何实现他们之前,我们先回顾一下它们的特性。从宏观上来看,HTTPHandler工厂用来动态的生成HTTPHandler对象,用于管理请求的资源。如果指定了一个HTTPHandler,它将直接被运行时实例化,不论如何,最终结果是把资源关联到了一个Handler。 $ U4 q( @% |* l* e
   在运行时把控制权交给Handler以后,处理请求就是Handler的工作了,包括实例化适当的ASP.NET服务端对象,发送HTTP响应。例如:PageHandlerFactory返回一个System.Web.UI.Page,处理对*.aspx资源的请求,System.Web.HttpForbiddenHandler会抛出一个HttpException表明请求不被支持。 & K- L6 \9 j* k   h
+ N6 f6 G- t8 O. b5 @, Q/ G" \
实现IHttpHandler 9 N& H) O# r4 a* r5 O
   和ISAPI类似,Handler提供了访问HTTP请求和应答的途径。实现IHttpHandler允许您处理对特殊资源的请求,您能截获对它们的请求并重载应答。例如:如果您想记录对禁止访问的文件进行访问的客户端的IP地址,您可以写一个Handler,在抛出HttpException之前记录它的IP。您也可以向它们发送一些友好的提示,就像下面的代码示例一样: / H: m7 x" ~% }' g' R8 X
* f, _+ X+ o! G- ?" @
   如果想产生一个定制的Handler,就要生成一个实现了IHttpHandler的组件,此接口包含两个成员:
• ProcessRequest() – 此方法被运行时调用,处理请求
• IsReusable – 这个属性表明:是否多个请求共享同样的Handler
   ProcessRequest()用来为请求传递HTTP上下文,HTTP上下文可用于访问HttpRequest, HttpResponse 和 HttpSessionState对象。 ! |9 o) E0 B8 J, t0 g6 n3 C   u" |$ d
注意:Handler必须实现IRequiresSessionState接口,假如它将访问会话对象的话。 1 R' D   E: y/ }4 l2 v3 D5 ^3 J
下面是一个HTTPHandler向浏览器输出信息的例子:

public class ForbiddenLogHandler: IHttpHandler 6 S" Y2 I2 Y2 }+ T( C$ {6 T
{    
public ForbiddenLogHandler()
{     } 1 |/ A+ g$ F+ a; i1 g5 m
public virtual void ProcessRequest(HttpContext context)  
{            
context.Trace.Write("ForbiddenLogHandler.ProcessRequest()");       HttpResponse rs = context.Response;       
HttpRequest rq = context.Request;       " b$ ]: `: b" k" c* w- U) W
rs.Write("<p><H1>We know who you are...</H1></p>");      
rs.Write("You were referred by " + rq.UrlReferrer + "<br>");        / j: z4 O" D- G5 n# l; h
rs.Write("Your IP is" + rq.UserHostAddress + "<br>");       
rs.Write("Your domain is" + rq.UserHostName + "<br>");       
rs.Write("<p>Why were you requesting a restricted resource?</p>");    8 o* \: ^0 K: r; u
}       # z2 t9 h% _0 f) J
public virtual bool IsReusable    
{       
get { return true; }     8 h/ s& n" J" p$ }
} $ f: R; V/ B8 a
} ) y5 L: }+ Y# m
8 u% y9 t, \   g, ^( x
   下面的web.config区域配置ForbiddenLogHandler为*.cs, *.resx, or *.config文件资源服务: # [7 X# P! k1 E   I) c+ {# o) x* L
' Z9 Z   R$ Z9 }0 R) M
<httpHandlers> # I2 u- f/ K& h( i% `( H
<add verb="*" path="*.cs" type="WebHandlers.ForbiddenLogHandler,WebHandlers"/> % b. a& _% m& V% I
<add verb="*" path="*.resx" type="WebHandlers.ForbiddenLogHandler,WebHandlers"/> - Y- \' K& \. B5 n' r( K
<add verb="*" path="*.config" type="WebHandlers.ForbiddenLogHandler,WebHandlers"/> ! W: C- P% H9 l/ Y- p% q6 d, Q, J
</httpHandlers>

实现IHttpHandlerFactory 3 p0 _, e6 j' J' @8 t. `9 N! F
   HTTP handlers可以在请求特殊资源的时候使用,handler工厂使中途截获请求成为可能,它可完成一些请求预处理工作,然后为资源生成handler。
   要产生定制的handler工厂,需要生成一个实现IHttpHandlerFactory接口的组件,这个接口包含下列成员:
• GetHandler() – 这个方法为运行时产生一个合法的IHttpHandler来处理对资源的请求。 , H! k* D" r0 _( Y
• ReleaseHandler() – 为工厂提供一个重用handler 的机会。 : z5 {5 @/ P/ \
   GetHandler()被运行时调用,它必须返回null或者合法的 IHttpHandler。这里有一个HTTP handler 工厂的例子,它可以统计从某个特定的IP地址访问的次数, 如果次数过多,则返回一个定制的handler;否则,把控制权交给默认的 handler。 / D+ F   G0 z& f# P5 M9 ]9 e
" W9 a! l( \$ i# ~$ P   ]
public class HitTrackingHandlerFactory: IHttpHandlerFactory
{ 1 L6 N, r# u. l0 {5 ?
public HitTrackingHandlerFactory() 9 _; r7 ^6 p( H$ I! s( w" _
{   } 5 f8 [0 K+ z' n/ q
public virtual IHttpHandler GetHandler(HttpContext context,    String requestType, String url, String pathTranslated) : i6 x( m7 }: d; L" O
{
if (!HitLogHelper.CheckHitCount(context.Request.UserHostAddress, context.Request.UserHostName))        0 j. ]   s4 K6 y4 u& V6 J
return new HitsExceededHandler();          # m! @1 d+ Z9 b8 G2 k0 R; \; r
Object handler = null;       
try    0 v2 g: s; f% s5 W; K
{       
String filename = url.Substring(url.LastIndexOf('/')+1);        2 w: \8 f3 ?0 Z0 O   v
String file = filename.Substring(0, filename.IndexOf('.'));        : B4 @8 ?9 J' W3 B
String ext = filename.Substring(filename.LastIndexOf('.')+1);       0 t4 i+ r( s' C$ c+ a4 S
if (ext == "aspx")        . j2 L6 n" [# z0 ^2 @2 i
{          
return System.Web.UI.PageParser.GetCompiledPageInstance(url, pathTranslated, context);       
}       
else if (ext == "asmx")       
{           7 ~3 p: `3 ~   U/ I! b2 G+ R
System.Web.Services.Protocols.WebServiceHandlerFactory fact = new System.Web.Services.Protocols.WebServiceHandlerFactory();  
handler = fact.GetHandler(context, context.Request.RequestType, url, pathTranslated);       
}       
else if (ext == "rem")        8 V# W. j1 W) W
{          
System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory fact = new System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory();           * X   w/ G0 I+ v! }# k
handler = fact.GetHandler(context, context.Request.RequestType, url, pathTranslated);       
}       
else        ) J' r2 |# U, i+ G! S
{           1 W1 c, Y. d, }
throw new HttpException("Unable to process extension *." + ext);        9 [- T: ?: G/ ]
}    - o   H% B; |" C
}     6 s, x4 @& M- f. l! i( b
catch (Exception e)     % W8 A) `7 c1 ]" q" z7 ^" z' ~
{       
throw new HttpException("HitTrackingHandlerFactory", e);     , U; i   j' U8 l6 T1 Q
}     - h, H& m: @; K( S4 |* x
return (IHttpHandler)handler; ! Q8 A& O/ I3 W7 H2 C$ f, U7 U' N3 k3 r
}     $ E3 d% ~8 j- v* W+ N, b( y! T+ S
public virtual void ReleaseHandler(IHttpHandler handler) & g8 H2 K1 q' J9 E
{     return; }
} , ~; D. R* v" z
public class HitsExceededHandler: IHttpHandler # {) v& U   l. ?   d% L
{
public HitsExceededHandler() # T, I/ }4 t& h# l2 {* v7 a
{ } . {0 J4 n+ |. I7 o# R
public virtual void ProcessRequest(HttpContext context)
{     ! b, C) \& a8 G0 \0 f" e
context.Response.Write("<h1>Number of hits exceeded!</h1><p>"); * H& U' p5 l5 y& x: }
}    5 c4 a; b* M; i   J, v4 Y( E0 N
public virtual bool IsReusable  
{     get { return true; } }
} ' N/ U/ G* P8 D8 k6 o
$ L: x+ _3 I1 X) f4 X( V9 v$ Q6 I- u! E
实现IHttpModule接口
   模块提供一种事件驱动的机制与应用的事件、HTTP请求和应答对象交互。注册了的模块在HTTP handlers和handlers工厂之前初始化,以便它可以注册响应应用的事件。实际上,模块与web应用通过两种方式交互:监听应用事件、向应用激发注册了的事件,后一种方式使应用与模块紧密耦合,前者以独立组件的形式,提供了在HTTP请求的不同阶段访问请求、应答和会话对象的能力。 ) @# ]4 q/ G4 `8 V+ x
   要产生一个定制的模块,需要产生一个实现了IHttpModule接口的组件。这个接口包括两个方法:
• Init() – 为模块注册应用事件提供机会。 2 p$ C& x& v4 L( _0 J
• Dispose() – 提供清除已分配资源的机会。
   在下面的示例中,Init()方法注册到了HttpApplication 对象的PreRequestHandlerExecute, PostRequestHandlerExecute和Error 事件:
: P; n4 Y8 O0 Y% x   w: v8 p
protected void Init()
{  
application.PreRequestHandlerExecute += (new EventHandler(this.Application_PreRequestHandlerExecute));    
application.PostRequestHandlerExecute +=(new EventHandler(this.Application_PostRequestHandlerExecute)); : J) `6 I% [/ D* S6 ?
application.Error += (new EventHandler(this.Application_Error));
} 1 B/ S   G* k) M0 i7 W0 C
, m& t4 U2 ]8 m( U5 r! M
   您也可以在global.asax中用独立的应用中处理这些事件,模块可以与外部的配置建立紧密联系,使部署一套通用的事件handlers为多个应用服务成为现实。 6 l5 F' j0 S8 H1 R7 Z! s, A2 U
3 N% I2 C0 D) T
<httpModules> <add name="EventModule" type="WebHandlers.EventModule,WebHandlers"/>
<add name="ErrorHandlerModule" type="WebHandlers.ErrorHandlerModule,WebHandlers"/></httpModules> 4 d, T8 K0 t% Y, j- l   U0 q+ q

   在这个全局错误handler里,可以用捕获所有错误的模块把未捕获的异常通知给管理员。
$ a9 m; A/ b. [1 ]$ C
Handlers vs.模块 6 Y! T; R+ x+ {. L' `   `3 w# Z
    从架构的角度来看,, handlers和模块的用途有很大区别。Handlers (和工厂返回的 handlers) 提供了截获请求的途径,可以改变请求如何被处理的途径。在这方面,模块最适合做的是发送定制的应答,取消handler 的任务。它们共有的能力是:在HttpApplication 的范围内,访问HTTP请求、HTTP应答、HTTP状态对象(在某些情况下),这样它就能够操纵这些数据或者与这些数据交互。 " U" S4 \. C$ z4 G/ D5 J   [# _1 ?
   这些组件的生成顺序是: , B' z2 [+ r' U; ~
• HttpApplication 被生成 (如果必要的话)
• HttpModules被生成 (按照它们在配置文件中的出现顺序) 0 @+ a2 M) e& V5 s* J6 l
• 在控制权交给合适的handler.之前,模块将收到请求通知和认证的事件。
• HttpHandler工厂/HttpHandler被生成,用来处理请求。 1 _6 H1 x( L. [& z& u# W
• 作为HttpHandler工厂/HttpHandler处理结果的对象被生成(例如:Page、Web服务、远程对象)。
• 这些生成的对象、应用程序以及模块,它们继续通过事件交互。 : N# ?( e! w4 Q* t! x
   下图按照实例化的顺序表述了默认的行为: 1 S5 P# b9 I/ \# P: A
' V0 x6 m+ w3 y: D: R$ ?) A) D
   下图描述了定制的工厂、handlers 、模块和扩展在处理流程中的位置: ' r. ]5 O6 X3 \$ o- ?; v2 {
! l) Y6 \, D3 O$ g4 m
代码 4 x8 a9 J' x/ A# O$ Q! k* x3 u
   示例代码证明了当定制的handler 工厂、handlers 和模块都出现的时候事件的顺序。这些代码阐述了下面的内容:
• web.config 文件中的<trace>元素用来配置Web 页面如何展示跟踪输出。当页面加载的时候,您将能够看到跟踪语句,也能够通过下面的方式查看trace.axd 文件:
http://localhost/ASPNETCS/WebResources/trace.axd & [7 j# s8 `. N0 _0 T7 C' J
• 从web页面的主页,您可以点击非法的资源,查看处理*.resx, *.cs, 和 *.config请求的ForbiddenLogHandler的处理结果。
• 定制化的消息被写到了应答流,返回的数据也可以是从那些访问您的站点的客户端的您感兴趣的数据,这些数据日后可以用来做数据挖掘。 & C+ O) E! P% S: P
• 主页有一个指向非法资源的连接,点击它将产生一个应用错误。ErrorHandlerModule 被配置用来捕获这个错误,并且通过电子邮件向管理员发送通知,同时向应答流写定制的HTML,展示给用户。
• 在2个请求被HitTrackingHandlerFactory 截获处理以后,您就再也不能浏览首页了。 工厂将写一个XML文件记录某IP访问的次数,在2次访问以后,将拒绝这个IP地址。注意:目前文件路径是硬编码,所以您必须为文件建立文件夹,并且修改代码使它们匹配 。删除这个文件将会重置HitTrackingHandlerFactory 的行为。工厂也展示了如果请求被接收,如何把请求转发交给ASP.NET配置的默认行为处理。 + p6 s# d5 t* ~+ Y6 U
, z: Q, h! k+ R* t; C
总结
   这篇文章应该能给您在确定最好的架构模型提供了一些工具,它们和截获HTTP请求、修改应用的全局行为有关。最后,再提 几点建议:
• 定制化认证,为没有权限的用户提供一个来宾账户。 1 M- C( k# T8 l( z   q
• 认证前的用户能够“体验”应用。
• 使用日志行为收集有用的统计数据,便于以后完成数据挖掘。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值