Community Server专题三:HttpModule
程序员文章站
2023-11-27 12:25:28
从专题三开始分析community server的一些具体的技术实现,根据iis对请求的处理流程,从httpmodule& httphandler切入...
从专题三开始分析community server的一些具体的技术实现,根据iis对请求的处理流程,从httpmodule& httphandler切入话题,同时你也可以通过一系列的专题了解cs的运行过程,不只如此,所有的.net 1.1 构架的web app都是以同样的顺序执行的。
先了解一下iis系统。它是一个程序,负责对网站的内容进行管理并且处理对客户的请求做出反应。当用户对一个页面提出请求时,iis做如下反应(不考虑权限问题):
1.把对方请求的虚拟路径转换成物理路径
2.根据物理路径搜索请求的文件
3.找到文件后,获取文件的内容
4.生成http头信息。
5.向客户端发送所有的文件内容:首先是头信息,然后是html内容,最后是其它文件的内容。
6.客户端ie浏览器获得信息后,解析文件内容,找出其中的引用文件,如.js .css .gif等,向iis请求这些文件。
7.iis获取请求后,发送文件内容。
8.当浏览器获取所有内容后,生成内容界面,客户就看到图像/文本/其它内容了。
但是iis本身是不支持动态页面的,也就是说它仅仅支持静态html页面的内容,对于如.asp,.aspx,.cgi,.php等,iis并不会处理这些标记,它就会把它当作文本,丝毫不做处理发送到客户端。为了解决这个问题。iis有一种机制,叫做isapi的筛选器,这个东西是一个标准组件(com组件),当在在访问iis所不能处理的文件时,如asp.net 1.1 中的iis附加isapi筛选器如图:
asp.net 服务在注册到iis的时候,会把每个扩展可以处理的文件扩展名注册到iis里面(如:*.ascx、*.aspx等)。扩展启动后,就根据定义好的方式来处理iis所不能处理的文件,然后把控制权跳转到专门处理代码的进程中。让这个进程开始处理代码,生成标准的html代码,生成后把这些代码加入到原有的 html中,最后把完整的html返回给iis,iis再把内容发送到客户端。
有上面对isapi的简单描述,我们把httpmodule& httphandler分开讨论,并且结合cs进行具体的实现分析。
httpmodule:
httpmodule实现了isapi filter的功能,是通过对ihttpmodule接口的继承来处理。下面打开cs中的communityservercomponents项目下的cshttpmodule.cs文件(放在httpmodule目录)
//------------------------------------------------------------------------------
// <copyright company="telligent systems">
// copyright (c) telligent systems corporation. all rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using system;
using system.io;
using system.web;
using communityserver.components;
using communityserver.configuration;
namespace communityserver
{
// *********************************************************************
// cshttpmodule
//
/**//// <summary>
/// this httpmodule encapsulates all the forums related events that occur
/// during asp.net application start-up, errors, and end request.
/// </summary>
// ***********************************************************************/
public class cshttpmodule : ihttpmodule
{
member variables and inherited properties / methods#region member variables and inherited properties / methods
public string modulename
{
get { return "cshttpmodule"; }
}
// *********************************************************************
// forumshttpmodule
//
/**//// <summary>
/// initializes the httpmodule and performs the wireup of all application
/// events.
/// </summary>
/// <param name="application">application the module is being run for</param>
public void init(httpapplication application)
{
// wire-up application events
//
application.beginrequest += new eventhandler(this.application_beginrequest);
application.authenticaterequest += new eventhandler(application_authenticaterequest);
application.error += new eventhandler(this.application_onerror);
application.authorizerequest += new eventhandler(this.application_authorizerequest);
//settingsid = sitesettingsmanager.getsitesettings(application.context).settingsid;
jobs.instance().start();
//csexception ex = new csexception(csexceptiontype.applicationstart, "appication started " + appdomain.currentdomain.friendlyname);
//ex.log();
}
//int settingsid;
public void dispose()
{
//csexception ex = new csexception(csexceptiontype.applicationstop, "application stopping " + appdomain.currentdomain.friendlyname);
//ex.log(settingsid);
jobs.instance().stop();
}
installer#region installer
#endregion
#endregion
application onerror#region application onerror
private void application_onerror (object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csexception csexception = context.server.getlasterror() as csexception;
if(csexception == null)
csexception = context.server.getlasterror().getbaseexception() as csexception;
try
{
if (csexception != null)
{
switch (csexception.exceptiontype)
{
case csexceptiontype.userinvalidcredentials:
case csexceptiontype.accessdenied:
case csexceptiontype.administrationaccessdenied:
case csexceptiontype.moderateaccessdenied:
case csexceptiontype.postdeleteaccessdenied:
case csexceptiontype.postproblem:
case csexceptiontype.useraccountbanned:
case csexceptiontype.resourcenotfound:
case csexceptiontype.userunknownloginerror:
case csexceptiontype.sectionnotfound:
csexception.log();
break;
}
}
else
{
exception ex = context.server.getlasterror();
if(ex.innerexception != null)
ex = ex.innerexception;
csexception = new csexception(csexceptiontype.unknownerror, ex.message, context.server.getlasterror());
system.data.sqlclient.sqlexception sqlex = ex as system.data.sqlclient.sqlexception;
if(sqlex == null || sqlex.number != -2) //don't log time outs
csexception.log();
}
}
catch{} //not much to do here, but we want to prevent infinite looping with our error handles
csevents.csexception(csexception);
}
#endregion
application authenticaterequest#region application authenticaterequest
private void application_authenticaterequest(object source, eventargs e)
{
httpcontext context = httpcontext.current;
provider p = null;
extensionmodule module = null;
// if the installer is making the request terminate early
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer) {
return;
}
// only continue if we have a valid context
//
if ((context == null) || (context.user == null))
return;
try
{
// logic to handle various authentication types
//
switch(context.user.identity.gettype().name.tolower())
{
// microsoft passport
case "passportidentity":
p = (provider) csconfiguration.getconfig().extensions["passportauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// windows
case "windowsidentity":
p = (provider) csconfiguration.getconfig().extensions["windowsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// forms
case "formsidentity":
p = (provider) csconfiguration.getconfig().extensions["formsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// custom
case "customidentity":
p = (provider) csconfiguration.getconfig().extensions["customauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
default:
cscontext.current.username = context.user.identity.name;
break;
}
}
catch( exception ex )
{
csexception forumex = new csexception( csexceptiontype.unknownerror, "error in authenticaterequest", ex );
forumex.log();
throw forumex;
}
// // get the roles the user belongs to
// //
// roles roles = new roles();
// roles.getuserroles();
}
#endregion
application authorizerequest#region application authorizerequest
private void application_authorizerequest (object source, eventargs e) {
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
cscontext cscontext = cscontext.current;
//bool enablebanneduserstologin = cscontext.current.sitesettings.enablebanneduserstologin;
// // if the installer is making the request terminate early
// if (cscontext.applicationtype == applicationtype.installer) {
// return;
// }
//cscontext.user = cscontext.current.user;
csevents.userknown(cscontext.user);
validateapplicationstatus(cscontext);
// track anonymous users
//
users.trackanonymoususers(context);
// do we need to force the user to login?
//
if (context.request.isauthenticated)
{
string username = context.user.identity.name;
if (username != null)
{
string[] roles = communityserver.components.roles.getuserrolenames(username);
if (roles != null && roles.length > 0)
{
cscontext.rolescachekey = string.join(",",roles);
}
}
}
}
#endregion
application beginrequest#region application beginrequest
private void application_beginrequest(object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csconfiguration config = csconfiguration.getconfig();
// if the installer is making the request terminate early
if (config.applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
checkwwwstatus(config,context);
cscontext.create(context, rewriteurl(context));
}
private void checkwwwstatus(csconfiguration config, httpcontext context)
{
if(config.wwwstatus == wwwstatus.ignore)
return;
const string withwww = "
const string nowww = "
string rawurl = context.request.url.tostring().tolower();
bool iswww = rawurl.startswith(withwww);
if(config.wwwstatus == wwwstatus.remove && iswww)
{
context.response.redirect(rawurl.replace(withwww, nowww));
}
else if(config.wwwstatus == wwwstatus.require && !iswww)
{
context.response.redirect(rawurl.replace(nowww, withwww));
}
}
rewriteurl#region rewriteurl
private bool rewriteurl(httpcontext context)
{
// we're now allowing each individual application to be turned on and off individually. so before we allow
// a request to go through we need to check if this product is disabled and the path is for the disabled product,
// if so we display the disabled product page.
//
// i'm also allowing the page request to go through if the page request is for an admin page. in the past if you
// disabled the forums you were locked out, now with this check, even if you're not on the same machine but you're accessing
// an admin path the request will be allowed to proceed, where the rest of the checks will ensure that the user has the
// permission to access the specific url.
// url rewriting
//
//rewriteurl(context);
string newpath = null;
string path = context.request.path;
bool isrewritten = siteurls.rewriteurl(path,context.request.url.query,out newpath);
//very wachky. the first call into rewritepath always fails with a 404.
//calling rewritepath twice actually fixes the probelm as well. instead,
//we use the second rewritepath overload and it seems to work 100%
//of the time.
if(isrewritten && newpath != null)
{
string qs = null;
int index = newpath.indexof('?');
if (index >= 0)
{
qs = (index < (newpath.length - 1)) ? newpath.substring(index + 1) : string.empty;
newpath = newpath.substring(0, index);
}
context.rewritepath(newpath,null,qs);
}
return isrewritten;
}
#endregion
private void validateapplicationstatus(cscontext cntx)
{
if(!cntx.user.isadministrator)
{
string disablepath = null;
switch(cntx.config.applocation.currentapplicationtype)
{
case applicationtype.forum:
if(cntx.sitesettings.forumsdisabled)
disablepath = "forumsdisabled.htm";
break;
case applicationtype.weblog:
if(cntx.sitesettings.blogsdisabled)
disablepath = "blogsdisabled.htm";
break;
case applicationtype.gallery:
if(cntx.sitesettings.galleriesdisabled)
disablepath = "galleriesdisabled.htm";
break;
case applicationtype.guestbook:
if(cntx.sitesettings.guestbookdisabled)
disablepath = "guestbookdisabled.htm";
break;
case applicationtype.document: //新增 ugoer
if(cntx.sitesettings.documentdisabled)
disablepath = "documentsdisabled.htm";
break;
}
if(disablepath != null)
{
string errorpath = cntx.context.server.mappath(string.format("~/languages/{0}/errors/{1}",cntx.config.defaultlanguage,disablepath));
using(streamreader reader = new streamreader(errorpath))
{
string html = reader.readtoend();
reader.close();
cntx.context.response.write(html);
cntx.context.response.end();
}
}
}
}
#endregion
}
}
在web.config中的配置:
<httpmodules>
<add name="communityserver" type="communityserver.cshttpmodule, communityserver.components" />
<add name="profile" type="microsoft.scalablehosting.profile.profilemodule, memberrole, version=1.0.0.0, culture=neutral, publickeytoken=b7c773fb104e7562"/>
<add name="rolemanager" type="microsoft.scalablehosting.security.rolemanagermodule, memberrole, version=1.0.0.0, culture=neutral, publickeytoken=b7c773fb104e7562" />
</httpmodules>
cshttpmodule.cs uml:
要实现httpmodule功能需要如下步骤:
1.编写一个类,实现ihttpmodule接口
2.实现init 方法,并且注册需要的方法
3.实现注册的方法
4.实现dispose方法,如果需要手工为类做一些清除工作,可以添加dispose方法的实现,但这不是必需的,通常可以不为dispose方法添加任何代码。
5.在web.config文件中,注册您编写的类
到这里我们还需要了解一个asp.net的运行过程:
在图中第二步可以看到当请求开始的时候,马上就进入了httpmodule,在cs中由于实现了httpmodule的扩展cshttpmodule.cs 类,因此当一个web请求发出的时候(如:一个用户访问他的blog),cs系统首先调用cshttpmodule.cs类,并且进入
public void init(httpapplication application)
该方法进行初始化事件:
application.beginrequest += new eventhandler(this.application_beginrequest);
application.authenticaterequest += new eventhandler(application_authenticaterequest);
application.error += new eventhandler(this.application_onerror);
application.authorizerequest += new eventhandler(this.application_authorizerequest);
有事件就要有对应的处理方法:
private void application_beginrequest(object source, eventargs e)
private void application_authenticaterequest(object source, eventargs e)
private void application_onerror (object source, eventargs e)
private void application_authorizerequest (object source, eventargs e)
事件被初始化后就等待系统的触发,请求进入下一步此时系统触发application_beginrequest事件,事件处理内容如下:
private void application_beginrequest(object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csconfiguration config = csconfiguration.getconfig();
// if the installer is making the request terminate early
if (config.applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
checkwwwstatus(config,context);
cscontext.create(context, rewriteurl(context));
}
private void checkwwwstatus(csconfiguration config, httpcontext context)
{
if(config.wwwstatus == wwwstatus.ignore)
return;
const string withwww = "
const string nowww = "
string rawurl = context.request.url.tostring().tolower();
bool iswww = rawurl.startswith(withwww);
if(config.wwwstatus == wwwstatus.remove && iswww)
{
context.response.redirect(rawurl.replace(withwww, nowww));
}
else if(config.wwwstatus == wwwstatus.require && !iswww)
{
context.response.redirect(rawurl.replace(nowww, withwww));
}
}
rewriteurl#region rewriteurl
private bool rewriteurl(httpcontext context)
{
// we're now allowing each individual application to be turned on and off individually. so before we allow
// a request to go through we need to check if this product is disabled and the path is for the disabled product,
// if so we display the disabled product page.
//
// i'm also allowing the page request to go through if the page request is for an admin page. in the past if you
// disabled the forums you were locked out, now with this check, even if you're not on the same machine but you're accessing
// an admin path the request will be allowed to proceed, where the rest of the checks will ensure that the user has the
// permission to access the specific url.
// url rewriting
//
//rewriteurl(context);
string newpath = null;
string path = context.request.path;
bool isrewritten = siteurls.rewriteurl(path,context.request.url.query,out newpath);
//very wachky. the first call into rewritepath always fails with a 404.
//calling rewritepath twice actually fixes the probelm as well. instead,
//we use the second rewritepath overload and it seems to work 100%
//of the time.
if(isrewritten && newpath != null)
{
string qs = null;
int index = newpath.indexof('?');
if (index >= 0)
{
qs = (index < (newpath.length - 1)) ? newpath.substring(index + 1) : string.empty;
newpath = newpath.substring(0, index);
}
context.rewritepath(newpath,null,qs);
}
return isrewritten;
}
#endregion
这个事件主要做两个事情
a:为发出请求的用户初始化一个context,初始化context用到了线程中本地数据槽(localdatastoreslot),把当前用户请求的上下文(contextb)保存在为此请求开辟的内存中。
b:判断是否需要重写 url(检查是否需要重写的过程是对siteurls.config文件中正则表达式和对应url处理的过程),如果需要重写url,就执行asp.net级别上的rewritepath方法获得新的路径,新的路径才是真正的请求信息所在的路径。这个专题不是讲url rewrite,所以只要明白url在这里就进行rewrite就可以了,具体的后面专题会叙述。
处理完 application_beginrequest 后进程继向下执行,随后触发了application_authenticaterequest(如果有朋友不明白这个执行过程,可以通过调试中设置多个断点捕获事件执行的顺序。如果你还不会调试,可以留言偷偷的告诉我,嘿嘿。), application_authenticaterequest事件初始化一个context的identity,其实cs提供了很多的 identity支持,包括microsoft passport,但是目前的版本中使用的是默认值 system.web.security.formsidentity。具体代码如下:
private void application_authenticaterequest(object source, eventargs e)
{
httpcontext context = httpcontext.current;
provider p = null;
extensionmodule module = null;
// if the installer is making the request terminate early
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer) {
return;
}
// only continue if we have a valid context
//
if ((context == null) || (context.user == null))
return;
try
{
// logic to handle various authentication types
//
switch(context.user.identity.gettype().name.tolower())
{
// microsoft passport
case "passportidentity":
p = (provider) csconfiguration.getconfig().extensions["passportauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// windows
case "windowsidentity":
p = (provider) csconfiguration.getconfig().extensions["windowsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// forms
case "formsidentity":
p = (provider) csconfiguration.getconfig().extensions["formsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// custom
case "customidentity":
p = (provider) csconfiguration.getconfig().extensions["customauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
default:
cscontext.current.username = context.user.identity.name;
break;
}
}
catch( exception ex )
{
csexception forumex = new csexception( csexceptiontype.unknownerror, "error in authenticaterequest", ex );
forumex.log();
throw forumex;
}
// // get the roles the user belongs to
// //
// roles roles = new roles();
// roles.getuserroles();
}
再下来是application_authorizerequest事件被触发,事件代码如下:
private void application_authorizerequest (object source, eventargs e) {
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
cscontext cscontext = cscontext.current;
//bool enablebanneduserstologin = cscontext.current.sitesettings.enablebanneduserstologin;
// // if the installer is making the request terminate early
// if (cscontext.applicationtype == applicationtype.installer) {
// return;
// }
//cscontext.user = cscontext.current.user;
csevents.userknown(cscontext.user);
validateapplicationstatus(cscontext);
// track anonymous users
//
users.trackanonymoususers(context);
// do we need to force the user to login?
//
if (context.request.isauthenticated)
{
string username = context.user.identity.name;
if (username != null)
{
string[] roles = communityserver.components.roles.getuserrolenames(username);
if (roles != null && roles.length > 0)
{
cscontext.rolescachekey = string.join(",",roles);
}
}
}
}
在application_authorizerequest中分析关键几行代码:
1:cscontext cscontext = cscontext.current; //该代码取出在前一个事件中保存在localdatastoreslot中的context,说明白点就是从内存中取出之前保存的一些数据。
2: csevents.userknown(cscontext.user); //这里触发了一个userknown事件,涉及到cs中大量使用委托与事件的一个类csapplication(csapplication.cs文件),后续对这个类做专题分析,这里只要先了解该事件起到判断登陆用户是否 forcelogin以及登录的帐户是否是禁用就可以了(把对user的判断移入application_authorizerequest事件处理程序中是很好的一种处理方法)
3:validateapplicationstatus(cscontext); //判断论坛、blog、相册是否被禁用,如果登录用户的角色不为isadministrator,就跳转到相应的禁用警告页面,如blog被禁用即跳转到 blogsdisabled.htm页面显示。
4:users.trackanonymoususers(context); //如果是匿名用户,在这个方法中跟踪记录。
处理完上面三个事件后,cs将开始处理请求页面中的具体业务逻辑,如果用户请求的是登录页面,接下来就处理登录页面需要的业务逻辑和呈现,当然这里还会触发一系列其他事件,因为这些事件没有在这里定义我们暂时不做考虑。要说明一点,httpmodule在整个web请求到响应完成过程中都没有退出进程,而是处于监控状态。application_onerror正是处于其监控范围下的一个事件,一旦有exception或者继承exception的类被异常抛出,httpmodule就捕获它,之后就可以根据exception中exceptiontype值统一处理这些不同的错误信息。cs中就是这样实现错误处理的,具体的我们看一下代码:
private void application_onerror (object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csexception csexception = context.server.getlasterror() as csexception;
if(csexception == null)
csexception = context.server.getlasterror().getbaseexception() as csexception;
try
{
if (csexception != null)
{
switch (csexception.exceptiontype)
{
case csexceptiontype.userinvalidcredentials:
case csexceptiontype.accessdenied:
case csexceptiontype.administrationaccessdenied:
case csexceptiontype.moderateaccessdenied:
case csexceptiontype.postdeleteaccessdenied:
case csexceptiontype.postproblem:
case csexceptiontype.useraccountbanned:
case csexceptiontype.resourcenotfound:
case csexceptiontype.userunknownloginerror:
case csexceptiontype.sectionnotfound:
csexception.log();
break;
}
}
else
{
exception ex = context.server.getlasterror();
if(ex.innerexception != null)
ex = ex.innerexception;
csexception = new csexception(csexceptiontype.unknownerror, ex.message, context.server.getlasterror());
system.data.sqlclient.sqlexception sqlex = ex as system.data.sqlclient.sqlexception;
if(sqlex == null || sqlex.number != -2) //don't log time outs
csexception.log();
}
}
catch{} //not much to do here, but we want to prevent infinite looping with our error handles
csevents.csexception(csexception);
}
当抛出exception后,cs开始处理application_onerror,根据抛出的exception的exceptiontype类型不同做不同的处理(forumexceptiontype.cs中定义所有的cs exceptiontype)。随后调用log()保存错误信息到数据库中,以便管理员跟踪这些错误的原因。这里还有重要的一句:csevents.csexception(csexception)它触发了2个事件类 cscatastrophicexceptionmodule与csexceptionmodule中的处理程序,与 application_authorizerequest中userknown处理机制是一样的,会在以后的专题讨论。只要知道这里会执行 redirecttomessage方法,把页面重新定向到一个友好的错误显示页即可,如下图所示:
至此,cshttpmodule类已经全部分析完毕。在cs里还有另外两个httpmodule,属于membership范畴,由于cs引用的是 membership的程序集无非进行内部的运行细节分析,但是工作原理与cshttpmodule是一致的,当你真正理解cshttpmodule的时候要去分析其他httpmodule也就不在话下了。希望我的这些分析能对你有帮助。
原文地址:
先了解一下iis系统。它是一个程序,负责对网站的内容进行管理并且处理对客户的请求做出反应。当用户对一个页面提出请求时,iis做如下反应(不考虑权限问题):
1.把对方请求的虚拟路径转换成物理路径
2.根据物理路径搜索请求的文件
3.找到文件后,获取文件的内容
4.生成http头信息。
5.向客户端发送所有的文件内容:首先是头信息,然后是html内容,最后是其它文件的内容。
6.客户端ie浏览器获得信息后,解析文件内容,找出其中的引用文件,如.js .css .gif等,向iis请求这些文件。
7.iis获取请求后,发送文件内容。
8.当浏览器获取所有内容后,生成内容界面,客户就看到图像/文本/其它内容了。
但是iis本身是不支持动态页面的,也就是说它仅仅支持静态html页面的内容,对于如.asp,.aspx,.cgi,.php等,iis并不会处理这些标记,它就会把它当作文本,丝毫不做处理发送到客户端。为了解决这个问题。iis有一种机制,叫做isapi的筛选器,这个东西是一个标准组件(com组件),当在在访问iis所不能处理的文件时,如asp.net 1.1 中的iis附加isapi筛选器如图:
asp.net 服务在注册到iis的时候,会把每个扩展可以处理的文件扩展名注册到iis里面(如:*.ascx、*.aspx等)。扩展启动后,就根据定义好的方式来处理iis所不能处理的文件,然后把控制权跳转到专门处理代码的进程中。让这个进程开始处理代码,生成标准的html代码,生成后把这些代码加入到原有的 html中,最后把完整的html返回给iis,iis再把内容发送到客户端。
有上面对isapi的简单描述,我们把httpmodule& httphandler分开讨论,并且结合cs进行具体的实现分析。
httpmodule:
httpmodule实现了isapi filter的功能,是通过对ihttpmodule接口的继承来处理。下面打开cs中的communityservercomponents项目下的cshttpmodule.cs文件(放在httpmodule目录)
//------------------------------------------------------------------------------
// <copyright company="telligent systems">
// copyright (c) telligent systems corporation. all rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using system;
using system.io;
using system.web;
using communityserver.components;
using communityserver.configuration;
namespace communityserver
{
// *********************************************************************
// cshttpmodule
//
/**//// <summary>
/// this httpmodule encapsulates all the forums related events that occur
/// during asp.net application start-up, errors, and end request.
/// </summary>
// ***********************************************************************/
public class cshttpmodule : ihttpmodule
{
member variables and inherited properties / methods#region member variables and inherited properties / methods
public string modulename
{
get { return "cshttpmodule"; }
}
// *********************************************************************
// forumshttpmodule
//
/**//// <summary>
/// initializes the httpmodule and performs the wireup of all application
/// events.
/// </summary>
/// <param name="application">application the module is being run for</param>
public void init(httpapplication application)
{
// wire-up application events
//
application.beginrequest += new eventhandler(this.application_beginrequest);
application.authenticaterequest += new eventhandler(application_authenticaterequest);
application.error += new eventhandler(this.application_onerror);
application.authorizerequest += new eventhandler(this.application_authorizerequest);
//settingsid = sitesettingsmanager.getsitesettings(application.context).settingsid;
jobs.instance().start();
//csexception ex = new csexception(csexceptiontype.applicationstart, "appication started " + appdomain.currentdomain.friendlyname);
//ex.log();
}
//int settingsid;
public void dispose()
{
//csexception ex = new csexception(csexceptiontype.applicationstop, "application stopping " + appdomain.currentdomain.friendlyname);
//ex.log(settingsid);
jobs.instance().stop();
}
installer#region installer
#endregion
#endregion
application onerror#region application onerror
private void application_onerror (object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csexception csexception = context.server.getlasterror() as csexception;
if(csexception == null)
csexception = context.server.getlasterror().getbaseexception() as csexception;
try
{
if (csexception != null)
{
switch (csexception.exceptiontype)
{
case csexceptiontype.userinvalidcredentials:
case csexceptiontype.accessdenied:
case csexceptiontype.administrationaccessdenied:
case csexceptiontype.moderateaccessdenied:
case csexceptiontype.postdeleteaccessdenied:
case csexceptiontype.postproblem:
case csexceptiontype.useraccountbanned:
case csexceptiontype.resourcenotfound:
case csexceptiontype.userunknownloginerror:
case csexceptiontype.sectionnotfound:
csexception.log();
break;
}
}
else
{
exception ex = context.server.getlasterror();
if(ex.innerexception != null)
ex = ex.innerexception;
csexception = new csexception(csexceptiontype.unknownerror, ex.message, context.server.getlasterror());
system.data.sqlclient.sqlexception sqlex = ex as system.data.sqlclient.sqlexception;
if(sqlex == null || sqlex.number != -2) //don't log time outs
csexception.log();
}
}
catch{} //not much to do here, but we want to prevent infinite looping with our error handles
csevents.csexception(csexception);
}
#endregion
application authenticaterequest#region application authenticaterequest
private void application_authenticaterequest(object source, eventargs e)
{
httpcontext context = httpcontext.current;
provider p = null;
extensionmodule module = null;
// if the installer is making the request terminate early
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer) {
return;
}
// only continue if we have a valid context
//
if ((context == null) || (context.user == null))
return;
try
{
// logic to handle various authentication types
//
switch(context.user.identity.gettype().name.tolower())
{
// microsoft passport
case "passportidentity":
p = (provider) csconfiguration.getconfig().extensions["passportauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// windows
case "windowsidentity":
p = (provider) csconfiguration.getconfig().extensions["windowsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// forms
case "formsidentity":
p = (provider) csconfiguration.getconfig().extensions["formsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// custom
case "customidentity":
p = (provider) csconfiguration.getconfig().extensions["customauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
default:
cscontext.current.username = context.user.identity.name;
break;
}
}
catch( exception ex )
{
csexception forumex = new csexception( csexceptiontype.unknownerror, "error in authenticaterequest", ex );
forumex.log();
throw forumex;
}
// // get the roles the user belongs to
// //
// roles roles = new roles();
// roles.getuserroles();
}
#endregion
application authorizerequest#region application authorizerequest
private void application_authorizerequest (object source, eventargs e) {
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
cscontext cscontext = cscontext.current;
//bool enablebanneduserstologin = cscontext.current.sitesettings.enablebanneduserstologin;
// // if the installer is making the request terminate early
// if (cscontext.applicationtype == applicationtype.installer) {
// return;
// }
//cscontext.user = cscontext.current.user;
csevents.userknown(cscontext.user);
validateapplicationstatus(cscontext);
// track anonymous users
//
users.trackanonymoususers(context);
// do we need to force the user to login?
//
if (context.request.isauthenticated)
{
string username = context.user.identity.name;
if (username != null)
{
string[] roles = communityserver.components.roles.getuserrolenames(username);
if (roles != null && roles.length > 0)
{
cscontext.rolescachekey = string.join(",",roles);
}
}
}
}
#endregion
application beginrequest#region application beginrequest
private void application_beginrequest(object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csconfiguration config = csconfiguration.getconfig();
// if the installer is making the request terminate early
if (config.applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
checkwwwstatus(config,context);
cscontext.create(context, rewriteurl(context));
}
private void checkwwwstatus(csconfiguration config, httpcontext context)
{
if(config.wwwstatus == wwwstatus.ignore)
return;
const string withwww = "
const string nowww = "
string rawurl = context.request.url.tostring().tolower();
bool iswww = rawurl.startswith(withwww);
if(config.wwwstatus == wwwstatus.remove && iswww)
{
context.response.redirect(rawurl.replace(withwww, nowww));
}
else if(config.wwwstatus == wwwstatus.require && !iswww)
{
context.response.redirect(rawurl.replace(nowww, withwww));
}
}
rewriteurl#region rewriteurl
private bool rewriteurl(httpcontext context)
{
// we're now allowing each individual application to be turned on and off individually. so before we allow
// a request to go through we need to check if this product is disabled and the path is for the disabled product,
// if so we display the disabled product page.
//
// i'm also allowing the page request to go through if the page request is for an admin page. in the past if you
// disabled the forums you were locked out, now with this check, even if you're not on the same machine but you're accessing
// an admin path the request will be allowed to proceed, where the rest of the checks will ensure that the user has the
// permission to access the specific url.
// url rewriting
//
//rewriteurl(context);
string newpath = null;
string path = context.request.path;
bool isrewritten = siteurls.rewriteurl(path,context.request.url.query,out newpath);
//very wachky. the first call into rewritepath always fails with a 404.
//calling rewritepath twice actually fixes the probelm as well. instead,
//we use the second rewritepath overload and it seems to work 100%
//of the time.
if(isrewritten && newpath != null)
{
string qs = null;
int index = newpath.indexof('?');
if (index >= 0)
{
qs = (index < (newpath.length - 1)) ? newpath.substring(index + 1) : string.empty;
newpath = newpath.substring(0, index);
}
context.rewritepath(newpath,null,qs);
}
return isrewritten;
}
#endregion
private void validateapplicationstatus(cscontext cntx)
{
if(!cntx.user.isadministrator)
{
string disablepath = null;
switch(cntx.config.applocation.currentapplicationtype)
{
case applicationtype.forum:
if(cntx.sitesettings.forumsdisabled)
disablepath = "forumsdisabled.htm";
break;
case applicationtype.weblog:
if(cntx.sitesettings.blogsdisabled)
disablepath = "blogsdisabled.htm";
break;
case applicationtype.gallery:
if(cntx.sitesettings.galleriesdisabled)
disablepath = "galleriesdisabled.htm";
break;
case applicationtype.guestbook:
if(cntx.sitesettings.guestbookdisabled)
disablepath = "guestbookdisabled.htm";
break;
case applicationtype.document: //新增 ugoer
if(cntx.sitesettings.documentdisabled)
disablepath = "documentsdisabled.htm";
break;
}
if(disablepath != null)
{
string errorpath = cntx.context.server.mappath(string.format("~/languages/{0}/errors/{1}",cntx.config.defaultlanguage,disablepath));
using(streamreader reader = new streamreader(errorpath))
{
string html = reader.readtoend();
reader.close();
cntx.context.response.write(html);
cntx.context.response.end();
}
}
}
}
#endregion
}
}
在web.config中的配置:
<httpmodules>
<add name="communityserver" type="communityserver.cshttpmodule, communityserver.components" />
<add name="profile" type="microsoft.scalablehosting.profile.profilemodule, memberrole, version=1.0.0.0, culture=neutral, publickeytoken=b7c773fb104e7562"/>
<add name="rolemanager" type="microsoft.scalablehosting.security.rolemanagermodule, memberrole, version=1.0.0.0, culture=neutral, publickeytoken=b7c773fb104e7562" />
</httpmodules>
cshttpmodule.cs uml:
要实现httpmodule功能需要如下步骤:
1.编写一个类,实现ihttpmodule接口
2.实现init 方法,并且注册需要的方法
3.实现注册的方法
4.实现dispose方法,如果需要手工为类做一些清除工作,可以添加dispose方法的实现,但这不是必需的,通常可以不为dispose方法添加任何代码。
5.在web.config文件中,注册您编写的类
到这里我们还需要了解一个asp.net的运行过程:
在图中第二步可以看到当请求开始的时候,马上就进入了httpmodule,在cs中由于实现了httpmodule的扩展cshttpmodule.cs 类,因此当一个web请求发出的时候(如:一个用户访问他的blog),cs系统首先调用cshttpmodule.cs类,并且进入
public void init(httpapplication application)
该方法进行初始化事件:
application.beginrequest += new eventhandler(this.application_beginrequest);
application.authenticaterequest += new eventhandler(application_authenticaterequest);
application.error += new eventhandler(this.application_onerror);
application.authorizerequest += new eventhandler(this.application_authorizerequest);
有事件就要有对应的处理方法:
private void application_beginrequest(object source, eventargs e)
private void application_authenticaterequest(object source, eventargs e)
private void application_onerror (object source, eventargs e)
private void application_authorizerequest (object source, eventargs e)
事件被初始化后就等待系统的触发,请求进入下一步此时系统触发application_beginrequest事件,事件处理内容如下:
private void application_beginrequest(object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csconfiguration config = csconfiguration.getconfig();
// if the installer is making the request terminate early
if (config.applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
checkwwwstatus(config,context);
cscontext.create(context, rewriteurl(context));
}
private void checkwwwstatus(csconfiguration config, httpcontext context)
{
if(config.wwwstatus == wwwstatus.ignore)
return;
const string withwww = "
const string nowww = "
string rawurl = context.request.url.tostring().tolower();
bool iswww = rawurl.startswith(withwww);
if(config.wwwstatus == wwwstatus.remove && iswww)
{
context.response.redirect(rawurl.replace(withwww, nowww));
}
else if(config.wwwstatus == wwwstatus.require && !iswww)
{
context.response.redirect(rawurl.replace(nowww, withwww));
}
}
rewriteurl#region rewriteurl
private bool rewriteurl(httpcontext context)
{
// we're now allowing each individual application to be turned on and off individually. so before we allow
// a request to go through we need to check if this product is disabled and the path is for the disabled product,
// if so we display the disabled product page.
//
// i'm also allowing the page request to go through if the page request is for an admin page. in the past if you
// disabled the forums you were locked out, now with this check, even if you're not on the same machine but you're accessing
// an admin path the request will be allowed to proceed, where the rest of the checks will ensure that the user has the
// permission to access the specific url.
// url rewriting
//
//rewriteurl(context);
string newpath = null;
string path = context.request.path;
bool isrewritten = siteurls.rewriteurl(path,context.request.url.query,out newpath);
//very wachky. the first call into rewritepath always fails with a 404.
//calling rewritepath twice actually fixes the probelm as well. instead,
//we use the second rewritepath overload and it seems to work 100%
//of the time.
if(isrewritten && newpath != null)
{
string qs = null;
int index = newpath.indexof('?');
if (index >= 0)
{
qs = (index < (newpath.length - 1)) ? newpath.substring(index + 1) : string.empty;
newpath = newpath.substring(0, index);
}
context.rewritepath(newpath,null,qs);
}
return isrewritten;
}
#endregion
这个事件主要做两个事情
a:为发出请求的用户初始化一个context,初始化context用到了线程中本地数据槽(localdatastoreslot),把当前用户请求的上下文(contextb)保存在为此请求开辟的内存中。
b:判断是否需要重写 url(检查是否需要重写的过程是对siteurls.config文件中正则表达式和对应url处理的过程),如果需要重写url,就执行asp.net级别上的rewritepath方法获得新的路径,新的路径才是真正的请求信息所在的路径。这个专题不是讲url rewrite,所以只要明白url在这里就进行rewrite就可以了,具体的后面专题会叙述。
处理完 application_beginrequest 后进程继向下执行,随后触发了application_authenticaterequest(如果有朋友不明白这个执行过程,可以通过调试中设置多个断点捕获事件执行的顺序。如果你还不会调试,可以留言偷偷的告诉我,嘿嘿。), application_authenticaterequest事件初始化一个context的identity,其实cs提供了很多的 identity支持,包括microsoft passport,但是目前的版本中使用的是默认值 system.web.security.formsidentity。具体代码如下:
private void application_authenticaterequest(object source, eventargs e)
{
httpcontext context = httpcontext.current;
provider p = null;
extensionmodule module = null;
// if the installer is making the request terminate early
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer) {
return;
}
// only continue if we have a valid context
//
if ((context == null) || (context.user == null))
return;
try
{
// logic to handle various authentication types
//
switch(context.user.identity.gettype().name.tolower())
{
// microsoft passport
case "passportidentity":
p = (provider) csconfiguration.getconfig().extensions["passportauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// windows
case "windowsidentity":
p = (provider) csconfiguration.getconfig().extensions["windowsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// forms
case "formsidentity":
p = (provider) csconfiguration.getconfig().extensions["formsauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
// custom
case "customidentity":
p = (provider) csconfiguration.getconfig().extensions["customauthentication"];
module = extensionmodule.instance(p);
if(module != null)
module.processrequest();
else
goto default;
break;
default:
cscontext.current.username = context.user.identity.name;
break;
}
}
catch( exception ex )
{
csexception forumex = new csexception( csexceptiontype.unknownerror, "error in authenticaterequest", ex );
forumex.log();
throw forumex;
}
// // get the roles the user belongs to
// //
// roles roles = new roles();
// roles.getuserroles();
}
再下来是application_authorizerequest事件被触发,事件代码如下:
private void application_authorizerequest (object source, eventargs e) {
if (csconfiguration.getconfig().applocation.currentapplicationtype == applicationtype.installer)
{
//cscontext.create(context);
return;
}
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
cscontext cscontext = cscontext.current;
//bool enablebanneduserstologin = cscontext.current.sitesettings.enablebanneduserstologin;
// // if the installer is making the request terminate early
// if (cscontext.applicationtype == applicationtype.installer) {
// return;
// }
//cscontext.user = cscontext.current.user;
csevents.userknown(cscontext.user);
validateapplicationstatus(cscontext);
// track anonymous users
//
users.trackanonymoususers(context);
// do we need to force the user to login?
//
if (context.request.isauthenticated)
{
string username = context.user.identity.name;
if (username != null)
{
string[] roles = communityserver.components.roles.getuserrolenames(username);
if (roles != null && roles.length > 0)
{
cscontext.rolescachekey = string.join(",",roles);
}
}
}
}
在application_authorizerequest中分析关键几行代码:
1:cscontext cscontext = cscontext.current; //该代码取出在前一个事件中保存在localdatastoreslot中的context,说明白点就是从内存中取出之前保存的一些数据。
2: csevents.userknown(cscontext.user); //这里触发了一个userknown事件,涉及到cs中大量使用委托与事件的一个类csapplication(csapplication.cs文件),后续对这个类做专题分析,这里只要先了解该事件起到判断登陆用户是否 forcelogin以及登录的帐户是否是禁用就可以了(把对user的判断移入application_authorizerequest事件处理程序中是很好的一种处理方法)
3:validateapplicationstatus(cscontext); //判断论坛、blog、相册是否被禁用,如果登录用户的角色不为isadministrator,就跳转到相应的禁用警告页面,如blog被禁用即跳转到 blogsdisabled.htm页面显示。
4:users.trackanonymoususers(context); //如果是匿名用户,在这个方法中跟踪记录。
处理完上面三个事件后,cs将开始处理请求页面中的具体业务逻辑,如果用户请求的是登录页面,接下来就处理登录页面需要的业务逻辑和呈现,当然这里还会触发一系列其他事件,因为这些事件没有在这里定义我们暂时不做考虑。要说明一点,httpmodule在整个web请求到响应完成过程中都没有退出进程,而是处于监控状态。application_onerror正是处于其监控范围下的一个事件,一旦有exception或者继承exception的类被异常抛出,httpmodule就捕获它,之后就可以根据exception中exceptiontype值统一处理这些不同的错误信息。cs中就是这样实现错误处理的,具体的我们看一下代码:
private void application_onerror (object source, eventargs e)
{
httpapplication application = (httpapplication)source;
httpcontext context = application.context;
csexception csexception = context.server.getlasterror() as csexception;
if(csexception == null)
csexception = context.server.getlasterror().getbaseexception() as csexception;
try
{
if (csexception != null)
{
switch (csexception.exceptiontype)
{
case csexceptiontype.userinvalidcredentials:
case csexceptiontype.accessdenied:
case csexceptiontype.administrationaccessdenied:
case csexceptiontype.moderateaccessdenied:
case csexceptiontype.postdeleteaccessdenied:
case csexceptiontype.postproblem:
case csexceptiontype.useraccountbanned:
case csexceptiontype.resourcenotfound:
case csexceptiontype.userunknownloginerror:
case csexceptiontype.sectionnotfound:
csexception.log();
break;
}
}
else
{
exception ex = context.server.getlasterror();
if(ex.innerexception != null)
ex = ex.innerexception;
csexception = new csexception(csexceptiontype.unknownerror, ex.message, context.server.getlasterror());
system.data.sqlclient.sqlexception sqlex = ex as system.data.sqlclient.sqlexception;
if(sqlex == null || sqlex.number != -2) //don't log time outs
csexception.log();
}
}
catch{} //not much to do here, but we want to prevent infinite looping with our error handles
csevents.csexception(csexception);
}
当抛出exception后,cs开始处理application_onerror,根据抛出的exception的exceptiontype类型不同做不同的处理(forumexceptiontype.cs中定义所有的cs exceptiontype)。随后调用log()保存错误信息到数据库中,以便管理员跟踪这些错误的原因。这里还有重要的一句:csevents.csexception(csexception)它触发了2个事件类 cscatastrophicexceptionmodule与csexceptionmodule中的处理程序,与 application_authorizerequest中userknown处理机制是一样的,会在以后的专题讨论。只要知道这里会执行 redirecttomessage方法,把页面重新定向到一个友好的错误显示页即可,如下图所示:
至此,cshttpmodule类已经全部分析完毕。在cs里还有另外两个httpmodule,属于membership范畴,由于cs引用的是 membership的程序集无非进行内部的运行细节分析,但是工作原理与cshttpmodule是一致的,当你真正理解cshttpmodule的时候要去分析其他httpmodule也就不在话下了。希望我的这些分析能对你有帮助。
原文地址: