HttpModule是ASP.NET提供的一个非常有用的功能扩展手段。通过HttpModule我们可以简单的在HttpApplication实例上挂接事件,通过响应HttpApplication触发的事件使我们就机会在Session建立或者销毁、请求处理之前或者请求处理之后等一些特殊的时机做一些额外的工作。使用HttpModule的好处是我们不需要将这些额外工作的代码显示的加入到业务代码中,是我们能够透明的增加额外的功能。微软自生也提供了大量的HttpModule的默认实现,例如: DefaultAuthenticationModule、FileAuthorizationModule等。
虽然HttpModule应用的非常广泛,但是我们基本上没有考虑过HttpModule的生存周期。在写这篇文章之前,我一直认为HttpModule 对象的生存周期与Application的生存周期一致,也就是一个Application中只有一个HttpModule。但是通过我对微软的代码进行反射分析,发现HttpModule的生存周期与HttpApplication的生存周期是一致的,代码如下:
1
internal
void
InitInternal()
2
{
3
this
._state
=
state;
4
PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);
5
this
._initContext
=
context;
6
this
._initContext.ApplicationInstance
=
this
;
7
try
8
{
9
try
10
{
11
context.ConfigPath
=
context.Request.ApplicationPath;
12
using
(HttpContextWrapper wrapper
=
new
HttpContextWrapper(context))
13
{
14
this
.InitModules();
15
if
(handlers
!=
null
)
16
{
17
this
.HookupEventHandlersForAppplicationAndModules(handlers);
18
}
19
this
._context
=
context;
20
this
._hideRequestResponse
=
true
;
21
try
22
{
23
this
.Init();
24
}
25
catch
(Exception exception)
26
{
27
this
.RecordError(exception);
28
}
29
}
30
31
32
private
void
InitModules()
33
{
34
HttpModulesConfiguration appConfig
=
(HttpModulesConfiguration) HttpContext.GetAppConfig(
"
system.web/httpModules
"
);
35
if
(appConfig
==
null
)
36
{
37
throw
new
HttpException(HttpRuntime.FormatResourceString(
"
Missing_modules_config
"
));
38
}
39
this
._moduleCollection
=
appConfig.CreateModules();
40
int
count
=
this
._moduleCollection.Count;
41
for
(
int
i
=
0
; i
<
count; i
++
)
42
{
43
this
._moduleCollection[i].Init(
this
);
44
}
45
GlobalizationConfig config
=
(GlobalizationConfig) HttpContext.GetAppConfig(
"
system.web/globalization
"
);
46
if
(config
!=
null
)
47
{
48
this
._appLevelCulture
=
config.Culture;
49
this
._appLevelUICulture
=
config.UICulture;
50
}
51
}
通过代码可以发现每次创建HttpApplication的时候都回初始化HttpModule。接下来我们在看HttpApplication的生存周期,代码如下:HttpRuntime类的ProcessRequest方法,这个方法是Web请求的入口方法。
注意:一个HttpRuntime对应一个WebApplication。在Windows2003下(IIS6),下一个w3wp.exe对应一个ApplicationPool,一个ApplicationPool中可以容纳多个WebApplication,每一个WebApplication对应一个AppDomain。如果是XP的话所有的WebApplication都处于iis_asp.exe(名字记得不太清楚了)这个进程中,使用AppDomain来分隔不同的AppDomain.
1
public
static
void
ProcessRequest(HttpWorkerRequest wr)
2
{
3
InternalSecurityPermissions.AspNetHostingPermissionLevelMedium.Demand();
4
if
(wr
==
null
)
5
{
6
throw
new
ArgumentNullException(
"
custom
"
);
7
}
8
RequestQueue queue
=
_theRuntime._requestQueue;
9
if
(queue
!=
null
)
10
{
11
wr
=
queue.GetRequestToExecute(wr);
12
}
13
if
(wr
!=
null
)
14
{
15
CalculateWaitTimeAndUpdatePerfCounter(wr);
16
ProcessRequestNow(wr);
17
}
18
}
注意下划线标注出来的两个方法。其中GetRequestToExecute方法去检查当前的线程池,判断是否有空闲的线程来处理这个请求,如果没有空闲的线程处理这个请求则将请加入到等待请求队列。接下来我们进一步的分析ProcessRequestNow这个方法。我们最终可以追踪到请求最终在HttpRuntime类的ProcessRequestInternal方法中被处理,代码如下:
1
private
void
ProcessRequestInternal(HttpWorkerRequest wr)
2
{
3
HttpContext extraData
=
new
HttpContext(wr,
false
);
4
wr.SetEndOfSendNotification(
this
._asyncEndOfSendCallback, extraData);
5
Interlocked.Increment(
ref
this
._activeRequestCount);
6
try
7
{
8
if
(
this
._beforeFirstRequest)
9
{
10
lock
(
this
)
11
{
12
if
(
this
._beforeFirstRequest)
13
{
14
this
._firstRequestStartTime
=
DateTime.UtcNow;
15
this
.FirstRequestInit(extraData);
16
this
._beforeFirstRequest
=
false
;
17
}
18
}
19
}
20
extraData.Impersonation.Start(
true
,
false
);
21
try
22
{
23
extraData.Response.InitResponseWriter();
24
}
25
finally
26
{
27
extraData.Impersonation.Stop();
28
}
29
IHttpHandler applicationInstance
=
HttpApplicationFactory.GetApplicationInstance(extraData);
30
if
(applicationInstance
==
null
)
31
{
32
throw
new
HttpException(FormatResourceString(
"
Unable_create_app_object
"
));
33
}
34
if
(applicationInstance
is
IHttpAsyncHandler)
35
{
36
IHttpAsyncHandler handler2
=
(IHttpAsyncHandler) applicationInstance;
37
extraData.AsyncAppHandler
=
handler2;
38
handler2.BeginProcessRequest(extraData,
this
._handlerCompletionCallback, extraData);
39
}
40
else
41
{
42
applicationInstance.ProcessRequest(extraData);
43
this
.FinishRequest(extraData.WorkerRequest, extraData,
null
);
44
}
45
}
46
catch
(Exception exception)
47
{
48
extraData.Response.InitResponseWriter();
49
this
.FinishRequest(wr, extraData, exception);
50
}
51
}
我们继续来关注HttpApplicationFactory.GetApplicationInstance(extraData)这个代码。继续追踪,最终定位到HttpApplicationFactory类的GetNormalApplicationInstance方法和CreateNonPublicInstance方法,代码如下:
1
private
HttpApplication GetNormalApplicationInstance(HttpContext context)
2
{
3
HttpApplication application
=
null
;
4
lock
(
this
._freeList)
5
{
6
if
(
this
._numFreeAppInstances
>
0
)
7
{
8
application
=
(HttpApplication)
this
._freeList.Pop();
9
this
._numFreeAppInstances
--
;
10
}
11
}
12
if
(application
==
null
)
13
{
14
application
=
(HttpApplication) HttpRuntime.CreateNonPublicInstance(
this
._theApplicationType);
15
context.Impersonation.Start(
true
,
false
);
16
try
17
{
18
try
19
{
20
application.InitInternal(context,
this
._state,
this
._eventHandlerMethods);
21
return
application;
22
}
23
finally
24
{
25
context.Impersonation.Stop();
26
}
27
}
28
catch
29
{
30
throw
;
31
}
32
}
33
return
application;
34
}
35
36
internal
static
object
CreateNonPublicInstance(Type type)
37
{
38
return
CreateNonPublicInstance(type,
null
);
39
}
看到没有,如果在_freeList中没有空闲的HttpApplication的时候就会创建新的HttpApplication,并且这个Application在Session结束的时候会被回收,具体的代码可以查看相关代码。在这段代码中我们又发现了InitInternal这个面熟的方法,就是在这个方法中初始化HttpModule的,事情到这里就明白了,HttpModule的生存周期是跟HttpApplication的生存周期一致的,并且在同一个WebApplication中可能同时存在多个HttpApplication的实例。如果处理了Global.asax,也就是特殊HttpApplcation的话最大数目大概是20,如果是标准的HttpApplication的话最大数目是100,代码如下:
1
private
void
RecycleNormalApplicationInstance(HttpApplication app)
2
{
3
if
(
this
._numFreeAppInstances
<
100
)
4
{
5
lock
(
this
._freeList)
6
{
7
this
._freeList.Push(app);
8
this
._numFreeAppInstances
++
;
9
return
;
10
}
11
}
12
app.DisposeInternal();
13
}
14
15
16
private
void
RecycleSpecialApplicationInstance(HttpApplication app)
17
{
18
if
(
this
._numFreeSpecialAppInstances
<
20
)
19
{
20
lock
(
this
._specialFreeList)
21
{
22
this
._specialFreeList.Push(app);
23
this
._numFreeSpecialAppInstances
++
;
24
}
25
}
26
}
而且同时存在的HttpApplication的数目有并发请求的数目和最大能够允许的HttpApplication的实例数目共同决定的。所以如果期望在HttpModule中做处理的时候,如果需要保存共享数据时就需要注意HttpModule的生存期,不过如果使用静态变量的话就没有问题了,静态变量的生存周期跟AppMomain的生存周期一致。