凯发k8天生赢家一触即发

asp.net 运行时详解 揭开请求过程神秘面纱 -凯发k8天生赢家一触即发

2023-10-19

对于asp.net开发,排在前五的话题离不开请求生命周期。像什么cache、身份认证、role管理、routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了。抛开乌云见晴天,接下来就一步步揭开请求管道神秘面纱。

上篇回顾

在介绍本篇内容之前,让我们先回顾下上一篇《asp.net运行时详解 集成模式和经典模式》的主要内容。在上一篇随笔中,我们提到asp.net运行时通过application的initinternal方法初始化运行管道。asp.net运行时提供了两种初始化管道模式,集成模式和经典模式。随后又分别介绍了两种模式下管道的初始化过程。那么,每个管道具体是做什么事以及管道具体是怎么执行的?接下来,本篇的内容会围绕着两个问题进行讲解。另外,上篇还遗留了页面的生命周期介绍。所以,本篇也会对页面生命周期做介绍。

管道步骤

什么是请求管道?请求管道就是把application的一系列事件串联成一条线,这些事件按照排列的先后顺序依次执行,事件处理的对象包括httpmodule、httphandler、asp.net page。那么,在管道中具体包括哪些事件?下图概括了asp.net请求管道中包括的事件。

现在我们知道了管道包含的执行事件,但每个事件具体执行哪些操作?下面的列表简要列举了每个事件的执行的工作:

序号 事件 说明
1 beginrequest 请求管道的第一个事件,当asp.net相应一个请求时就会被触发。
2 authenticaterequest 验证请求,开始检查用户身份,一般是获取请求的用户信息。
3 authorizerequest 用户权限检查,未通过一般跳转到endrequest事件。
4 resolverequestcache 当权限验证通过后,通过缓存模块提供服务,检查请求是否存在缓存,存在则直接返回缓存结果。
5 maprequesthandler asp.net 基础结构使用 maprequesthandler 事件来确定用于当前请求的请求处理程序。
6 acquirerequeststate 获取请求状态。
7 preexecuterequesthandler 在asp.net执行处理事件handler之前执行。
8 executerequesthandler 执行具体的handler。
9 releaserequeststate 当 asp.net执行完请求的handler后, state模块保存当前的状态数据。
10 updaterequestcache 缓存模块存储相应,提供给后面的请求缓存。
11 logrequest 在asp.net生成日志之前触发。
12 endrequest 结束当前请求。
13 sendresponse 发送请求响应。

列表简单的描述了管道中包含的事件,每个事件都可以通过httpmodule进行扩展。其实上面这些事件只是一个空架子,而实际干活的还是上一篇随笔中我们提到的httpmodule,asp.net默认实现了很多ihttpmodule类,而这些类就是处理篇头提出的像cache、身份认证、role、rounting等操作。接下来我们就one by one的分析这些module具体做了什么操作。

之前我们有列举出了asp.net自身提供的ihttpmodule,下表包含了asp.net自身提供的ihttpmodule以及对应的类型:

序号 名称 类型
1 outputcachemodule system.web.caching.outputcachemodule
2 session system.web.sessionstate.sessionstatemodule
3 windowsauthentication system.web.security.windowsauthenticationmodule
4 formsauthentication system.web.security.formsauthenticationmodule
5 defaultauthentication system.web.security.defaultauthenticationmodule
6 rolemanager system.web.security.rolemanagermodule
7 urlauthorization system.web.security.urlauthorizationmodule
8 fileauthorization system.web.security.fileauthorizationmodule
9 anonymousidentification system.web.security.anonymousidentificationmodule
10 urlmappingsmodule system.web.urlmappingsmodule
11 servicemodel-4.0 system.servicemodel.activation.servicehttpmodule, system.servicemodel.activation
12 urlroutingmodule-4.0 system.web.routing.urlroutingmodule
13 scriptmodule-4.0 system.web.handlers.scriptmodule, system.web.extensions

列表中的module会被安插到管道的事件步骤上,但每个module具体安插到哪一个管道事件上,我们还是不清楚。要了解清楚这些,我们不得不分析这13个module的源代码。

13个ihttpmodule源代码分析

为了不影响整篇的阅读效果,我把13个ihttpmodule代码的详细介绍放在了附录。我们不需要全部了解,但是像处理缓存的outputcachemodule、身份认证的formsauthenticationmodule、授权url地址的urlauthorizationmodule、处理路由映射的urlroutingmodule等module是有必要了解的。详细请查看附录中的源代码介绍。

