一.HTTP请求
我们自己写的程序,是怎样进行处理的?一个完整的HTTP请求流程:
1.用户浏览器输入地址,例如 http://www.csdn.net
2.DNS解析(域名供应商):将输入的网址解析成IP+端口
3.请求到达服务器Server:IP可以在互联网上唯一定位一台服务器,而端口是用来确定进程的,端口还可以带有协议信息,用于穿过防火墙。
4.HTTP.SYS服务接收HTTP请求:
我们可以自己用IIS部署一个网站,模拟HTTP请求。顺序是部署网站----指定一个端口监听----请求到服务器----带了端口信息和协议----被HTTP.SYS监听到。HTTP.SYS是安装IIS时自动装上去的。
5.IIS将请求转发给ISAPI
IIS不能处理我们写的代码,它只能将我们的代码转发到对应的程序进行处理,它里面有一个“处理映射程序”,这里配置的是IIS的处理方式,即请求是什么后缀名,就用哪种dll处理程序进行处理,其中*.cshtml、*.aspx、*.ashx都是由asp.net_isapi.dll来进行处理,如图
因为IIS只是将请求根据后缀转发到不同的处理程序,所以我们甚至可以给java和php指定处理程序。比如将php转发给php_ISAPI,java转发给java_ISAPI,只要配置好就可以实现。
如果是js,css,html等静态文件,IIS是直接返回的。
这里有个小问题,聪明的你可能会说,像mvc这样 Home/Index,没有后缀怎么办?
IIS6和它之前都不支持mvc的,后来出现了mvc,没有后缀怎么写匹配?IIS会给没有后缀的加一个axd的后缀再处理,如图:
到了IIS7.0,就不需要这么做了。
IIS的应用程序池分为集成和经典,如图:
经典模式表明是旧的,所以一般都用集成模式,
6.HttpWorkerRequest:在上一步,会将请求包装成一个对象,通过pipeline传到这里来,这里才是asp.net开发的入口,前面是系统帮我们做好的,我们程序员是从这里开始搞事情的!
好了,终于进到我们自己写的程序中来了,看看HttpWorkerRequest的定义,如图:
7.HttpRuntime.processrequest
HttpRuntime:Http运行时,processrequest 是它下面的一个方法,看定义,如图:
ProcessRequest 方法需要一个HttpWorkerRequest参数,也就是上一步得到的。
public ActionResult Index()
{
HttpRuntime.ProcessRequest(null);//web请求的入口
return View();
}
至于ProcessRequest是怎么处理的,就要进入“管道处理模型”了。
什么是“管道处理模型”呢?就是一个请求进入到HttpRunTime之后,也就是从第7步开始,要做的事情,就叫做“管道处理模型”,因为前6步都是系统做好的,我们管不着,从第7步才开始运行我们写的代码。
我们用反编译工具打开System.Web.HttpRunTime,找到ProcessRequest
传入参数是HttpWorkerRequest类型的,如果传入null,抛出异常;如果没有使用管道模型,也抛出异常,如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr)。
这里有个RequestQueue,因为Http请求也可以队列,如果队列不为空,就执行GetRequestToExecute(wr)方法进行处理,再看下面的ProcessRequestNow(wr)方法:
它调用了ProcessRequestInternal(wr)方法:
ProcessRequestInternal(wr)方法是怎么做的呢?如果被释放了(disposingHttpRuntime),那么就发送一个503的错误,"Server Too Busy",并用一个html页面来显示。如果没有被释放,就往下走,初始化一个HttpContext,它是Http请求上下文,如果初始化HttpContext失败了,就用html页面给用户返回一个400错误,下面的代码比较长,我再抓一个下面的图给大家看:
如果HttpContext初始化成功了,就把它拿到HttpApplicationFactory.GetApplicationInstance方法里面创建了一个HttpApplication对象。
每个请求都经过了上面的步骤,创建了一个HttpApplication对象,用这个HttpApplication对象来处理请求,HttpApplication是我们管道模型的核心。
通过上述步骤,终于执行到了我们写的代码,前面我们几乎没做什么,都是框架做的,我们也扩展不了。
看看HttpApplication的代码:
为什么有这么多的事件呢?因为HttpApplication要处理各种不同的请求,每个请求也许要做相同的事情,也可能不同的事情,也可能要做的事情的顺序不同,我们要把共性的部分封装起来,所以就封装成了这么多的事件(event),这样做的好处就是,遇到不同的请求,我们可以把这些事件排列和组合起来,就能完成请求的处理。
下图是对各个事件功能的介绍:
其中BeginRequest和EndRequest是方便我们做扩展的,可以在这两个方法里面加上我们要的触发动作。
PostMapRequestHandler就是把我们的请求创建一个处理器对象,我们写的MVC,Webform,都在它里面,要让它来实际执行的。
二.HttpModule
先写一段代码,用反射的方式获取所有系统自带的HttpApplication 的 Event事件:
public ViewResult Events()
{
//获取当前上下文的HttpApplication实例
HttpApplication app = base.HttpContext.ApplicationInstance;
List sysEventsList = new List();
int i = 1;
foreach (EventInfo e in app.GetType().GetEvents())
{
sysEventsList.Add(new SysEvent()
{
Id = i++,
Name = e.Name,
TypeName = e.GetType().Name
});
}
return View(sysEventsList);
}
运行一下,25个Event事件:
先说一下,HttpModule是什么:对HttpApplication做的扩展事件(上面图上,都是HttpApplication原本有的事件)。先看一下IHttpModule接口里面是什么:
很简单对不对?Init方法,参数是HttpApplication。接下来,我们就做对IHttpModule做一个实现吧:
新建一个MyCustomModule,继承自IHttpModule,具体做的事情就是给原有事件输出一段文字。
public class MyCustomModule : IHttpModule
{
public void Dispose()
{
//此处放置清除代码。
}
public void Init(HttpApplication application)
{
application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AcquireRequestState "));
application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AuthenticateRequest "));
application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AuthorizeRequest "));
application.BeginRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "BeginRequest "));
application.Disposed += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "Disposed "));
application.EndRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "EndRequest "));
application.Error += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "Error "));
application.LogRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "LogRequest "));
application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "MapRequestHandler "));
application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAcquireRequestState "));
application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAuthenticateRequest "));
application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAuthorizeRequest "));
application.PostLogRequest += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostLogRequest "));
application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostMapRequestHandler "));
application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostReleaseRequestState "));
application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostRequestHandlerExecute "));
application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostResolveRequestCache "));
application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostUpdateRequestCache "));
application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreRequestHandlerExecute "));
application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreSendRequestContent "));
application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreSendRequestHeaders "));
application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "ReleaseRequestState "));
application.RequestCompleted += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "RequestCompleted "));
application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "ResolveRequestCache "));
application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("来自MyCustomModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "UpdateRequestCache "));
}
}
好了,自定义的MyCustomModule写好了,但是还不能使用,要去webconfig注册,
注册后,每个页面都会执行这个Module。
运行一下,发现注册后比以前多了一些东西:
MyCustomModule被从头到尾执行了一遍,这些蓝色的文字,说明在这个module中执行了哪些事件以及执行的顺序,而以前通过反射找到HttpApplication中事件(ShowEvents方法),是在PreRequstHandlerExecute方法中执行的,执行完后,还进行了收尾操作,比如PostRequestHandlerExecute(已经执行了处理程序),ReleaseRequestState(释放请求的状态),PostReleaseRequestState(已经释放了请求的状态)等等。
好了,上面就是我们自定义的Module,现在看看框架自带的Module,打开
web.config中有一段:
看到没?有OutputCache,Session,WindowsAuthentication等,这些都是.net4.0框架自己注册好了的Module,是全局的,运行在这台服务器上的Web程序都要用到这个config,每个页面用到了这些Module。但是这里列的一些Module我们是用不上的,比如WindowsAuthentication,FormsAuthentication,PassportAuthentication,可以干掉的,不过不要在这个全局的webconfig中干掉,可以在每个项目的webconfig中干掉,比如刚才项目的webcongif中可以加这么一些代码干掉不要的Module:
有没有干掉呢?看效果:
写一个方法,获取全部的Module,包括我们扩展的MyCustomModule,还有框架自带的
public ViewResult Modules()
{
HttpApplication app = base.HttpContext.ApplicationInstance; //获取当前上下文的HttpApplication实例
List sysModulesList = new List();
int i = 1;
foreach (string name in app.Modules.AllKeys)
{
sysModulesList.Add(new SysModules()
{
Id = i++,
Name = name,
TypeName = app.Modules[name].ToString()
});
}//1 我们自定义配置的config 2 来自于.Net框架的配置
return View(sysModulesList);
}
运行一下,发现在webconfig中remove的Module,它们都不出现了:
总结一下,HttpModule要实现IHttpModule接口,在webconfig注册,里面给HttpApplication事件去添加动作,并且HttpModule是每个页面(每次请求)都要执行的。
三.HttpModule 能干什么
1.权限认证:每个请求都经过Module,所以做权限认证很好。
2.URL转发:
新注册一个BaseModule
Controller添加对应方法
public class BaseModule : IHttpModule
{
///
/// Init方法仅用于给期望的事件注册方法
///
///
public void Init(HttpApplication httpApplication)
{
httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
}
}
// 处理BeginRequest 事件的实际代码
private void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
{
context.Response.Write(string.Format("来自BaseModule 的处理,{0}请求到达
", DateTime.Now.ToString()));
}
//处理地址重写
if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
context.RewritePath("/Pipe/Handler");
}
注意这两句,先获取到了HttpApplication,后来又获取到了HttpContext,有了HttpContext就有了全世界。
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
接下来如果访问 "/Pipe/Some",Module会偷偷跳到"/Pipe/Handler",而且浏览器的地址栏不改变,还是"/Pipe/Some"。
MVC其实就是Module的扩展,即上面说过的UrlRoutingModule, MVC框架的诞生对webform没有影响,只是在原来框架流程上增加了一个UrlRoutingModule,它会把我们的请求做一次映射处理,指向MvcHandler,MVC路由为什么能生效啊?就是利用了UrlRoutingModule,处理方式和上面说的Url转发是一样的。
伪静态也可以这样做,先在IIS里面给后缀指定处理程序asp.net_isapi(上面有介绍),请求进到HttpApplication后,我们扩展一个Module,在里面建立一些跳转页面的规则。
3.反爬虫:
每个请求都要经过Module,所以可以记录每个IP,如果某个IP请求太频繁,那么就不让它访问原始页面,让它访问一个需要输入验证码的页面,验证通过了才能继续访问。
但也有Module不适合的场景,比如对一些特殊请求的处理。Module是每个请求都会执行的,单独给某些请求服务的不合适。
又总结一下,管道处理模型就是请求进入System.Web(HttpRuntime.processrequest)之后,那些处理的类、方法、过程,这些就叫管道处理模型,mvc只是其中很小的一部分。
四.Module的扩展
新注册一个GlobalModule,添加事件
public class GlobalModule : IHttpModule
{
public event EventHandler GlobalModuleEvent;
///
/// Init方法仅用于给期望的事件注册方法
///
///
public void Init(HttpApplication httpApplication)
{
httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
}
// 处理BeginRequest 事件的实际代码
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
context.Response.Write(string.Format("来自GlobalModule 的处理,{0}请求到达
", DateTime.Now.ToString()));
//处理地址重写
if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
context.RewritePath("/Pipe/Handler");
if (GlobalModuleEvent != null)
GlobalModuleEvent.Invoke(this, e);
}
// 处理EndRequest 事件的实际代码
void context_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
context.Response.Write(string.Format("
来自GlobalModule的处理,{0}请求结束
", DateTime.Now.ToString()));
}
public void Dispose()
{
}
}
注意看这里了:
public event EventHandler GlobalModuleEvent;
本来Module是用来添加事件的,比如:
httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
这里结果又给Module配置了一个事件GlobalModuleEvent。
怎样让这个GlobalModuleEvent生效呢?在global里面添加代码:
///
/// HttpModule注册名称_事件名称
/// 约定的
///
///
///
protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e)
{
Response.Write("来自 Global.asax 的文字 GlobalModule_GlobalModuleEvent");
}
其中方法名 = webconfig中注册的Module名称 + ‘_’+ Module中的事件名称
这样,这个方法就会自动触发,如图:
有没有类似的?有的,大家应该见过这个,也是在global里面
protected void Session_Start(object sender, EventArgs e)
{
// 在新会话启动时运行的代码
Console.WriteLine("Session_Start 啥也不干");
logger.Info("Session_Start");
}
protected void Session_End(object sender, EventArgs e)
{
// 在会话结束时运行的代码。
// 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
// InProc(默认内存里) 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer
// 或 SQLServer,则不会引发该事件。
Console.WriteLine("Session_End 啥也不干");
logger.Info("Session_End");
}
Start和End就是Session这个Module里面定义的Event。有证据吗?有的!先看看全局的webconfig,session注册过。
然后通过反编译工具,找到SessionState这个类,从它内部找到了Start和End。
这样做的好处是:我们封装了Module之后,Module里面还有要扩展的,就可以做成Event,在global里面去实现,并且也只能在global里面实现,什么时候执行这个Event呢?根据自己的需求。使用套路如图:
五.其他
///
/// 请求出现异常,都可以处理
/// 也可以完成全局异常处理
/// filter只能处理控制器里面的异常
///
///
///
protected void Application_Error(object sender, EventArgs e)
{
logger.Info("Application_Error");
Response.Write("出错");
Server.ClearError();
}
Application_Error是处理异常的,filter也是处理异常的,但前提是要进入了控制器,如果是cshtml出错了,或者页面不存在,filter就管不到了,但可以被Application_Error处理,它可以捕获整个网站的异常,不管是控件级事件、页面级事件还是请求级事件,都可以get到。