现在的位置: 首页 > 综合 > 正文

ASP.net(1.1)原理学习笔记–第四章 HTTP管道 Pipeline

2011年09月05日 ⁄ 综合 ⁄ 共 28253字 ⁄ 字号 评论关闭

    ASPNET通过可扩展的HTTP
Pipeline(管道)构架来处理HTTP请求,如此一个.aspx页面就是该构架中一个终点(endpoint)(处理每个请求的—系列类中的最后一个类)。

一,请求的处理周期
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属性直接访问。

Table 4-1. Properties of HttpContext

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

Listing 4-1 Using the Current
Property of HttpContext

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 %>
<%@ 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>

  2,如果想要预编译HttpApplication派生类,可以使用code-behind技术(VS.NET中自动采用该技术),另外还可以象page指令一样使用src属性来把整个文件预编译成一个类。

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.cs
using System;
using System.Web;
using System.Web.UI;

public class MyApp : HttpApplication
{
//...
}

  4,通常要创建一个自定义的Application类,以请求为application级别事件添加handlers(处理程序)。

Listing 4-5 Tracking Request Time in a global.asax
File
<%! file: global.asax %>
<%@ 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>

  5,HttpApplication向外界提供以下每请求事件,可以在应用程序初始化时显式绑定一个事件的委托,也可以定义一个形式如
Applicastion_event 的方法,在程序运行时自动绑定。

Table 4-2. Events Exposed by
HttpApplication

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

-

  6,另外还可以把 handlers
和应用程序生命周期内其他事件绑定,只能通过定义名称与事件匹配的方法。 

Table 4-3. Additional Events Available through
global.asax

Event

Reason for
Firing

Application_Start

Application starting

Application_End

Application ending

Session_Start

User session begins

Session_End

User session
ends

  7,HttpApplication类的常用属性与方法

Listing 4-6 Members of the HttpApplication Class
public class HttpApplication : IHttpAsyncHandler, IComponent
{ // 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();///可以在请求处理期间任何时候调用,以独占性终止一个请求
// ...
}

  8,声明式对象的建立(为兼容ASP而保留的语法)
 
  可通过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 %>

<object id="MyGlobalCollection" runat="server"
scope="application" class="System.Collections.ArrayList" />

四,自定义
Handlers
(处理程序)
Handlers(处理程序)是管道的最常用的可扩展点。实际上每个.aspx页(作为请求终点的一个Page派生类)就是一个Handlers(必须实现IHttpHandler接口)。Page类可以自动使用ProcessRequest方法实现IHttpHandler接口,根据各种控件回应输出缓存,你创建.aspx页时只需要考虑控件的使用就好。

Listing 4-8 The IHttpHandler Interface
public interface IHttpHandler
{
void ProcessRequest(HttpContext ctx);
bool IsReusable {get;}
}

但是如果你想创建一个处理请求却又不基于Page类的类时,或者想创建一个更完美的处理程序,或者想使用Http管道对非ASP.NET扩展的请求提供服务,那么你都需要创建一个实现IHttpHandler接口的自定义的处理程序。

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

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只读属性决定是对每个请求分别创建实例或者共享实例。
}

第二步是设置web.config
,把上例CalcHandler类作为所有以calc.calc为终点的请求的处理程序。需要在配置文件中加入httpHandlers元素:

Listing 4-11 web.config Entry
for Calc Handler
<!� file: web.config �>
<configuration>
<system.web>
<httpHandlers>
<add verb="GET" path="calc.calc"
type="CalcHandler, CalcHandler" />
//verb="*"表示不限制请求类型
//path表示指定映射到该处理程序的终点
//type表示类型和应加载类型对请求进行服务的程序集
</httpHandlers>
</system.web>
</configuration>

最后要设置IIS,把指定扩展名的请求映射为ASP.NET
ISAPI.扩展DLL,使得如上例的.calc,能映射到.net工作者进程。

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.cs
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; }
}
}

2,.ashx扩展可以不需要配置web.config和IIS而直接自定义Handeler:,直接使用实现IHttpHandler接口的类而不是Page类。

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=multiply

3,处理程序共享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
{
IHttpHandler GetHandler(HttpContext ctx, string
requestType, string url, string translatedPath);
void ReleaseHandler(IHttpHandler handler);
}

如果要创建自定义的Pooling(共享)机制,或者对Handler传递了一个非默认构造函数而需要用某些数据对Handler进行初始化,可能就要考虑使用Handler
Factories。

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 �>
<configuration>
<system.web>
<httpHandlers>
<add verb="GET" path="calc.calc"
type="PooledCalcFactory, PoolingFactory" />
</httpHandlers>
</system.web>
</configuration>

五,自定义模块Modules
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
{
//该接口包含两个模块:Init()Dispose()
void Dispose();
//首次创建模块时调用Init方法,并以对当前HttpApplication对象的引用为参数
void Init(HttpApplication context);
}

为举例说明,我们创建一个模块TimerModule,跟踪应用程序所服务的所有请求的处理时间。(该类的逻辑等同于前文用global.asax跟踪请求处理时间的实现。实际上global.asax和modules在功能上有很大重叠,需要选择使用)

Listing 4-19 Sample Module to Collect Request Timing
Information
// File: TimerModule.cs
//
//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());
}
}

为了部署该Modules,我们要把它编译成assembly(程序集?)并放到/bin目录下或者GAC中,并且必须在web.config中添加配置httpModules
元素。如下:

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使用选择


Table 4-5. Module versus 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,如响应计时器。

六,管道中的线程Threading
ASP.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
{
public object AsyncState { get; }
public bool CompletedSynchronously { get; }
public bool IsCompleted { get; }
public WaitHandle AsyncWaitHandle { get; }
}

我们的例子存储了一个与请求关联的HttpContext对象的引用,一个对传递给BeginProcessRequest (必须在后面调用它以完成请求)的AsyncCallback委托的引用,以及可能被BeginProcessRequest的调用者使用的额外数据的通用对象引用。该类必须实现的另一个元素是同步对象,在操作完成时,线程等待它发出信号。我们运用通用技术实现的另一个元素是同步对象,在操作完成时,线程等待它发出信号。我们运用通用技术提供ManualResetEvent,它在我们的请求完成时激活,但是我们仅仅在有人请求它时才分配它。最后,我们的类有一个方便的方法,称为CompleteRequest,它触发Manual ResetEvent(如果有创建的话), 调用AsyncCallback委托, 并把我们的IsCompleted标记设为True。以下是AsyncRequestState的完整的类定义

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.

构建异步处理程序,而不构建同步处理程序,会大大增加设计的复杂性,因此,在确定是否真正需要这些类型的处理程序的功能时,应当慎重考虑。异步处理程序的目的是,在处理程

抱歉!评论已关闭.