统一管道生厂线

13个module分析完了,我们也大概知道每个httpmodule应该安插在哪个管道事件上了。上面介绍的ihttpmodule,我们通过一张流程图直观的展现出来。流程图如下:

到目前为止,我已经知道了管道中的ihttpmodule。但是,只有这些ihttpmodule,一次请求的完整流程还是跑不通的。例如,urlroutingmodule生成了ihttphandler,但在哪个管道步骤上调用ihttphandler生成请求页面我们还是不知道。一个请求的完成流程可以归纳为mhpm。什么事mhpm呢?先看看下面的流程图:

    图中的mhpm分别表示:ihttpmodule、ihttphandler、page、ihttpmodule。从图中可以看出,有些ihttpmodule在处理ihttphandler之前执行,而有些ihttpmodule在生成页面page之后执行。分析了所有的ihttpmodule,但我们还是没看到执行ihttphandler的executerequesthandler管道上有任何附加操作。回想上一篇随笔,我们还记得集成模式的管道类pipelinestepmanager有一个buildsteps方法,部分代码如下:

internal override void buildsteps(waitcallback stepcallback)
{
httpapplication.iexecutionstep step2 = new httpapplication.callhandlerexecutionstep(app);
app.addeventmapping("managedpipelinehandler", requestnotification.executerequesthandler, false, step2);
}

代码中实例化了一个执行步骤step2,然后把step2映射到管道的executerequesthandler步骤。callhandlerexecutionstep实现了管道步骤接口iexecutionstep。通过实现接口的execute方法执行ihttphandler的processrequest方法。execute代码如下:

void httpapplication.iexecutionstep.execute()
{
httpcontext context = this._application.context;
ihttphandler handler = context.handler; if (handler == null)
{
this._sync = true;
}
else if (handler is ihttpasynchandler)
{
iasyncresult result;
bool flag;
bool flag2;
ihttpasynchandler handler2 = (ihttpasynchandler)handler;
this._sync = false;
this._handler = handler2;
func func = appverifier.wrapbeginmethod(this._application, new func(handler2.beginprocessrequest));
result = func(context, this._completioncallback, null);
this._asyncstepcompletioninfo.registerbeginunwound(result, out flag, out flag2);
if (flag)
{
handler2.endprocessrequest(result);
}
}
else
{
this._sync = true;
handler.processrequest(context);
}
}

代码首先对handler做判断,判断handler是否是异步类ihttpasynchandler。如果是,则执行handler的异步方法:beginprocessrequest;如果不是,则直接调用handler的同步方法processrequest。
    callhandlerexecutionstep步骤执行完后,asp.net就能得到具体的asp.net page页面。在processrequest执行过程中,涉及到页面的生成周期。对于页面的生命周期,我们必须区分webform页面和mvc页面。两种不同的页面,生命周期也完全不同。webform是基于事件驱动,但mvc页面已经不再基于事件驱动。

executerequesthandler管道事件上现在也附件的有操作了。目前为止,asp.net执行过程的整个管道步骤我们也差不多涉及的有个90%了。了解清楚请求过程的生命周期是非常有必要的。了解清楚了请求过程原理,我们可以设计出更加灵活的asp.net系统,并且能基于asp.net做更多的自定义扩展。

总结

本篇内容首先分析了asp.net执行管道包含哪些事件。但最初这些管道只是一个空架子,而在管道事件上添加具体任务是有ihttpmodule完成。微软自己为asp.net执行管道实现了13个ihttpmodule接口,并且这13个module分布在不同的管道是事件上。本篇我们也具体介绍了这13个module具体分布在哪些管道是事件上,以及每个module在管道事件上具体做了什么操作。

本篇也简单的介绍了executerequesthandler管道事件上的ihttphandler任务怎样执行。但没有具体介绍ihttphandler是怎样生成我们需要的asp.net page页面,也既是页面的生命周期。所以,下一篇随笔的预定内容既是asp.net高频话题:asp.net页面生命周期。

如果本篇内容对大家有帮助,请点击页面右下角的关注。如果觉得不好,也欢迎拍砖。你们的评价就是博主的动力!下篇内容,敬请期待!

附录

1.outputcachemodule


所在管道步骤:resolverequestcache、updaterequestcache。查看outputcachemodule实现的init方法,代码如下:

void ihttpmodule.init(httpapplication app)
{
if (runtimeconfig.getappconfig().outputcache.enableoutputcache)
{
app.resolverequestcache = new eventhandler(this.onenter);
app.updaterequestcache = new eventhandler(this.onleave);
}
}

