视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Community Server专题三:HttpModule
2020-11-27 22:45:32 责编:小采
文档


从专题三开始分析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 = "http://www.";
            const string noWWW = "http://";
            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 = "http://www.";
            const string noWWW = "http://";
            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)
                   

下载本文
显示全文
专题