一,请求的处理周期
1,概图: 请求-->IIS(inetinfo.exe进程)-->aspnet_isapi.dll-->已命名通道pipe-->ASP.NET工作者进程(aspnet_wp.exe)-->AppDomain.-->Http Pipe(创建一个实现IHttpHandler接口的一般是从Page派生的类)-->通过上述已命名通道发回响应输出到缓冲区(以.aspx页面为终点)。
Figure 4-1. High-Level View of Request Processing in ASP.NET
管道的内部组成(1).HttpWorkerRequest类实例的生成(包含当前请求的所有信息)
(2).传入HttpRuntime类(在应用程序的AppDomain中执行,初始化请求处理) 的静态ProcessRequest方法
(3).HttpRuntime类首先创建一个 HttpContext 类(充当各管道的粘合剂,因为它存储了当前请求的所有相关信息)的新实例,用 HttpWorkerRequest 类对其初始化
(4).HttpRuntime类接着调用HttpApplicationFactory类的静态GetApplicationInstance 方法,请求一个HttpApplication派生的类的实例。
(5).GetApplicationInstance方法要么创建一个HttpApplication类(或其派生类)的新实例,要么从应用程序池中直接取用已经存在的一个。
(6). HttpApplication类在被创建或者再取用后被初始化,并被分配以为该应用程序所定义的所有模块(实现 IHttpModule接口,为进程前后请求服务的类)。
(7). HttpRuntime类接着调用最新再取用的HttpApplication类的 BeginProcessRequest方法(为application类所实现的IHttpAsyncHandler所定义),服务于当前请求。 HttpApplication类接管请求的处理,根据URL路径来为当前请求定位适当的handler factory。例如请求一个.aspx页面,就使用 PageHandlerFactory 类。
(8). 一旦指定好适当的factory后,它就召唤 IHttpHandlerFactory 接口的 GetHandler 方法,以再取用该指定handler 类(一般是由.aspx所创建的Page派生的类,作为请求的服务终端,一般是实现了IHttpHandler 接口的类,在执行请求时填充响应缓存)的最新副本。一旦 handler 创建后,就调用它的 ProcessRequest 方法,传入当前 HttpContext 类,以访问诸如Request, the Response 等指定信息。一旦 ProcessRequest 方法返回,请求就结束了。
Figure 4-2. Classes in the HTTP Pipeline
二,HttpContext 类 (上下文类)HttpContext 类作为许多方法的参数出现,包括处理程序的ProcessRequest方法,并且可以通过Page类和Application类的context属性直接访问。
Name |
Type |
Description |
---|---|---|
Current (static) |
HttpContext |
Context for the request currently in
progress,当前服务中的HttpContext类的实例,静态属性,必须在最初的请求线程上完成
,否则多线程要自己提供对HttpContext类的访问 |
Application |
HttpApplicationState |
Application-wide property bag |
ApplicationInstance |
HttpApplication |
Active application instance |
Session |
HttpSessionState |
Per-client session state |
Request |
HttpRequest |
HTTP request object |
Response |
HttpResponse |
HTTP response object |
User |
IPrincipal |
Security ID of the caller |
Handler |
IHttpHandler |
Handler for the request |
Items |
IDictionary |
Per-request property bag 支持在管道任何位置存储和检索特定请求数据,Items集合的接口类似于所有其它属性包集合 |
Server |
HttpServerUtility |
HTTP server object |
Error |
Exception |
Unhandled exception object |
Cache |
Cache |
Application-wide cache |
Trace |
TraceContext |
Trace class for diagnostic output |
TraceIsEnabled |
Boolean |
Whether tracing is currently enabled |
WorkerRequest |
HttpWorkerRequest |
The current worker request object |
IsCustomErrorEnabled |
Boolean |
Whether custom error pages are currently enabled |
IsDebuggingEnabled |
Boolean |
Whether the current request is in debug mode |
IsInCancellablePeriod |
Boolean |
Whether the current request can still be cancelled |
public class MyClass
{
void SomeFunction()
{
HttpContext ctx = HttpContext.Current;
ctx.Response.Write("Hello, ");
string name = ctx.Request.QueryString["name"];
ctx.Response.Output.WriteLine(name);
}
}
三,HttpApplication类 (应用程序类)
1,HttpApplication类是应用程序中可用全局资源的存储库,也是某个特定应用程序请求的最初入口。可以使用在应用程序启动时自动隐含创建的 HttpApplication类的实例,或者通过HttpContext类和 Page 类的ApplicationInstance属性来访问它。
你可以通过创建一个HttpApplication派生的类、截取事件或者增加某预期辅助功能,来自定义与appliction关联的 applicatioin类。为此需要在与application关联的虚拟目录顶部放一个global.asax文件以通知系统。它在首次被访问的时候被解释与编译成一个assembly(程序集) ,此时所创建的类派生于HttpApplication。
Listing 4-2 A Sample global.asax File
<%! file: global.asax %>2,如果想要预编译HttpApplication派生类,可以使用code-behind技术(VS.NET中自动采用该技术),另外还可以象page指令一样使用src属性来把整个文件预编译成一个类。
<%@ Application Language="C#" %>
<script runat=server>
protected void Application_Start(object src, EventArgs e)///特别命名的方法定义,以处理相应事件
{ }
protected void Session_Start(object src, EventArgs e)
{ }
protected void Application_BeginRequest(object src,
EventArgs e)
{ }
protected void Application_EndRequest(object src,
EventArgs e)
{ }
protected void Application_AuthenticateRequest(object src,
EventArgs e)
{ }
protected void Application_Error(object src, EventArgs e)
{ }
protected void Session_End(object sender, EventArgs e)
{ }
protected void Application_End(object src, EventArgs e)
{ }
</script>
Listing 4-3 Using Code-Behind with global.asax
<%! file: global.asax %>
<%@ Application Inherits="MyApp" %>
Listing 4-4 Code-Behind File for global.asax
//file: myapp.cs4,通常要创建一个自定义的Application类,以请求为application级别事件添加handlers(处理程序)。
using System;
using System.Web;
using System.Web.UI;
public class MyApp : HttpApplication
{
//...
}
Listing 4-5 Tracking Request Time in a global.asax File
<%! file: global.asax %>5,HttpApplication向外界提供以下每请求事件,可以在应用程序初始化时显式绑定一个事件的委托,也可以定义一个形式如 Applicastion_event 的方法,在程序运行时自动绑定。
<%@ Application Language="C#" %>
<script language="C#" runat=server>
protected void Application_BeginRequest(object sender,
EventArgs e)
{
this.Context.Items["startTime"] = DateTime.Now;///为了防止多个请求之间混淆数据,尽量不用此HttpApplication派生类的字段来存储实例状态。否则就要每个请求都重新进行实例状态的初始化。相反可以采用HttpContent的Item集合,application范围的cache对象,或者每客户的会话状态包。
}
protected void Application_EndRequest(object sender,
EventArgs e)
{
DateTime dt = (DateTime)this.Context.Items["startTime"];
TimeSpan ts = DateTime.Now - dt;
this.Context.Response.Output.Write(
"<br/><font size=1>request processing time: {0}</font>",
ts);
}
</script>
Event |
Reason for Firing |
Order |
---|---|---|
BeginRequest |
New request received |
1 |
AuthenticateRequest |
Security identity of the user has been established |
2 |
AuthorizeRequest |
User authorization has been verified |
3 |
ResolveRequestCache |
After authorization but before invoking handler, used by caching modules to bypass execution of handlers if cache entry hits |
4 |
AcquireRequestState |
To load session state |
5 |
PreRequestHandlerExecute |
Before request sent to handler |
6 |
PostRequestHandlerExecute |
After request sent to handler |
7 |
ReleaseRequestState |
After all request handlers have completed, used by state modules to save state data |
8 |
UpdateRequestCache |
After handler execution, used by caching modules to store responses in cache |
9 |
EndRequest |
After request is processed |
10 |
Disposed |
Just before shutting down the application |
- |
Error |
When an unhandled application error occurs |
- |
PreSendRequestContent |
Before content sent to client |
- |
PreSendRequestHeaders |
Before HTTP headers sent to client |
- |
Event |
Reason for Firing |
---|---|
Application_Start |
Application starting |
Application_End |
Application ending |
Session_Start |
User session begins |
Session_End |
User session ends |
Listing 4-6 Members of the HttpApplication Class
public class HttpApplication : IHttpAsyncHandler, IComponent8,声明式对象的建立(为兼容ASP而保留的语法)
{ // Properties
public HttpApplicationState Application {get;}
public HttpContext Context {get;}
public HttpModuleCollection Modules {get;}
public HttpRequest Request {get;}
public HttpResponse Response {get;}
public HttpServerUtility Server {get;}
public HttpSessionState Session {get;}
public IPrincipal User {get;}
// Methods
public virtual void Init();
public void CompleteRequest();///可以在请求处理期间任何时候调用,以独占性终止一个请求
// ...
}
可通过object标签来定义类(.NET类/COM类均可)的实例。首先它会根据object id指定的名字在你的HttpApplication派生类中创建一个只读属性,并在首次访问时初始实例化该类,根据scope存储到 HttpApplicationState.StaticObjects 或者 the HttpSessionState.StaticObjects集合中;然后它会为.aspx页面创建的每个page派生类增加一个只读属性,以便引用每个页面的对象。
这种靠隐含增加属性以访问每个页面的全局对象的思想和.NET 基于类的编程思想是不合的,只是为了.asp程序能兼容运行。否则应该直接操作会话状态包,或者application范围的对象cache。
Listing 4-7 Using the object Tag in global.asax
<%! File: global.asax %>四,自定义 Handlers (处理程序)
<object id="MyGlobalCollection" runat="server"
scope="application" class="System.Collections.ArrayList" />
Handlers(处理程序)是管道的最常用的可扩展点。实际上每个.aspx页(作为请求终点的一个Page派生类)就是一个Handlers(必须实现IHttpHandler接口)。Page类可以自动使用ProcessRequest方法实现IHttpHandler接口,根据各种控件回应输出缓存,你创建.aspx页时只需要考虑控件的使用就好。
Listing 4-8 The IHttpHandler Interface
public interface IHttpHandler但是如果你想创建一个处理请求却又不基于Page类的类时,或者想创建一个更完美的处理程序,或者想使用Http管道对非ASP.NET扩展的请求提供服务,那么你都需要创建一个实现IHttpHandler接口的自定义的处理程序。
{
void ProcessRequest(HttpContext ctx);
bool IsReusable {get;}
}
Listing 4-9 GET-Based Calculator Request
http://localhost/httppipeline/calc.calc?a=3&b=4&op=multiply首先创建一个实现IHttpHandler接口的类,ProcessRequest在上例请求中会先查找a,b,op的值然后计算再回传给Response
Listing 4-10 Sample Calc Handler Class
// File: CalcHandler.cs第二步是设置web.config ,把上例CalcHandler类作为所有以calc.calc为终点的请求的处理程序。需要在配置文件中加入httpHandlers元素:
public class CalcHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
int a = int.Parse(ctx.Request["a"]);
int b = int.Parse(ctx.Request["b"]);
switch (ctx.Request["op"])
{
case "add":
ctx.Response.Write(a+b);
break;
case "subtract":
ctx.Response.Write(a-b);
break;
case "multiply":
ctx.Response.Write(a*b);
break;
default:
ctx.Response.Write("Unrecognized operation");
break;
}
}
public bool IsReusable { get { return true; } }//IsReusable只读属性决定是对每个请求分别创建实例或者共享实例。
}
Listing 4-11 web.config Entry for Calc Handler
<!� file: web.config �>最后要设置IIS,把指定扩展名的请求映射为ASP.NET ISAPI.扩展DLL,使得如上例的.calc,能映射到.net工作者进程。
<configuration>
<system.web>
<httpHandlers>
<add verb="GET" path="calc.calc"
type="CalcHandler, CalcHandler" />
//verb="*"表示不限制请求类型
//path表示指定映射到该处理程序的终点
//type表示类型和应加载类型对请求进行服务的程序集
</httpHandlers>
</system.web>
</configuration>
1,把如.cs的应用程序文件以"Html化"的格式显示源代码:.cs默认在IIS已能映射到.net,只需要在web.config加入
CsSourceHandler.cs作为.cs的处理程序就可。
Listing 4-12 A Custom Handler for Viewing .cs Source Files(部份代码,详见http://www.develop.com/books/essentialasp.net.附例)
// File: CsSourceHandler.cs2,.ashx扩展可以不需要配置web.config和IIS而直接自定义Handeler:,直接使用实现IHttpHandler接口的类而不是Page类。
public class CsSourceHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
try
{
StreamReader sr =
new StreamReader(ctx.Request.PhysicalPath);
// write out html and body elements first
context.Response.Output.Write("<html><body>");
// extract short file name to print at top of file
string filename = context.Request.PhysicalPath;
int idx = filename.LastIndexOf("\\");
if (idx >= 0)
filename = filename.Substring(idx+1,
filename.Length-idx-1);
context.Response.Output.Write("//File: {0}<br>",
filename);
string str;
do
{
str = sr.ReadLine();
if (str != null)
context.Response.Output.Write("{0}<br/>",
/*convert str to colorized html here*/ );
} while (str != null);
context.Response.Write("</body></html>");
}
catch (FileNotFoundException )
{
context.Response.Write("<h2>Missing file</h2>");
}
}
public bool IsReusable
{
get {return false; }
}
}
Listing 4-13 Building a Custom Handler with .ashx Files
<!� file: calc.ashx �>
<%@ WebHandler Language="C#" Class="CalcHandler" %>//使用@ WebHandler指定实现IHttpHandler接口的类
using System;
using System.Web;
public class CalcHandler : IHttpHandler//实现IHttpHandler接口的类,实现如前一例的calc.calc处理程序
{
public void ProcessRequest(HttpContext ctx)
{
int a = int.Parse(ctx.Request["a"]);
int b = int.Parse(ctx.Request["b"]);
switch (ctx.Request["op"])
{
case "add":
ctx.Response.Write(a+b);
break;
case "subtract":
ctx.Response.Write(a-b);
break;
case "multiply":
ctx.Response.Write(a*b);
break;
default:
ctx.Response.Write("Unrecognized operation");
break;
}
}
public bool IsReusable { get { return false; } }
}
Listing 4-14 Calculator Request for .ashx Handler
http://localhost/httppipeline/calc.ashx?a=3&b=4&op=multiply3,处理程序共享Handler Pooling
IHttpHandler接口通过IsReusable属性来指定是否共享一个Handler实例。因为.NET的CLR中实例化机制和垃圾回收机制比较有效,所以共享Handler没大意义。所以ASP.NET标准默认都是不执行共享的,如Page类就从IsReusable返回False,而分配页面的factory class甚至不执行 Pooling,.ashx同样也从不进行Handler Pooling。
除非是在需要大量时间设置处理程序的情况下,才考虑使用Pooling,如需要从一个数据库检索信息以执行Handler,而该信息又不会随请求而变动。
4,自定义处理程序工厂Handler Factories
如果需要对自定义处理程序有更多的控制,可以写一个实现IHttpHandlerFactory 实例的类(Handler Factories),Handler Factories的部署和Handler相同,指定使用Handler Factories创建Handler实例。
Listing 4-15 IHttpHandlerFactory Interface
public interface IHttpHandlerFactory如果要创建自定义的Pooling(共享)机制,或者对Handler传递了一个非默认构造函数而需要用某些数据对Handler进行初始化,可能就要考虑使用Handler Factories。
{
IHttpHandler GetHandler(HttpContext ctx, string
requestType, string url, string translatedPath);
void ReleaseHandler(IHttpHandler handler);
}
Listing 4-16 A Custom Pooling Factory for the Calc Handler
// File: PoolingFactory.cs实现共享使用Stack的集合类的一个静态实例它的配置文件如下:
public class PooledCalcFactory : IHttpHandlerFactory
{
const int cPoolSize = 10;//元素个数最多十个
// Static stack of CalcHandler instances
private static Stack _handlers = new Stack(cPoolSize);
// GetHandler returns a CalcHandler instance from
// the static stack, if available, or a new instance
public IHttpHandler GetHandler(HttpContext ctx,
string requestType, string url, string translatedPath)
{
IHttpHandler handler = null;
// Acquire a lock on the SyncBlock associated with our
// Type object to prevent concurrent access to our
// static stack
lock (this.GetType())
{
// if handler is available on stack, pop it
if (_handlers.Count > 0)
handler = (IHttpHandler) _handlers.Pop();
}
// if no handler was available, create new instance
if (handler == null)
handler = new CalcHandler();
return handler;
}
// ReleaseHandler puts a handler back on the static
// stack, if the handler is reusable and the stack
// is not full
public void ReleaseHandler(IHttpHandler handler)
{
if (handler.IsReusable)
lock(this.GetType())
{
if (_handlers.Count < cPoolSize)
_handlers.Push(handler);
}
}
}
Listing 4-17 Configuration File for Specifying a Custom Handler Factory
<!� File: web.config �>五,自定义模块Modules
<configuration>
<system.web>
<httpHandlers>
<add verb="GET" path="calc.calc"
type="PooledCalcFactory, PoolingFactory" />
</httpHandlers>
</system.web>
</configuration>
Modules是Http管道最强大的自定义模块。Modules在第一个application 被创建时也被创建,并且存在于application 整个生命周期内。Modules可以引入任何一个HttpApplication。Modules常用来处理请求的预处理和后加工(类似于IIS的ISAPI过滤器)。
ASP.NET本身也用Modules实现许多application 级的功能,如下,身份验证,授权,输出缓存,进程外会话状态管理等。
Table 4-4. Modules Defined in ASP.NET
|
Purpose |
---|---|
OutputCacheModule |
Page-level output caching页面级输出缓存 |
SessionStateModule |
Out-of-process session state management |
WindowsAuthenticationModule |
Client authentication using integrated Windows authentication |
FormsAuthenticationModule |
Client authentication using cookie-based forms authentication |
PassportAuthenticationModule |
Client authentication using MS Passport |
UrlAuthorizationModule |
Client authorization based on requested URL |
FileAuthorizationModule |
Client authorization based on requested file |
注意,这些服务都要求在把请求发送到一个Handler之前连接一下管道pipeline,这是为了接进请求处理管道,并潜在地修改,放弃或者变更当前请求。
要构建自定义的模块,第一步是构建一个实现IHttpModule 接口的类。Listing 4-18 IHttpModule Interface
public interface IHttpModule为举例说明,我们创建一个模块TimerModule,跟踪应用程序所服务的所有请求的处理时间。(该类的逻辑等同于前文用global.asax跟踪请求处理时间的实现。实际上global.asax和modules在功能上有很大重叠,需要选择使用)
{
//该接口包含两个模块:Init() 和 Dispose()
void Dispose();
//首次创建模块时调用Init方法,并以对当前HttpApplication对象的引用为参数
void Init(HttpApplication context);
}
Listing 4-19 Sample Module to Collect Request Timing Information
// File: TimerModule.cs为了部署该Modules,我们要把它编译成assembly(程序集?)并放到/bin目录下或者GAC中,并且必须在web.config中添加配置httpModules 元素。如下:
//
//TimerModule类,将跟踪HttpContext类中Items集合中的开始请求时间,并创建一个包含计时信息的自定义头文件。
public class TimerModule : IHttpModule
{
public void Dispose() {}
public void Init(HttpApplication httpApp)
{
// subscribe delegates to the BeginRequest and
// EndRequest events of the HttpApplication class
httpApp.BeginRequest +=
new EventHandler(this.OnBeginRequest);
httpApp.EndRequest +=
new EventHandler(this.OnEndRequest);
}
public void OnBeginRequest(object o, EventArgs ea)
{
HttpApplication httpApp = o as HttpApplication;
// record time that event was handled in
// per-request Items collection
httpApp.Context.Items["sTime"] = DateTime.Now;
}
public void OnEndRequest(object o, EventArgs ea)
{
HttpApplication httpApp = o as HttpApplication;
DateTime dt = (DateTime)httpApp.Context.Items["sTime"];
// measure time between BeginRequest event
// and current event
TimeSpan ts = DateTime.Now - dt;
httpApp.Context.Response.AddHeader("RequestTiming",
ts.ToString());
}
}
Listing 4-20 Timer Module Configuration
<!� File: web.config �>//假设已经编译并放置好assembly程序集TimerModule
<configuration>
<system.web>
<httpModules>
//add的name属性是Module名,type属性前面是类名(完全限定的命名空间,后面是assembly名)
<add name="Timer"
type="TimerModule, TimerModule" />
</httpModules>
</system.web>
</configuration>
1,模块Modules作为Request和Response的过滤器Filters:如向处理的所有页面添加公共脚注,向输出的缓存滤去某些部份。
第一步,创建一个派生于Stream的类,并在Application_BeginRequest请求开始时创建一个实例并以此作为Request/Response的Filter
属性。原始流保存于自定义流类中,而新的流类就成为原始流和任何读写流之间的过滤器。
为举例说明,我们创建一个ASP.NET错误输出过滤模块,以达到错误诊断信息输出到一个文件中,而非原来的Response流中。
Listing 4-21 Custom Stream Class to Redirect Trace Output
public class TraceRedirectStream : Stream
{
private Stream _primaryStream;//首先调用_primaryStream 并初始化它为实际的Response流
private Stream _otherStream;//调用_otherStream并初始化它为用以写入信息的文件流
private bool _inTrace = false;
private string _requestUrl;
// Signals the start of the trace information in a page
private const string _cStartTraceStringTag =
"<div id=\"__asptrace\">";
//用文档记录什么请求产生了信息
public TraceRedirectStream(Stream primaryStream,
Stream otherStream,
string requestUrl )
{
_primaryStream = primaryStream;
_otherStream = otherStream;
_requestUrl = requestUrl;
}
private void WriteLine(Stream s, string format,
params object[] args )
{
string text = string.Format(format +
Environment.NewLine, args);
byte[] textBytes = Encoding.ASCII.GetBytes(text);
s.Write(textBytes, 0, textBytes.Length);
}
public override bool CanRead
{
get { return(_primaryStream.CanRead); }
}
public override bool CanSeek
{
get { return(_primaryStream.CanSeek); }
}
public override bool CanWrite
{
get { return(_primaryStream.CanWrite); }
}
public override long Length
{
get { return(_primaryStream.Length); }
}
public override long Position
{
get { return(_primaryStream.Position); }
set
{
_primaryStream.Position = value;
_otherStream.Position = value;
}
}
public override long Seek(long offset,
SeekOrigin direction)
{
return _primaryStream.Seek(offset, direction);
}
public override void SetLength(long length)
{
_primaryStream.SetLength(length);
}
public override void Close()
{
_primaryStream.Close();
_otherStream.Close();
}
public override void Flush()
{
_primaryStream.Flush();
_otherStream.Flush();
}
public override int Read( byte[] buffer, int offset,
int count )
{
return _primaryStream.Read(buffer, offset, count);
}
public override void Write( byte[] buffer, int offset,
int count )
{
if (_inTrace)
{
// if we are writing out trace information,
// it is always the last part of the output stream,
// so just continue writing to the log file until
// the request completes.
_otherStream.Write(buffer, offset, count);
}
else
{
// We are not currently writing out trace information,
// so as we write response information, look for the
// trace start string, and begin writing to the trace
// log if we encounter it. Scan the entire buffer
// looking for the trace start tag.
int idx = FindTraceStartTag(buffer, offset, count);
if (idx > 0) // if non-negative, start tag found
{
WriteLine(_otherStream,
"<hr/><h3>Request URL: {0}</h3>",
_requestUrl);
_inTrace = true;
// write non-trace portion of buffer to primary
// response stream
_primaryStream.Write(buffer, offset, idx);
// write trace portion to other stream (log)
_otherStream.Write(buffer, idx+offset, count - idx);
}
}
}
public override int ReadByte()
{
int b = _primaryStream.ReadByte();
_otherStream.Position = _primaryStream.Position;
return(b);
}
public override void WriteByte( byte b )
{
if (this._inTrace)
_otherStream.WriteByte(b);
else
_primaryStream.WriteByte(b);
}
//FindTraceStartTag,扫描被写文件,找到表示跟踪输出开始的字符串。
private int FindTraceStartTag(byte[] buffer, int offset,
int count)
{
int bufIdx = offset;
int ret = -1;
while ((bufIdx < count+offset) && (ret < 0))
{
if (buffer[bufIdx] ==
TraceRedirectStream._cStartTraceStringTag[0])
{
int i=1;
while ((i <
TraceRedirectStream._cStartTraceStringTag.Length)
&& (bufIdx+i < count+offset))
{
if (buffer[bufIdx+i] !=
TraceRedirectStream._cStartTraceStringTag[i])
break;
i++;
}
if (i >=
TraceRedirectStream._cStartTraceStringTag.Length)
ret = bufIdx;
} // if (buffer[bufIdx]...
bufIdx++;
} // while (bufIdx < ...
return ret;
} // private int FindTraceStartTag...
} // public class TraceRedirectStream...
接下来的任务是把这个流安装到一个Module中,自定义HttpModule为BeginRequest和EndRequest事件增加了handlers以安装该流,并在请求结束时关闭该流。该自定义模块如下:
Listing 4-22 TraceDumpModule Class
public class TraceDumpModule : IHttpModule
{
private TraceRedirectStream _responseStream;
private string _logFileName;
public void Init( HttpApplication httpApp )
{
_logFileName = @"C:\temp\tracelog.htm";
httpApp.BeginRequest +=
new EventHandler(OnBeginRequest);
httpApp.EndRequest += new EventHandler(OnEndRequest);
}
void OnBeginRequest( object sender, EventArgs a )
{
HttpApplication httpApp = sender as HttpApplication;
FileInfo fiLogFile;
if( File.Exists(_logFileName) )
fiLogFile = new FileInfo(_logFileName);
// Open the log file (for appending) and log
// any trace output made to that request.
//
Stream responseLog = File.Open( _logFileName,
FileMode.Append, FileAccess.Write );
long pos = httpApp.Request.InputStream.Position;
CopyStream(httpApp.Request.InputStream, responseLog);
httpApp.Request.InputStream.Position = pos;
// Set the response filter to refer to the trace
// redirect stream bound to the original response
// stream and the log file. As this stream processes
// the response data, it will selectively send non-
// trace output to the original stream, and trace
//output to the log file
//
_responseStream =
new TraceRedirectStream(httpApp.Response.Filter,
responseLog, httpApp.Request.Url.ToString());
httpApp.Response.Filter = _responseStream;
}
void OnEndRequest( object sender, EventArgs a )
{
if( _responseStream != null )
_responseStream.Close();
}
void CopyStream( Stream inStream, Stream outStream )
{
byte[] buf = new byte[128];
int bytesRead = 0;
while ((bytesRead=inStream.Read(buf, 0, buf.Length))
> 0 )
{
outStream.Write(buf, 0, bytesRead);
}
}
public void Dispose()
{}
}
2,模块共享(池)Module Pooling
Module总是共享的,(Handler只有创建自定义Handler并且IsReusable为True才共享)。当一个HttpApplication派生的类创建时,就自动生成一个Module集合,所以不要用Module来保存请求之间的任何状态(如同前文所提的application类一样)。
3,模块与global.asax使用选择
Feature |
Module |
global.asax |
---|---|---|
Can receive event notifications for all HttpApplication-generated events |
Yes |
Yes |
Can receive event notifications for Session_Start/_End, Application_Start/_End |
No |
Yes |
Can be deployed at the machine level |
Yes |
No |
Supports declarative object instantiation
支持声明式对象的实例化 |
No |
Yes |
Moduels的重要优点就是可以在机器级上进行部署,通过在GAV中部署Moduels的Assemble,就能把Moduels直接增加到机器级的machine.config中,也可以单独倍增application的web.config文件。而不需要把Assemble复制到每个application的/bin目录下。
Global.asax的主要优点就是,支持每个应用程序/会话在开始/结束时发出的额外事件。另外,它还支持声明式对象的实例化,但也因此为许多开始者诟病。
因此,如果是某个application特有的特征,就把它放入Global.asax中直接编译,如果是多个application都有用的特征,就可以写入创建一个Module,如响应计时器。
六,管道中的线程ThreadingASP.NET为工作者进程中驻留的每个application分配一个AppDomain,不同页面的请求在同一个线程内自动创建不同的Page类实例,并自动应用application和Module的不同实例对每个请求进行服务。所以开发者本身不需要考虑太多多线程编程环境。但是为了不做出对application任何一个状态做出错误的并发访问的假设,就要理解ASP.NET如何用线程服务于每个请求。
首先,ASP.NET用进程范围内的CLR线程池(其大小在machine.config的processModule中配置,默认为25工作者进程25I/O进程)服务于请求。
在Win2000/WinXP中,为了效率,主要在源自CLR线程池的I/O线程上服务请求。每个请求都是在IIS进程中(inetinfo.exe)中,通过异步写入ISAPI扩展DLL(aspnet_isapi.dll)中的一个已命名管道而被启动。当ASP.NET工作者进程(aspnet_wp.exe)接收到该异步写时,就在一个I/O线程上对它进行处理,因此,为了避开线程切换,通常直接在那个线程上对请求进行服务。
而到了Win2003和IIS6.0中,ASP.NET与系统更加紧密集成,在IIS6.0中没有专门的asp.net工作者进程,因为它已经被集成到IIS6.0暴露的进程模型中。该模型使你能够指定一个特定的虚拟目录是存在于一个不同的工作者进程(w3wp.exe)中,还是存在于由其它虚拟目录共享的工作者进程中。在这种情况下,asp.net在从进程范围的CLR线程池中取出的工作者线程上对请求进行服务。
对于每个输入请求,创建相应的HttpApplication派生类的一个新实例。这是application的关联模块。为了避开频繁地重新分配application和Module,每个AppDomain维护一个application和module。application池的大小最大等于线程池的大小,因此默认每个工作者进程最多能并发处理25个请求,每个进程都有它自己的application和moduls集。如下图,是asp.net工作者进程某时刻的快照。
在该构架中,有几个方面可能会对应用程序的构造产生影响。首先,在一个应用程序中,应用程序和模块被实例化多次的事实,意味着你绝不应该依赖于为应用程序或者模块类增加字段或者其他状态,因为要对它进行复制,并且不会如你想像的那样在多个请求间共享。相反,使用管道中许多可用状态存储库中的一个库,诸如应用程序范围的缓存、会话状态包、应用程序状态包、或者HttpContext类的每请求项目集。此外,在默认情况下,许多为了服务请求而创建的处理程序都是不被共享的。正如我们所知道的,你可以共享处理程序,甚至可以通过IHttpHandler的IsReusable属性在每处理程序的基础上控制共享。但是唯一被隐含共享的处理程序是自定义处理程序,对此在编写时没有指定处理程序工厂。PageHandlerFactory类不执行共享,SimpleHandlerFactory工厂类也不执行共享,但是它实例化.aspx定义的处理程序。因此,每个请求通常用相应的处理程序类的一个最新分配的实例进行服务,并且请求完成后被丢弃。
1,异步处理程序
如前所述,对处理程序的ProscessRequest方法调用一般都是在管道中执行请求的线程环境中异步进行的。然而可能你需要该调用同步发生,允许在处理程序执行工作的同时将主要的请求处理线程返还给线程池。在这些情况下,存在另一个派生于IHttpHandler的处理程序接口IHttpAsyncHandler,如下:
Listing 4-23 The IHttpAsyncHandler Interface
public interface IHttpAsyncHandler : IHttpHandler
{
//应用程序调用BeginProcessRequest方法而不直接调用ProcessRequest
//然后处理程序启动一个新线程来处理请求
//并立即从ProcessRequest返回并传回一个对实现IAsyncResult类的引用,使运行时能够检查操作何时完成
IAsyncResult BeginProcessRequest(HttpContext ctx,
AsyncCallback cb,
object obj);
//另一个方法是EndProcessRequest,在请求处理完成时被调用,在需要时可清除所有已分配资源。
void EndProcessRequest(IAsyncResult ar);
}
实现异步处理程序的最直接方法是使用异步委托调用,或者用执行请求处理的方法调用ThreadPool.QueueUserWorkItem。遗憾的时用任何一种方法都会彻底败坏构建一个异步处理程序的目的,因为它们都是从同一个进程范围的CLR线程池中取出的,被ASPNET用户服务请求。虽然主要的请求线程的确可以释放,并返还给线程池,但是可以从池中取出另一个线程以实现异步委托执行(或者工作项完成),导致没有获得任何线程对其他请求进行服务,从而产生无用处理程序的异步性质。
因此,为了构建一个真正有效的异步处理程序,必须人工产生一个额外的线程,以响应BeginProcessRequest(或者在更好的情况下,使用另一个线程池中的因线程,对此将在后文讨论)。要构建一个成功的异步处理程序,需要考虑以下三个重要方面:
1构造一个支持IASyncResult从BeginProcessRequest返回的类
2产生一个异步执行请求处理的线程
3通知ASPNET你已经处理完请求,并准备返回响应我们从构建一个支持IASyncResult的类开始,该类将从对BeginProcessRequest的调用中返回,而后传递给EndProcessRequest的实现,因此,该类是存储我们在处理一个请求期间可能需要使用的特定请求状态的好场所。IASyncResult接口如下所示:
Listing 4-24 The IAsyncResult Interface
public interface IAsyncResult我们的例子存储了一个与请求关联的HttpContext对象的引用,一个对传递给BeginProcessRequest (必须在后面调用它以完成请求)的AsyncCallback委托的引用,以及可能被BeginProcessRequest的调用者使用的额外数据的通用对象引用。该类必须实现的另一个元素是同步对象,在操作完成时,线程等待它发出信号。我们运用通用技术实现的另一个元素是同步对象,在操作完成时,线程等待它发出信号。我们运用通用技术提供ManualResetEvent,它在我们的请求完成时激活,但是我们仅仅在有人请求它时才分配它。最后,我们的类有一个方便的方法,称为CompleteRequest,它触发Manual ResetEvent(如果有创建的话), 调用AsyncCallback委托, 并把我们的IsCompleted标记设为True。以下是AsyncRequestState的完整的类定义
{
public object AsyncState { get; }
public bool CompletedSynchronously { get; }
public bool IsCompleted { get; }
public WaitHandle AsyncWaitHandle { get; }
}
Listing 4-25 The AsyncRequestState Class Definition
class AsyncRequestState : IAsyncResult
{
public AsyncRequestState(HttpContext ctx,
AsyncCallback cb,
object extraData )
{
_ctx = ctx;
_cb = cb;
_extraData = extraData;
}
internal HttpContext _ctx;
internal AsyncCallback _cb;
internal object _extraData;
private bool _isCompleted = false;
private ManualResetEvent _callCompleteEvent = null;
internal void CompleteRequest()
{
_isCompleted = true;
lock (this)
{
if (_callCompleteEvent != null)
_callCompleteEvent.Set();
}
// if a callback was registered, invoke it now
if (_cb != null)
_cb(this);
}
// IAsyncResult interface property implementations
public object AsyncState
{ get { return(_extraData); } }
public bool CompletedSynchronously
{ get { return(false); } }
public bool IsCompleted
{ get { return(_isCompleted); } }
public WaitHandle AsyncWaitHandle
{
get
{
lock( this )
{
if( _callCompleteEvent == null )
_callCompleteEvent = new ManualResetEvent(false);
return _callCompleteEvent;
}
}
}
}
下一步是产生一个新线程,我们在该线程上处理我们的请求。在该新线程上调用的方法,可能需要访问AsyncRequestState类中缓存的状态。但遗憾的是,.NET 中用于产生新线程的ThreadStart委托不带任何参数。为了克服这一缺陷,我们创建了另一个类,将必须缓存的状态作为数据成员(在本例中,只是对该请求的AsyncRequestState对象的引用),还有一个实例方法,用于初始化 ThreadStart 委托。注意,我们在该类中定义的ProcessRequest方法,是将从我们人工创建的线程中调用的方法。而且在它完成时,通过调用 AsyncRequestState 对象上的CompleteRequest,通知请求处理已经完成。
Listing 4-26 The AsyncRequest Class Definition
class AsyncRequest
{
private AsyncRequestState _asyncRequestState;
public AsyncRequest(AsyncRequestState ars)
{
_asyncRequestState = ars;
}
public void ProcessRequest()
{
// This is where your non-CPU-bound
// activity would take place, like accessing a Web
// service, polling a slow piece of hardware, or
// performing a lengthy database operation.
// We put the thread to sleep for 2 seconds to simulate
// a lengthy operation.
Thread.Sleep(2000);
_asyncRequestState._ctx.Response.Write(
"<h1>Async handler responded</h1>");
// tell asp.net we are finished processing this request
_asyncRequestState.CompleteRequest();
}
}
最后,我们准备构建异步处理程序本身,我们把该类叫做AsyncHandler,它必须实现前面所示的IHttpAsyncHandler接口(派生于IHttpHandler) 的所有方法(总共有四个)。我们没友使用fProcessRequest方法而且绝不会调用它,因为我们实现了BeginProcessRequest,在BeginProcessRequest中,我们创建了AsyncRequestState类的一个新实例,并用HttpContext对象, AsyncCallback委托、以及作为参数传递的通用对象引用对它进行初始化。然后准备一个全新的AsyncRequest对象,用最新创建的AsyncRequestState进行初始化,并启动一个新线程。在本例中,我们的EndProcessRequest实现没有做任何事情,但是一般可以用它执行任何清除或者最后时刻的附加响应。AsyncHandler类定义如下:
Listing 4-27 The AsyncHandler Class Definition
public class AsyncHandler : IHttpAsyncHandler
{
public void ProcessRequest(HttpContext ctx)
{
// not used
}
public bool IsReusable
{
get { return false;}
}
public IAsyncResult BeginProcessRequest(HttpContext ctx,
AsyncCallback cb,
object obj)
{
AsyncRequestState reqState =
new AsyncRequestState(ctx, cb, obj);
AsyncRequest ar = new AsyncRequest(reqState);
ThreadStart ts = new ThreadStart(ar.ProcessRequest);
Thread t = new Thread(ts);
t.Start();
return reqState;
}
public void EndProcessRequest(IAsyncResult ar)
{
// This will be called on our manually created thread
// in response to our calling ASP.NET's AsyncCallback
// delegate once our request has completed processing.
// The incoming
// IAsyncResult parameter will be a reference to the
// AsyncRequestState class we built, so we can access
// the Context through that class if we like.
// Note - you *cannot* access the current context
// using the HttpContext.Current property, because we
// are running on our own thread, which has not been
// initialized with a context reference.
AsyncRequestState ars = ar as AsyncRequestState;
if (ars != null)
{
// here you could perform some cleanup, write
// something else to the Response, or do whatever
// else you needed
}
}
}
如果现在构建该处理程序,并把它注册成为一个终点(如我们在前一个处理程序中那样),它就可以以异步方式,成功地处理所有来自 ASP.NET的调用请求线程的请求。.如下显示了把一个请求映射到异步处理程序时发生的事件顺序。首先,应用程序类注意到处理程序实现了IHttpAsyncHandler,因此,它不是调用同步ProcessRequest方法,而是激活处理程序上的 BeginProcessRequest,传递当前上下文和一个异步回调委托,使我们的处理程序在完成时调用。然后,处理程序创建一个新的 AsyncRequestState 对象,并用传递给BeginProcessRequest的参数进行初始化。接着,处理程序又创建一个新的 AsyncRequest对象,并用AsyncRequestState对象进行初始化,然后用 AsyncRequest.ProcessRequest 方法作为入口点启动一个新线程。然后,处理程序把 AsyncRequestState对象返回给应用程序对象,调用线程返回给线程池,而我们人工创建的线程继续处理请求。
Figure 4-6. Asynchronous Handler Operation桺hase 1: The Handoff
一旦AsyncRequest对象的ProcessRequest方法执行完冗长的任务,它就调用AsyncRequestState类的CompleteRequest 方法。而这又依次激活最初传递给BeginProcessRequest方法的AsyncCallback委托,通知响应已经准备就绪,并且准备返回。AsyncCallback委托进行的第一件事情是调用异步处理程序类上的EndProcessRequest方法。调用返回后,AsyncCallback委托通过发回准备好的响应,触发请求的完成。注意,所有的这一切处理都是在处理程序中创建的辅助线程上发生的,而不是在线程池线程上发生的。如下显示了完成对异步处理程序的请求步骤:
Figure 4-7. Asynchronous Handler Operation桺hase 2: Request Completion
One problem remains with our asynchronous handler implementation梚t has the potential to create an unbounded number of threads. If many requests are made to our asynchronous handler, all of which take a significant amount of time to service, we could easily end up creating more threads than the underlying operating system could handle. To deal with this, we need to provide a secondary thread pool to service our asynchronous requests in a bounded fashion. The mechanics of creating custom thread pools are beyond the scope of this chapter, but a fully operational asynchronous handler using a supplemental thread pool is available for download in the online samples for this book.
构建异步处理程序,而不构建同步处理程序,会大大增加设计的复杂性,因此,在确定是否真正需要这些类型的处理程序的功能时,应当慎重考虑。异步处理程序的目的是,在处理程序处理原始请求的同时,释放一个ASP.NET线程池的线程以对其他请求进行服务。只有在对请求进行服务的工作需要大量非CPU绑定的时间才能完成时,这样做才有意义。例如,如果一个请求的完成依赖于几个远程过程调用的完成,或者依赖于WEB服务调用,则这种情况可以作为实现异步处理程序的候选。如果构建异步处理程序对CPU集中的请求进行服务,则只会增加与ASP.NET线程池中的线程的竞争,而且可能会降低请求的总体处理时间。
七,小结:
在ASP.NET中,请求由大量 HTTP pipeline进行服务。管道有三个主要扩展点:自定义applications, 自定义 handlers, 和自定义modules. 自定义application 通过创建一个新的派生于HttpApplication的类而建成,这在你创建一个global.asax文件时自动发生。通过捕获事件并对它们作出响应,自定义 applications可以用来执行程序级的任务。通过创建一个实现IHttpHandler接口的新类,并在应用程序的配置文件中注册它,可以构建一个自定义applications。如果Page处理构架的规则是不必要的,则自定义处理程序可以代替Page类,用作一个请求的终点。通过创建一个实现IHttpModule 接口的新类,并在那个类中的所有应用程序级事件增加事件处理程序,就构建了一个自定义模块。在ASP.NET ,自定义模块主要用于执行通用过滤和监督。
ASP.NET的大多数工作可以不考虑多线程问题下发生,因为管道中的大多数类,每次只在实例级上被一个线程使用。然而,重要的是理解模块和应用程序都可以被共享,因此,用任何一种方式存储跨请求的状态都是不明智的做法。在某些情况下,可能需要构建异步处理程序,以免阻塞ASP.NET工作者线程池,导致花很长时间才能完成请求。