通过代码我们能看出它在resolverequestcache和updaterequestcache这两个管道事件上执行了某些操作。onenter事件查看缓存记录是否有缓存,有缓存则直接返回缓存,而不执行之后的管道流程了。代码如下:

internal void onenter(object source, eventargs eventargs)
{
if (outputcache.inuse)
{
switch (request.httpverb)
{
case httpverb.get:
case httpverb.head:
case httpverb.post:
{
string str;
this._key = str = this.createoutputcacheditemkey(context, null);
object obj2 = outputcache.get(str);
if (obj2 != null)
{
response.cache.resetfromhttpcachepolicysettings(settings, context.utctimestamp);
string originalcacheurl = response2._kernelcacheurl;
if (originalcacheurl != null)
{
response.setupkernelcaching(originalcacheurl);
}
perfcounters.incrementcounter(appperfcounter.output_cache_ratio_base);
perfcounters.incrementcounter(appperfcounter.output_cache_hits);
this._key = null;
this._recordedcachemiss = false;
application.completerequest();
return;
}
return;
}
}
}
}

其实onenter里边的代码比我粘贴出来的多很多,但主流程是一致的。都是先通过请求的上下文信息(例如requestpath、method参数等)获取缓存主键,然后通过缓存主键到缓存队列里边去查看是否有主键对应的缓存。如果有缓存,则直接把缓存输出到response.output中,然后整个流程请求流程结束;如果没有缓存,则按管道流程执行下一步。
    既然在onenter里边能取出来缓存,那么肯定有写缓存的地方。写缓存正式通过outputcachemodule的onleave方法写入,onleave方法代码如下:

internal void onleave(object source, eventargs eventargs)
{
bool flag = false;
if (response.hascachepolicy)
{
cache = response.cache;
if (((cache.ismodified() && (response.statuscode == )) && ((request.httpverb == httpverb.get) || (request.httpverb == httpverb.post))) && response.isbuffered())
{
if (((((cache.getcacheability() == httpcacheability.public) || (cache.getcacheability() == httpcacheability.serverandprivate)) || ((cache.getcacheability() == httpcacheability.server) || flag3)) && ((!cache.getnoservercaching() && !response.containsnonshareablecookies()) && (cache.hasexpirationpolicy() || cache.hasvalidationpolicy()))) && ((!cache.varybyheaders.getvarybyunspecifiedparameters() && (cache.varybyparams.acceptsparams() || ((request.httpverb != httpverb.post) && !request.hasquerystring))) && (!cache.varybycontentencodings.ismodified() || cache.varybycontentencodings.iscacheableencoding(context.response.gethttpheadercontentencoding()))))
{
flag = true;
}
}
}
if (flag)
{
cachedvary vary;
string str;
string[] varybyparams;
this.recordcachemiss();
httpcachepolicysettings currentsettings = cache.getcurrentsettings(response);
string[] varybycontentencodings = currentsettings.varybycontentencodings;
string[] varybyheaders = currentsettings.varybyheaders; if (this._key == null)
{
this._key = this.createoutputcacheditemkey(context, null);
}
datetime noabsoluteexpiration = cache.noabsoluteexpiration;
timespan noslidingexpiration = cache.noslidingexpiration;
if (currentsettings.slidingexpiration)
{
noslidingexpiration = currentsettings.slidingdelta;
}
else if (currentsettings.ismaxageset)
{
datetime time2 = (currentsettings.utctimestampcreated != datetime.minvalue) ? currentsettings.utctimestampcreated : context.utctimestamp;
noabsoluteexpiration = time2 currentsettings.maxage;
}
if (noabsoluteexpiration > datetime.utcnow)
{
httprawresponse snapshot = response.getsnapshot();
string kernelcacheurl = response.setupkernelcaching(null);
guid cachedvaryid = (vary != null) ? vary.cachedvaryid : guid.empty;
cachedrawresponse rawresponse = new cachedrawresponse(snapshot, currentsettings, kernelcacheurl, cachedvaryid);
cachedependency dependencies = response.createcachedependencyforresponse();
outputcache.insertresponse(this._key, vary, str, rawresponse, dependencies, noabsoluteexpiration, noslidingexpiration);
}
}
}

代码首先检查请求头和响应头,看看是否符合写缓存的条件,例如检查缓存是否修改、返回状态是否为200等。接下来创建缓存主键、设置缓存周期。最后一步就是通过outputcache.insertresponse方法把结果缓存到outputcache中。

