dotText源码阅读(5)--URLreWrite和Handler
dotText源码阅读(5)--URLreWrite和Handler Dottext需要映射全部不存在的文件到blog应用程序,实际上是需要IIS对于该应用下的问不进行处理,而是交给dottext程序处理,而dottext则利用一系列的handler来进行配置,对应不同的文件类型,或者匹配特定的文件,实现整个blog的URL 重写的。 首先,是通过<httpHandlers><addverb='*'path='*.asmx'type='System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'validate='false'/> <addverb='*'path='Error.aspx'type='System.Web.UI.PageHandlerFactory'/><addverb='*'path='*'type='Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common'/> </httpHandlers>确保了任何对blog所在应用程序的访问都会被以上3个handler处理,如果是扩展名.asmx的http请求,会被系统缺省的处理程序处理;而对于错误处理(大多数都是转到error.aspx)会转入到系统的缺省aspx处理程序,其他任何请求都会转到Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common 所以我们首先来看看这个处理句柄:这是一个工厂类型的执行句柄,他自身并不进行处理。而是负责将请求根据不同的类别进行分别派遣,调出不同的处理程序进行执行,而这构成了dottext高效处理整个blog运行的精妙设计部分。protected virtual HttpHandler[] GetHttpHandlers(HttpContext context) { return HandlerConfiguration.Instance().HttpHandlers;//这是个收集 }是IhttpHandler接口的实现,用于返回处理http请求的全部句柄。而句柄配置在web.config中,所以dottext是这样获得全部handler的。而HandlerConfiguration.Instance()类似我们前面分析的配置处理体系那里的处理过程:public static HandlerConfiguration Instance() { return ((HandlerConfiguration)ConfigurationSettings.GetConfig('HandlerConfiguration')); }是从配置文件的xml片断中,获得产生具体的类实例,并经过反序列化后(请看看HandlerConfiguration的属性定义),得到一个句柄的数组,返回给UrlReWriteHandlerFactory的调用函数。具体为:public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string path) { HttpHandler[] items = GetHttpHandlers(context); if(items != null) { int count = items.Length; string appStr=Dottext.Framework.Util.Globals.RemoveAppFromPath(context.Request.Path,context.Request.ApplicationPath);//得到访问哪一个应用程序的哪一个具体文件 for(int i = 0; i<count; i++) { //定向到特定的aspx文件 if(items[i].IsMatch(appStr)) //看看是否匹配系统配置中的者则表达式 { //throw new Exception(); switch(items[i].HandlerType) { case HandlerType.Page://默认是Page return ProccessHandlerTypePage(items[i],context,requestType,url); case HandlerType.Direct: HandlerConfiguration.SetControls(context,items[i].BlogControls); return (IHttpHandler)items[i].Instance(); case HandlerType.Factory: return ((IHttpHandlerFactory)items[i].Instance()).GetHandler(context,requestType,url,path); default: throw new Exception('Invalid HandlerType: Unknown'); } } } } //如果请求的页面不匹配任何一个句柄,就使用ASP.NET的 return PageHandlerFactory.GetHandler(context,requestType,url, path); }获得全部web.config指定的handler以及正则表达式后,就进行匹配当前http访问请求的处理分析,如果请求的URL字符串匹配一个Page类型正则表达式(HttpHandler是一个实体类,通过反序列化后获得了type和Pattern属性,如果请求的资源是aspx或者html的,那么会进行正则式判断是否符合句柄的模式,)如果符合,那么同时就知道了句柄的类型,根据HandlerType.Page 、HandlerType.Direct、HandlerType.Factory进行分别处理。
如果是Page类型(某个aspx页面),那么执行:private IHttpHandler ProccessHandlerTypePage(HttpHandler item, HttpContext context, string requestType, string url) { string pagepath = item.FullPageLocation; if(pagepath == null) { pagepath = HandlerConfiguration.Instance().FullPageLocation; } HandlerConfiguration.SetControls(context,item.BlogControls); IHttpHandler myhandler=PageParser.GetCompiledPageInstance(url,pagepath,context); return myhandler; }HandlerConfiguration.Instance()的代码如下:public static HandlerConfiguration Instance() { return ((HandlerConfiguration)ConfigurationSettings.GetConfig('HandlerConfiguration')); }同样,这也是配置文件通过反序列化得到一个HandlerConfiguration的实例,HandlerConfiguration的配置节内容在web.config中存在,我们会得到一个defaultPageLocation属性,FullPageLocation如果在属性无法获取的时候就返回defaultPageLocation的值,也就是说,通常我们访问某个目录,不带指定的aspx 的page文件名,就会自动访问defaultPageLocation.的指示的值。SetControls 是针对部分页面的,就是类似<HttpHandlerpattern='/archive/\d{4}/\d{1,2}\.aspx$'controls='ArchiveMonth.ascx'/>这类page 的,通常是指向一个用户控件,而大家知道用户控件实际上就是一个page。SetControls会把控件加入到当前请求的context重,以便执行期间从context中区的控件。PageParser对象实际上是asp.net的解释对对象,它将指定的资源编译成程序集,这类似一个普通的物理存在的aspx页面执行机制。大家注意到,返回的是一个IhttpHandler对象,实际上asp.net的任何一个page都应该实现这个接口的,所以此处的逻辑就相当于执行了一个存在的页面。虽然页面可能不存在,但是通过配置指定最后得到了一个IhttpHandler对象处理了用户的http请求,这是Page类型的处理过程简要描述。 第二类HandlerType是Direct ,有以下的http资源请求定向到这类Handler:<HttpHandlerpattern='(\.config|\.asax|\.ascx|\.config|\.cs|\.vb|\.vbproj|\.asp|\.licx|\.resx|\.resources)$' type='Dottext.Framework.UrlManager.HttpForbiddenHandler, Dottext.Framework'handlerType='Direct'/> <HttpHandlerpattern='(\.gif|\.js|\.jpg|\.zip|\.jpeg|\.jpe|\.css|\.rar|\.xml|\.xsl)$'type='Dottext.Common.UrlManager.BlogStaticFileHandler, Dottext.Common'handlerType='Direct'/> ...... <HttpHandlerpattern='/services\/pingback\.aspx$'type='Dottext.Framework.Tracking.PingBackService, Dottext.Framework' handlerType='Direct'/> <HttpHandlerpattern='/services\/metablogapi\.aspx$'type='Dottext.Framework.XmlRpc.MetaWeblog, Dottext.Framework' handlerType='Direct'/> 可以看到,大部分我们找不到实际的文件名,但是却可以通过访问blog下的url返回内容,系统根据url判断如何返回内容。我们举一个例子来看看Direct怎么执行的。看看<HttpHandlerpattern='/rss\.aspx$'type='Dottext.Common.Syndication.RssHandler, Dottext.Common' handlerType='Direct'/>执行:case HandlerType.Direct: HandlerConfiguration.SetControls(context,items[i].BlogControls); return (IHttpHandler)items[i].Instance();时候,会实例化一个Dottext.Common.Syndication.RssHandler类的实例(RssHandler是间接实现了IhttpHandler接口的),它继承自抽象类BaseSyndicationHandler,BaseSyndicationHandler实现了总体的返回特定格式RSS文档的功能和能力,通过继承覆盖,不同的格式的实现类(RSS20和ATOM等)实现了各自格式的rss文档返回给用户。总之,在这类的handler中,最终通过Context.Response操纵到客户的输出流。 第三类的是Factory类型的,其自身就是一个工厂模式的handler,会再次将当前url转交给下一级handler,这样实现了可扩展性。如果dottext的新功能需要进一步处理URL得到其他功能就可以利用此类进行处理。譬如:<HttpHandlerpattern='/(?:admin)'type='Dottext.Web.UI.Handlers.BlogExistingPageHandler, Dottext.Web'handlerType='Factory'/>当用户访问应用程序下的/admin目录时候,自然处于该Dottext.Web.UI.Handlers.BlogExistingPageHandle 处理。由于是工厂模式,所以我们着重看看:public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string path) { BlogConfig config = Config.CurrentBlog(context); if(ConfigProvider.Instance().IsAggregateSite) { string app = config.Application.ToLower(); url = Regex.Replace(url,app,'/',RegexOptions.IgnoreCase);app = '\\\\'+config.CleanApplication+'\\\\'; path = Regex.Replace(path,app,'/',RegexOptions.IgnoreCase); if(!Regex.IsMatch(path,'\\.\\w+$')) { path = System.IO.Path.Combine(path,'index.aspx'); } } return PageParser.GetCompiledPageInstance(url, path, context); }处理时候,首先取得当前blog的配置,ConfigProvider.Instance()返回一个Iconfig接口的实例,看看这个Instance的代码: static ConfigProvider() //静态构造函数 { ConfigProviderConfiguration cpc = Config.Settings.BlogProviders.ConfigProvider; config = (IConfig)cpc.Instance(); config.Application = cpc.Application; config.CacheTime = cpc.CacheTime; config.Host = cpc.Host; config.ImageDirectory = cpc.ImageDirectory; config.BlogID = cpc.BlogID; } private static IConfig config = null; public static IConfig Instance() { return config; }执行静态构造函数,通过Config.Settings.BlogProviders.ConfigProvider反序列化得到ConfigProviderConfiguration。ConfigProviderConfiguration继承抽象类BaseProvider,通过BaseProvider的instance方法:public object Instance() { return Activator.CreateInstance(System.Type.GetType(this.ProviderType)); }此处的ProviderType是通过反序列化得到:[XmlAttribute('type')] public string ProviderType { get { return _type; } set { _type = value; } }也就是<ConfigProvidertype='Dottext.Common.Config.MultipleBlogConfig, Dottext.Common'host='localhost' cacheTime='120'/> 中指明的MultipleBlogConfig 类型。MultipleBlogConfig继承自BaseBlogConfig , BaseBlogConfig实现了IConfig,所以你才看到config = (IConfig)cpc.Instance();然后,得到了相应的属性IsAggregateSite。该属性的意思是当前访问的是否是聚合站点(而不是但个博客的站点,只有聚合站点才可以使用存在的aspx文件)。确认聚合站点后,就取得应用程序目录下的实际aspx文件,然后利用CLR的功能PageParser.GetCompiledPageInstance(url, path, context)返回页面执行结果。所有blog的http请求,依据URL通过正则表达式匹配到不同的Handler类型,实现了3种类别的处理,但最终用户看到的是请求执行结果。修改web.config我们可以进行特定资源的特殊执行,这是UrlReWrite的实质。