2. sessionstatemodule


所在管道步骤:acquirerequeststate、releaserequeststate、endrequest。sessionstatemodule的init方法调用了initmodulefromconfig方法,从配置文件中读取配置,初始化状态存储。在web.config配置中我们经常看到配置,mode包括inproc(进程内)、sqlserver(数据库)、stateserver(进程外)、custom(自定义)、off(关闭session)等。我们先看下initmodulefromconfig方法的代码:

private void initmodulefromconfig(httpapplication app, sessionstatesection config)
{
if (config.mode != sessionstatemode.off)
{
app.addonacquirerequeststateasync(new begineventhandler(this.beginacquirestate), new endeventhandler(this.endacquirestate));
app.releaserequeststate = new eventhandler(this.onreleasestate);
app.endrequest = new eventhandler(this.onendrequest);
this._partitionresolver = this.initpartitionresolver(config);
switch (config.mode)
{
case sessionstatemode.inproc:
if (httpruntime.useintegratedpipeline)
{
s_canskipendrequestcall = true;
}
this._store = new inprocsessionstatestore();
this._store.initialize(null, null);
break; case sessionstatemode.stateserver:
if (httpruntime.useintegratedpipeline)
{
s_canskipendrequestcall = true;
}
this._store = new outofprocsessionstatestore();
((outofprocsessionstatestore)this._store).initialize(null, null, this._partitionresolver);
break; case sessionstatemode.sqlserver:
this._store = new sqlsessionstatestore();
((sqlsessionstatestore)this._store).initialize(null, null, this._partitionresolver);
break; case sessionstatemode.custom:
this._store = this.initcustomstore(config);
break;
}
this._idmanager = this.initsessionidmanager(config);
if (((config.mode == sessionstatemode.inproc) || (config.mode == sessionstatemode.stateserver)) && this._usingaspnetsessionidmanager)
{
this._ignoreimpersonation = true;
}
}
}

前面的几行代码加载session事件到执行管道,接下来的switch代码根据mode的枚举值初始化不同的session存储介质。 后面还有一行代码调用了initsessionidmanager方法,生成一个session的id管理器。

当管道执行到acquirerequeststate事件时,sessionstatemodule中的beginacquirestate事件被触发,精简后的代码如下:

private iasyncresult beginacquirestate(object source, eventargs e, asynccallback cb, object extradata)
{
this.resetperrequestfields();
this._rqcontext = ((httpapplication)source).context;
this._rqar = new httpasyncresult(cb, extradata);
this.changeimpersonation(this._rqcontext, false);
this._store.initializerequest(this._rqcontext);
if (this._idmanager.initializerequest(this._rqcontext, false, out this._rqsupportsessionidreissue))
{
//不使用cookie直接结束
this._rqar.complete(true, null, null);
return this._rqar;
}
this._rqid = this._idmanager.getsessionid(this._rqcontext);
this._rqexecutiontimeout = this._rqcontext.timeout;
this._rqreadonly = this._rqcontext.readonlysessionstate;
if (this._rqid != null)
{
sessionstateitem = this.getsessionstateitem();
}
else if (!flag3)
{
bool flag4 = this.createsessionid();
this._rqidnew = true;
if (flag4)
{
if (s_configregenerateexpiredsessionid)
{
this.createuninitializedsessionstate();
}
this._rqar.complete(true, null, null);
return this._rqar;
}
}
if (sessionstateitem)
{
this.completeacquirestate();
this._rqar.complete(true, null, null);
}
result = this._rqar;
return result;
}

代码首先调用存储介质_store的initializerequest方法,初始化本次请求。然后调用id管理器_idmanager的initializerequest初始化请求,initializerequest方法会返回一个布尔值,为true表示不使用cookie,直接返回;为false表示使用cookie,继续执行beginacquirestate接下来的流程。初始化完成后调用_idmanager.getsessionid方法获取sessionid。如果没有获取到sessionid,则调用createsessionid生成sessionid。
    当管道执行到releaserequeststate步骤时,sessionstatemodule中的onreleasestate事件被触发。我们知道在beginacquirestate事件中已经生成了sessionid。所以,在releaserequeststate中我们能够获取到sessionid,然后根据session状态调用_store.removeitem方法移除缓存项或者调用_store setandreleaseitemexclusive方法插入、更新或者移除缓存项。
    当管道执行到endrequest步骤时,sessionstatemodule中的onendrequest事件被触发。这里边主要的内容就是初始化请求参数以及重置超时事件,准备接收下一次请求。

3. windowsauthenticationmodule


所在管道步骤:authenticaterequest。windowsauthticationmodule的init方法在管道的authenticaterequest步骤注册onenter事件,onenter执行的内容比较简单,从上下文中取出用户身份,然后把用户身份设置到上下文的安全实体windowsprincipal中。

4. formsauthenticationmodule


所在管道步骤:authenticaterequest、endrequest。formsauthenticationmodule的init方法代码如下:

public void init(httpapplication app)
{
if (!_fauthchecked)
{
_fauthrequired = authenticationconfig.mode == authenticationmode.forms;
_fauthchecked = true;
}
if (_fauthrequired)
{
formsauthentication.initialize();
app.authenticaterequest = new eventhandler(this.onenter);
app.endrequest = new eventhandler(this.onleave);
}
}

代码调用了formsauthentication.initialize()方法对表单验证做初始化操作。initialize方法代码如下:

public static void initialize()
{
authenticationsection authentication = runtimeconfig.getappconfig().authentication;
authentication.validateauthenticationmode();
_formsname = authentication.forms.name;
_requiressl = authentication.forms.requiressl;
_slidingexpiration = authentication.forms.slidingexpiration;
if (_formsname == null)
{
_formsname = ".aspxauth";
}
_protection = authentication.forms.protection;
_timeout = (int)authentication.forms.timeout.totalminutes;
_formscookiepath = authentication.forms.path;
_loginurl = authentication.forms.loginurl;
if (_loginurl == null)
{
_loginurl = "login.aspx";
}
_defaulturl = authentication.forms.defaulturl;
if (_defaulturl == null)
{
_defaulturl = "default.aspx";
}
_cookiemode = authentication.forms.cookieless;
_cookiedomain = authentication.forms.domain;
_enablecrossappredirects = authentication.forms.enablecrossappredirects;
_ticketcompatibilitymode = authentication.forms.ticketcompatibilitymode;
_initialized = true;
}

通过代码可以看出,initialize方法从配置文件中读取表单配置信息并初始化到formsauthentication类的静态字段中。cookieless指定cookie类型, defaulturl表示验证后重定向的默认地址,loginurl表示找不到验证cookie重定向的登录地址,protection指定cookie的加密类型。详细说明请可以查看msdn:https://msdn.microsoft.com/zh-cn/library/1d3t3c61.aspx。 在管道authenticaterequest步骤上,我们注册了onenter方法,代码如下:

private void onenter(object source, eventargs eventargs)
{
httpcontext context = application.context;
this.onauthenticate(new formsauthenticationeventargs(context));
cookielesshelperclass cookielesshelper = context.cookielesshelper;
if (authenticationconfig.accessingloginpage(context, formsauthentication.loginurl))
{
context.setskipauthorizationnodemand(true, false);
cookielesshelper.redirectwithdetectionifrequired(null, formsauthentication.cookiemode);
}
if (!context.skipauthorization)
{
context.setskipauthorizationnodemand(assemblyresourceloader.isvalidwebresourcerequest(context), false);
}
}

分析代码,首先调用了onauthenticate方法,onauthenticate通过cookie配置信息对每次的请求作cookie更新,例如如果设置cookie为可调过期(slid),那么每次请求都会对cookie的过期时间更新。然后调用了authenticationconfig.accessingloginpage方法,判断是否正在请求配置的loginurl,如果是则直接跳过授权步骤。如果没有跳过授权步骤,检查当前请求是否为web资源请求,如果是则直接跳过授权步骤。在endrequest管道步骤上,我们注册了onleave方法,代码如下:

private void onleave(object source, eventargs eventargs)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
//context.response.statuscode == 401 && buzh
if ((context.response.statuscode == 0x191) && !context.response.suppressformsauthenticationredirect)
{
//当前请求的原始地址
string rawurl = context.request.rawurl;
if ((rawurl.indexof("?" formsauthentication.returnurlvar "=", stringcomparison.ordinal) == -) && (rawurl.indexof("&" formsauthentication.returnurlvar "=", stringcomparison.ordinal) == -))
{
strurl = authenticationconfig.getcompletelogin;
cookielesshelperclass cookielesshelper = context.cookielesshelper;
if (strurl.indexof('?') >= )
{
strurl = formsauthentication.removequerystringvariablefrom;
str3 = strurl "&" formsauthentication.returnurlvar "=" httputility.urlencode(rawurl, context.request.contentencoding);
}
else
{
str3 = strurl "?" formsauthentication.returnurlvar "=" httputility.urlencode(rawurl, context.request.contentencoding);
}
int index = rawurl.indexof('?');
if ((index >= ) && (index < (rawurl.length - )))
{
str3 = str3 "&" rawurl.substring(index );
}
cookielesshelper.setcookievalue('f', null);
cookielesshelper.redirectwithdetectionifrequired(str3, formsauthentication.cookiemode);
context.response.redirect(str3, false);
}
}
}

先列举一个场景,例如我们在taobao凯发k8天生赢家一触即发首页查看到某个商品,点击“购买”,但我们还没有登录。这个时候taobao会跳转到登录界面,登录成功后直接跳转到你的购买界面。onleave所做的正是这样的工作,首先校验返回状态status是否为0x191(401,权限验证失败),如果是则获取登录界面地址,并且在后面加上returnurl=请求地址。例如我请求地址是http://buy.heavi.com,但请求状态返回了401,这则时候onleave拼凑登录地址:http://login.heavi.com? returnurl=http://buy.heavi.com。最后直接通过context.response.redirect(str3, false)重定向到登录界面。当登录成功后,在重定向到http://buy.heavi.com界面。

5. defaultauthenticationmodule


所在管道步骤:authenticaterequest。defaultauthenticationmodule的init把onenter方法注册到authenticaterequest管道步骤上。onenter代码如下:

private void onenter(object source, eventargs eventargs)
{
if (context.response.statuscode > )
{
if (context.response.statuscode == 0x191)
{
this.writeerrormessage(context);
}
application.completerequest();
}
else
{
if (context.user == null)
{
this.onauthenticate(new defaultauthenticationeventargs(context));
if (context.response.statuscode > )
{
if (context.response.statuscode == 0x191)
{
this.writeerrormessage(context);
}
application.completerequest();
return;
}
}
if (context.user == null)
{
context.setprincipalnodemand(new genericprincipal(new genericidentity(string.empty, string.empty), new string[]), false);
}
thread.currentprincipal = context.user;
}
}

代码也比较简单,判断response中的status状态是等于401,是则写日志,直接结束本次请求。不是则设置当前线程的currentprincipal为当前请求的用户。

6. rolemanagermodule


所在管道步骤:authenticaterequest、endrequest。rolemanagermodule的init方法把onenter方法注册到authenticaterequest管道步骤上,把onleave方法注册到endrequest。onenter方法代码如下:

private void onenter(object source, eventargs eventargs)
{
if (roles.cacherolesincookie)
{
if (context.user.identity.isauthenticated && (!roles.cookierequiressl || context.request.issecureconnection))
{
httpcookie cookie = context.request.cookies[roles.cookiename];
if (cookie != null)
{
string encryptedticket = cookie.value;
if (!string.isnullorempty(roles.cookiepath) && (roles.cookiepath != "/"))
{
cookie.path = roles.cookiepath;
}
cookie.domain = roles.domain;
context.setprincipalnodemand(this.createroleprincipalwithassert(context.user.identity, encryptedticket));
}
}
else
{
if (context.request.cookies[roles.cookiename] != null)
{
roles.deletecookie();
}
if (httpruntime.useintegratedpipeline)
{
context.disablenotifications(requestnotification.endrequest, );
}
}
}
if (!(context.user is roleprincipal))
{
context.setprincipalnodemand(this.createroleprincipalwithassert(context.user.identity, null));
}
httpapplication.setcurrentprincipalwithassert(context.user);
}

如果设置了cacherolesincookie,并且身份已经通过认证了。接下来就从请求中获取role的cookie,并使用认证的身份创建角色安全体保存到上下文中;如果认证没通过,并且cookie中有角色的cookie,则删除角色cookie。onleave代码如下:

private void onleave(object source, eventargs eventargs)
{
if (((roles.enabled && roles.cacherolesincookie) && !context.response.headerswritten) && (((context.user != null) && (context.user is roleprincipal)) && context.user.identity.isauthenticated))
{
if (roles.cookierequiressl && !context.request.issecureconnection)
{
if (context.request.cookies[roles.cookiename] != null)
roles.deletecookie();
}
else
{
roleprincipal user = (roleprincipal)context.user;
if (user.cachedlistchanged && context.request.browser.cookies)
{
string str = user.toencryptedticket();
if (string.isnullorempty(str) || (str.length > 0x1000))
roles.deletecookie();
else
{
httpcookie cookie = new httpcookie(roles.cookiename, str)
{
httponly = true,
path = roles.cookiepath,
domain = roles.domain
};
if (roles.createpersistentcookie)
{
cookie.expires = user.expiredate;
}
cookie.secure = roles.cookierequiressl;
context.response.cookies.add(cookie);
}
}
}
}
}

首先判断角色是否可用、是否把角色缓存存储在cookie、上下文身份是否是角色安全体、是否通过认证,只有满足这些条件才执行下面的流程。满足条件后,如果cookie需要ssl认证并且不是安全连接,则删除cookie中的角色cookie;否则,重新生成新的cookie并返回到response中。

7. urlauthorizationmodule


所在管道步骤:authorizerequest。urlauthorizationmodule的init把onenter方法注册到authorizerequest管道步骤上。onenter方法代码如下:

private void onenter(object source, eventargs eventargs)
{
authorizationsection authorization = runtimeconfig.getconfig(context).authorization;
if (!authorization.everyoneallowed && !authorization.isuserallowed(context.user, context.request.requesttype))
{
reporturlauthorizationfailure(context, this);
}
else
{
if ((context.user == null) || !context.user.identity.isauthenticated)
{
perfcounters.incrementcounter(appperfcounter.anonymous_requests);
}
webbaseevent.raisesystemevent(this, 0xfa3);
}
}

首先从配置中获取授权节点,如果当前用户被限制,则调用reporturlauthorizationfailure方法记录url授权报告并终止本次请求;如果授权成功,执行websuccessauditevent系统事件。

8. fileauthorizationmodule


所在管道步骤:authorizerequest。fileauthorizationmodule的init把onenter方法注册到authorizerequest管道步骤上。onenter代码如下:

private void onenter(object source, eventargs eventargs)
{
if (!isuserallowedtofile(context, null))
{
context.response.setstatuscode(0x191, );
this.writeerrormessage(context);
application.completerequest();
}
}

代码中,调用isuserallowedtofile方法判断当前用户是否允许访问请求的文件。如果不允许访问,则设置返回状态为401(认证失败)并记录错误信息,结束本次请求。需要说明的是,isuserallowedtofile只验证windows用户。如果是其他用户,则不需要file验证。

9. anonymousidentificationmodule


所在管道步骤:authorizerequest。anonymousidentificationmodule的init把onenter方法注册到authorizerequest管道步骤上。onenter代码如下:

private void onenter(object source, eventargs eventargs)
{
if (!s_initialized)
//从配置文件中读取anonymousidentification节点配置
initialize(); if (s_enabled)
{
isauthenticated = context.request.isauthenticated;
if (isauthenticated)
flag2 = cookielesshelperclass.usecookieless(context, false, s_cookiemode); //false表示使用cookie
else
flag2 = cookielesshelperclass.usecookieless(context, true, s_cookiemode); //true表示不适用cookie
//如果需要ssl,并且请求不是安全连接,并且使用cookie
if ((s_requiressl && !context.request.issecureconnection) && !flag2)
{
if (context.request.cookies[s_cookiename] != null)
{
//重新设置cookie,并且设置过期时间为已过期,0x7cf表示1999年。
cookie = new httpcookie(s_cookiename, string.empty)
{
httponly = true,
path = s_cookiepath,
secure = s_requiressl
};
cookie.expires = new datetime(0x7cf, , );
context.response.cookies.add(cookie);
}
}
//不需要ssl认证
else
{
if (!flag2)
{
cookie = context.request.cookies[s_cookiename];
if (cookie != null)
{
cookievalue = cookie.value;
cookie.path = s_cookiepath;
cookie.domain = s_domain;
}
}
else
{
cookievalue = context.cookielesshelper.getcookievalue('a');
}
decodedvalue = getdecodedvalue(cookievalue);
if ((decodedvalue != null) && (decodedvalue.anonymousid != null))
{
context.request.anonymousid = decodedvalue.anonymousid;
}
if (!isauthenticated)
{
//设置anonymousid
if (context.request.anonymousid == null)
{
if (this._createnewideventhandler != null)
{
anonymousidentificationeventargs e = new anonymousidentificationeventargs(context);
this._createnewideventhandler(this, e);
context.request.anonymousid = e.anonymousid;
}
flag = true;
}
datetime utcnow = datetime.utcnow;
//如果cookie设置为滑动调整,并且cookie过期时间小于cookie过期周期的一半,则需要更新cookie
if (!flag && s_slidingexpiration)
{
if ((decodedvalue == null) || (decodedvalue.expiredate < utcnow))
{
flag = true;
}
else
{
timespan span = (timespan)(decodedvalue.expiredate - utcnow);
if (span.totalseconds < ((s_cookietimeout * ) / ))
{
flag = true;
}
}
}
//生成新的cookie
if (flag)
{
datetime dt = utcnow.addminutes((double)s_cookietimeout);
cookievalue = getencodedvalue(new anonymousiddata(context.request.anonymousid, dt));
if (!flag2)
{
cookie = new httpcookie(s_cookiename, cookievalue)
{
httponly = true,
expires = dt,
path = s_cookiepath,
secure = s_requiressl
};
if (s_domain != null)
{
cookie.domain = s_domain;
}
context.response.cookies.add(cookie);
}
else
{
context.cookielesshelper.setcookievalue('a', cookievalue);
context.response.redirect(context.request.rawurl);
}
}
}
}
}

首先调用initialize方法从配置文件中读取anonymousidentification节点配置信息,例如我们在web.config中配置:


    cookietimeout="" cookierequiressl="true" cookieslidingexpiration="true" />

initialize方法把这些配置读取到anonymousidentificationmodule实体中。如果匿名身份需要ssl认证并且当前连接不是安全连接,则直接把cookie设置为已过期并返回到response中。如果不需要ssl认证,则根据配置信息以及过期周期更新匿名cookie的anonymousid以及过期时间,最后把更新的cookie返回到response.cookie中。

10. urlmappingsmodule


所在管道步骤:beginrequest。urlmappingsmodule的init做了两件事,一是从配置文件中读取urlmappings 节点配置,下面就是web.cofnig中配置实例:



init的第二件事就是把onenter方法注册到beginrequest管道步骤,onenter方法直接调用urlmappingrewritepath方法,所以,我们可以直接分析urlmappingrewritepath方法代码:

static void urlmappingrewritepath(httpcontext context)
{
httprequest request = context.request;
urlmappingssection urlmappings = runtimeconfig.getappconfig().urlmappings;
string path = request.path;
string str2 = null;
string querystringtext = request.querystringtext;
if (!string.isnullorempty(querystringtext))
{
str2 = urlmappings.httpresolvemapping(path "?" querystringtext);
}
if (str2 == null)
{
str2 = urlmappings.httpresolvemapping(path);
}
if (!string.isnullorempty(str2))
{
context.rewritepath(str2, false);
}
}

代码比较简单,首先从配置文件中获取urlmappings节点信息,然后调用httpresolvemapping方法,匹配请求的全路径url(包括路径和参数)是否有对应的mappedurl。如果没有,再匹配请求的路径path是否有对应的mappedurl。匹配成功,调用context.rewirtepath方法设置请求的路径为mappedurl。

11. servicehttpmodule


servicehttpmodule没有执行任何操作。用户向后扩展

12. urlroutingmodule


urlroutingmodule在所有管道中起到承上启下的作用,http请求的ihttphandler就在是这里生成的。所在管道步骤:resolverequestcache。init方法把urlroutingmodule中的onapplicationpostresolverequestcache方法注册到resolverequestcache管道步骤。
    onapplicationpostresolverequestcache方法直接调用了postresolverequestcache方法,postresolverequestcache代码如下:

public virtual void postresolverequestcache(httpcontextbase context)
{
//根据上下文从路由集合中获取对应路由数据
routedata routedata = this.routecollection.getroutedata(context);
if (routedata != null)
{
//获取路由处理器
iroutehandler routehandler = routedata.routehandler;
if (!(routehandler is stoproutinghandler))
{
requestcontext requestcontext = new requestcontext(context, routedata);
context.request.requestcontext = requestcontext;
//获取ihttphandler
ihttphandler httphandler = routehandler.gethttphandler(requestcontext);
//重定向上下文中的httphandler
context.remaphandler(httphandler);
}
}
}

上面的代码已经是一目了然,清清楚楚的了。首先从路由集合中获取路由数据routedata,然后从routedata获取routehandler,接下来调用routehandler的gethttphandler方法获取ihttphandler实例。最后,调用上下文context的remaphandler方法重定向httphandler。下面是整个执行的流程图:

13. scriptmodule


scriptmodule没有执行任何操作。

如果本篇内容对大家有帮助,请点击页面右下角的关注。如果觉得不好,也欢迎拍砖。你们的评价就是博主的动力!下篇内容,敬请期待!

asp.net 运行时详解 揭开请求过程神秘面纱的相关教程结束。

网站地图