第一章 The SharePoint 2007 Architecture
1.ASP.NET Hosting Environment
ASP.NET能够被托管到不同的环境中,比如,IIS 5.0, IIS 6.0, IIS 7.0,也可以托管到一个定制的受管制的应用程序中,比如控制台程序。把ASP.NET 托管到一个特定的环境中,需要两个组件。
Worker request class,这个类是从HttpWorkerRequest抽象基类继承而来,所有的Worker request class 都需要实现HttpWorkerRequest API,这些API把HttpRuntime和底层通信的具体环境细节隔离开来,这样,任何一个实现了HttpWorkerRequest API的运行环境都可以托管 ASP.NET。
Runtime class.对于每一个客户端的请求,Runtime Class都必须完成两个任务。
1. 实例化和初始化对应的worker request class。
2. 调用对应的HttpRuntime的ProcessRequest方法,传递worker request class实例完成请求。
public abstract class HttpWorkerRequest
{
public virtual void CloseConnection();
public abstract void EndOfRequest();
public abstract void FlushResponse(bool finalFlush);
public virtual byte[] GetPreloadedEntityBody();
public virtual int GetPreloadedEntityBody(byte[] buffer, int offset);
public abstract string GetQueryString();
public virtual int ReadEntityBody(byte[] buffer, int size);
public abstract void SendResponseFromFile(string filename, long offset,long length);
public abstract void SendResponseFromMemory(byte[] data, int length);
}
如上所示,HttpRuntime调用上面的HttpWorkerRequest的CloseConnection等函数时,不需要知道底层的运行环境是IIS5.0,IIS6.0还是IIS7.0。因为ASP.NET实现了标准的IIS5.0,IIS6.0以及IIS7.0版本的HttpWorkerRequest API。对于定制的受管制的应用程序,比如控制台程序,ASP.NET亦实现了标准的SimpleWorkerRequest API。
ASP.NET 2.0框架自带了两个重要的Runtime Classses,分别是ISPAIRuntime和PipelineRuntime。
ISPAIRuntime,适用于运行在ISAPI mode下边的IIS5.0,IIS6.0以及IIS7.0程序。
PipelineRuntime,只适用于运行在integrated mode边的IIS7.0程序。
由于表述了不同的环境,这些运行时类各自差异很大。但是它们都实现了两个一样的方法,分别是:
ProcessRequest used for ISAPIRuntim。
GetExecuteDelegate used for PipelineRuntime。
每个运行时在处理用户请求时都有两个重要的步骤:首先,实例化并且初始化一个worker request class。然后,把这个实例传递给一个对应的HttpRuntime方法处理该请求。
2. Internet Information Services ( IIS )
从IIS6.0开始,用户可以开发自己的ISAPI扩展和过滤模块来扩展服务器的功能。IIS通过下面的函数来和扩展模块通信。
DWORD WINAPI HttpExtensionProc (LPEXTENSION_CONTROL_BLOCK lpECB);
参数lpECB引用了和当前请求相关的Extension_Control_Block数据结构。
每一个ISAPI扩展模块都是为处理某个特定的资源而设计的,这些资源以特定的文件名后缀作区分。比如asp.dll处理所有以asp为后缀的页面。在安装的时候,ASP.NET自动把.aspx, .asmx, .asax, .ashx, 都注册到IIS的metabase中以处理相应的资源请求。
从IIS6.0开始,用户可以把web应用程序集结到应用程序池中,同一个应用程序池中的web应用程序分享同一个worker process,工作者进程是w3wp.exe的一个实例。
应用程序池的引入增加了服务器的可靠性和稳定性。Web应用程序不再运行在IIS进程内,所以,Web应用程序的不良行为将不会对服务器产生直接影响。由于每个应用程序池都是以进程为界定的,所以,应用程序池各自之间也互不影响。对某个特定的应用程序池进行升级或者排除错误时,都不需要重新启动整个服务器,这为管理员的工作带来很大便利。
在IIS 6.0中还引入了一个新的kernel-mode组件: http.sys。HTTP协议栈避免了worker process和IIS process之间的通信。早期的IIS是通过Windows Socket API (WinSock)来接收客户端的http请求以及发送反馈,WinSock运行在user-mode下面。
IIS6.0把每一个应用程序池下面的新增虚拟目录都注册到http.sys驱动程序,http.sys的主要功能是监听所有的http请求,并把它传递给对应的应用程序池的worker process。在http.sys里为每一个应用程序池都实现了一个相应的队列,所有的http请求都被依次排入队列,worker process只需要直接从相应队列中读取相应请求即可。这种机制避免了worker process和IIS process之间的通信。减少了系统的资源消耗,提升了性能。
这种机制同进也改善了系统的稳定性:当某个worker process的工作出现问题时,http.sys仍然继续工作,并且接收相应的客户端的http请求排入队列,等到服务器重新启动一个新的worker process来处理队列中的请求,用户请求会因为故障而延时,但不会被拒绝。
额外的系统性能提升是:http.sys实现了一个kernel – mode缓存,同样的请求可以直接从kernel – mode缓存读出。
3. SharePoint Extensions
SP WebService
SPWebService是SPWebApplication的容器。
SPApplicationPool
SPWebServiceCollection wsc = new SPWebServiceCollection(SPFarm.Local);
foreach (SPWebService ws in wsc)
{
SPApplicationPoolCollection apc = ws.ApplicationPools;
foreach (SPApplicationPool ap in apc)
{
Response.Write(ap.Name);
Response.Write(“ < /br > ”);
}
}
SPIisWebSite
HttpContext context = HttpContext.Current;
int instanceId = int.Parse(context.Request.ServerVariables[“INSTANCE_ID”],
NumberFormatInfo.InvariantInfo);
SPIisWebSite site = new SPIisWebSite(instanceId);
site.ServerComment = “My “ + site.ServerComment;
site.Update();
SPWebApplication
SPWebServiceCollection wsc = new SPWebServiceCollection(SPFarm.Local);
foreach (SPWebService ws in wsc)
{
SPWebApplicationCollection wac = ws.WebApplications;
foreach (SPWebApplication wa in wac)
{
Response.Write(wa.Name);
Response.Write(“ < br/ > ”);
}
}
SP WebApplicationBuilder
SPWebApplicationBuilder类的实例可以用来创建SPWebApplication类。
SPWebApplicationBuilder wab = new SPWebApplicationBuilder(SPFarm.Local);
wab.Port = 12000;
SPWebApplication wa = wab.Create();
wa.Provision();
ISAPI Runtime
public int ProcessRequest(IntPtr ecb, int iWRType)
{
HttpWorkerRequest request = CreateWorkerRequest(ecb, iWRType);
HttpRuntime.ProcessRequest(request);
return 0;
}
ASP.NET HTTP Runtime Pipeline
public static void ProcessRequest(HttpWorkerRequest wr)
{
HttpContext context1 = new HttpContext(wr, true);
IHttpHandler handler1 =
HttpApplicationFactory.GetApplicationInstance(context1);
if (handler1 is IHttpAsyncHandler)
{
IHttpAsyncHandler handler2 = (IHttpAsyncHandler)handler1;
context1.AsyncAppHandler = handler2;
handler2.BeginProcessRequest(context1, _handlerCompletionCallback,context1);
}
else
{
handler1.ProcessRequest(context1);
FinishRequest(context1.WorkerRequest, context1, null);
}
}
http请求总是被异步处理,所以上面程序中只会调用BeginProcessRequest。
HttpRuntime, HttpApplicationFactory, and HttpApplication以及后面要讨论的其它一些组件一起构成了ASP.NET HTTP 运行时管道。管道中的每一个组件都会从前一个组件中获取一个HttpContext对象,从对象中抽取所需要处理的信息,并把处理完的信息存回到对象中,最后把对象传递给下一个组件。每一次http请求都会由不同的管道来处理,同一个管道不可以被两次请求共享。
每个应用程序域都有一个HttpApplicationFactory对象实例, 这个对象的GetApplicationInstance方法可以用来获取当前应用程序的HttpApplicationFactory对象实例,这个实例在类的内部存储在_theApplicationFactory中。
global.asax
global.asax是可选的,它必须被放置在应用程序的根目录下,放置在其它子目录下的global.asax文件将被忽略。
如果当前应用程序的根目录下没有global.asax文件,GetApplicationInstance函数将会返回一个ASP.NET类HttpApplication的 一个实例来代表当前应用程序。
如果根目录下面存在global.asax文件,GetApplicationInstance函数将会返回一个HttpApplication子类的实例来代表当前应用程序。
这个HttpApplication派生的子类并不是一个ASP.NET框架的标准类。它是根据global.asax文件内容由GetApplicationInstance动态生成的,并且动态编译成组件。动态生成和编译只在首次访问时进行一次,只有当global.asax文件的内容或者时间戳发生改变时才会再次进行。
public class HttpApplicationFactory
{
private Stack _freeList;
private int _numFreeAppInstances;
internal static IHttpHandler GetApplicationInstance(HttpContext context)
{
GenerateAndCompileAppClassIfNecessary(context);
HttpApplication appInstance = null;
lock (_freeList)
{
if (_numFreeAppInstances > 0)
{
appInstance = (HttpApplication)_freeList.Pop();
_numFreeAppInstances--;
}
}
if (appInstance == null)
{
appInstance = InstantiateAppInstance();
appInstance.InitializeAppInstance();
}
return appInstance;
}
}
HttpApplication
每个HTTP模块都是一个实现了IHttpModule接口的类。InstantiateAppInstance方法首先读取配置文件中的< httpModules >单元,每个单元都会有一些< add >子元素。每个<add>子元素都两个重要的属性:名字和类型。
public interface IHttpModule
{
void Dispose();
void Init(HttpApplication context);
}
InitializeAppInstance方法首先读取每个<add>子元素的type属性和.Net refecltion来动态地实例化相应的HTTP模块。然后再调用HTTP模块的Init方法来初始化模块(参见上面接口的代码)。Init方法的主要功能是注册HttpApplication对象的事件处理句柄。
InitializeAppInstance方法然后实例化ApplicationStepManager的一个实例,HttpApplication对于请求的处理是由许多个有序的执行步骤组成的。每一个执行步骤都是一个IExecutionStep类型的对象,IExecutionStep总是和某个HttpApplication事件相关联,并且实现Execute方法来执行这些步骤。
ApplicationStepManager有两个方法:BuildSteps和ExecuteStage,前者负责IExecutionStep对象的生成,后者调用IExecutionStep的Execute方法完成对象的执行。在实例化一个ApplicationStepManager对象以后,InitializeAppInstanced 调用其BuildSteps依序生成相关事件的IExecutionStep对象,并把这些对象依序加入一个内部的集合中。
在ProcessRequest方法中调用的BeginProcessRequest方法,会在内部遍历这个集合,按照加入的顺序读出IExecutionStep对象,并且最终调用ApplicationStepManager的ExecuteStage方法来执行这些IExecutionStep对象。
SPHttpApplication
SPHttpApplication类主要重载了HttpApplication基类的GetVaryByCustomString和Init方法,并且提供了两个公共的方法:RegisterGetVaryByCustomStringHandler和DeregisterGetVaryByCustomStringHandler。
public sealed override string GetVaryByCustomString(HttpContext context, string custom)
{
StringBuilder stringBuilder = new StringBuilder();
readerWriterLock.AcquireReaderLock(-1);
try
{
foreach (IVaryByCustomHandler varyByCustomHandler in varyByCustomHandlers)
{
stringBuilder.Append(varyByCustomHandler.GetVaryByCustomString(this, context, custom));
}
}
finally
{
readerWriterLock.ReleaseReaderLock();
}
return stringBuilder.ToString();
}
SP RequestModule
当一个新的web应用程序被创建时,SharePoint自动在站点的根目录下增加一个web.config文件,此文件中包含< httpModules >配置单元。由于InitializeAppInstance方法是按照在< httpModules>配置单元中出现的顺序来调用HTTP模块的Init方法的,相关的事件处理程序也按照这个顺序注册的。
SharePoint自带了一个名为SPRequestModule的HTTP模块,注册了大部分的HttpApplication事件句柄,因为这些事件句柄被系统用来初始化SharePoint运行时环境。所以它必须要被放置在所有的HTTP模块前面。SharePoint在其前面加入< clear/ >元素 清除之前注册的所有HTTP模块,以确保SPRequestModule在其它HTTP模块被调用时已经完成运行时环境的初始化。
< httpModules >
<clear / >
<add name=”SPRequest”
type=”Microsoft.SharePoint.ApplicationRuntime.SPRequestModule,
Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c”/>
……
< /httpModules >
SPVirtualPathProvider
SPRequestModule实现IHttpModule接口,实例化SPVirtualPathProvider类,并将其注册到运行时环境中。
SPVirtualPathProvider virtualPathProvider = new SPVirtualPathProvider();
HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider);
ASP.NET 1.1的页面解析器只能装载web前端的文件系统中的页面,在SharePoint 2007以前的版本中,SharePoint自带了一个页面解析器,但是这个解析器无法解析User Control。ASP.NET 2.0的页面解析器把装载的逻辑判断转移到继承自VirtualPathProvider的一个组件中,通过VirtualPathProvider和组件通信完成工作。
SPVirtualPathProvider实现了VirtualPathProvider API,当要装载的页面是Application或者ghosted页面时,文件装载路径将被设置到web前端的文件系统中。当要访问的页面是unghosted页面时,文件装载路径将被设置到内容数据库中。
IHttpHandlerFactory and IHttpHandler
HttpApplication有一个IExecutionStep类型的MapRequestHandler的事件处理句柄, MapRequestHandle的Execute方法的主要工作是找出处理当前请求的HTTP handler 或者HTTP handler factory,所有的HTTP handler都必须实现IHttpHandler接口,所有的HTTP handler factory都必须实现IHttpHandlerFactory接口。
public interface IHttpHandler
{
void ProcessRequest(HttpContext context);
bool IsReusable { get; }
}
public interface IHttpHandlerFactory
{
IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
void ReleaseHandler(IHttpHandler handler);
}
MapRequestHandler的Execute方法通过web.config的< httpHandlers >配置单元来找出对应的事件处理句柄。
< httpHandlers >
< add path=”*.aspx” verb=”*” type=”System.Web.UI.PageHandlerFactory” / >
< add path=”*.ashx” verb=”*” type=System.Web.UI.SimpleHandlerFactory” / >
< add path=”*.asmx” verb=”*”
type=”System.Web.Services.Protocols.WebServiceHandlerFactory,
System.Web.Services, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a” / >
...
< /httpHandlers >
如上所示,<add>子元素的path属性指定了资源的虚拟路径,verb属性指定一些以逗号分割的HTTP动作,type属性指定了句柄初始化所需要的类型信息。Type属性包括5个以逗号分割的字符串,只有第一个子串是必须指定的,余为可选。
SP HttpHandler
ASP.NET自带了一个HTTP句柄DefaultHttpHandler用来定制所有的请求。当一个web应用程序被创建时,SharePoint自动在web.config的< httpHandlers >配置单元中加入如下代码:
< httpHandlers >
< remove verb=”GET,HEAD,POST” path=”*” / >
< add verb=”GET,HEAD,POST” path=”*”
type=”Microsoft.SharePoint.ApplicationRuntime.SPHttpHandler,
Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c” / >
......
< /httpHandlers >
和所有HTTP句柄一样,DefaultHttpHandler有一个ProcessRequest方法。并实现了IHttpAsyncHandler接口,这个接口有两个方法。
public interface IHttpAsyncHandler : IHttpHandler
{
IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
void EndProcessRequest(IAsyncResult result);
}
在DefaultHttpHandler中,实现了异步调用的BeginProcessRequest方法。
public virtual IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback asyncCallback, object asyncState)
{
this.context = context;
string virtualPath = OverrideExecuteUrlPath();
…
return context.Response.BeginExecuteUrlForEntireResponse(virtualPath,executeUrlHeaders, asyncCallback, asyncState);
}
…
}
如上所示,BeginProcessRequest首先调用OverrideExecuteUrlPath方法,这个方法不执行任何操作,继承自DefaultHttpHandler的子类将完成OverrideExecuteUrlPath方法的具体实现 。BeginExecuteUrlForEntireResponse在其内部调用HttpWorkerRequest 的BeginExecuteUrl方法来执行指定的URL。如前文所述,为了减轻HttpWorkerRequest和IIS之间的通信负荷,BeginExecuteUrl方法实际上直接用新的请求头和URL执行一个新的请求。
使用派生自DefaultHttpHandler句柄的一个好处是,不管是什么类型的资源,不管是什么样的HTTP动作,其请求都会遍历所有已注册的HTTP模块。所有的HTTP模块都会执行一些预处理任务,并把结果存储到传入的HttpContext对象中。
因为BeginExecuteUrlForEntireResponse把用户名和认证类型以参数的形式传递给了BeginExecuteUrl方法。所以使用派生自DefaultHttpHandler句柄的另一个好处是,不管是什么类型的资源,不管是什么样的HTTP动作,其请求都会使用同一个ASP.NET认证和授权模块来获取认证和授权。
SPHttpHandler是DefaultHttpHandler的一个子类,重载了OverrideExecuteUrlPath方法。作为继承的一个重要特性,SPHttpHandler亦会遍历所有的已注册HTTP模块,包括SPRequestModule模块和ASP.NET HTTP 模块。这样,所有请求的SharePoint execution context和ASP.NET execution context都能及时初始化。
因为应用程序域是CLR unloading unit,所以无法将组件从应用程序域中单独地卸载掉,必须将应用程序域卸载同时卸载所有的组件。不包含任何类的组件将被系统删除,如果当前有请求占用该组件,则该组件将被重命名,以待下次应用程序重启时删除。
当Default.aspx或者Default.aspx.cs的内容有所更正时,ASP.NET页面解析器都将引入一个新的编译的页面,而原来的老的页面组件一直滞留在内存中,直到下一次应用程序重启时被删除,或者当组件滞留到某一上限时,应用程序自动重启。当SharePoint站点非常庞大时,这些滞留在内存中的过期页面组件将产生严重的问题。
这就是为什么unghosted页面不使用标准的ASP.NET编译过程的原因。unghosted页面被从数据库中直接读出后,传递给ASP.NET页面解析器,SPVirtualPathProvider指定ASP.NET页面解析器以no–compile模式来解析页面,这样页面不被编译成组件,就避免了组件滞留内存的问题,从而大大提高了系统的稳定性。
由于页面是存储在数据库当中,没有被编译成组件,为了防止数据库注入攻击,缺省在safe mode下面运行,不支持inline code。并且页面只能使用在注册成为安全控件的服务器控件。所以当我们要实现一个自定义服务器控件时,必须在web.config的<SafeControl> 配置单元中增加相关条目。在生成web应用程序时,所有的SharePoint和ASP.NET的组件条目都自动被添加到<SafeControl> 配置单元中。
当我们用页面模板生成一个站点页面时,这些页面在没有被定制之前均保持在ghosted状态。由于对ghosted页面的访问是通过普通的ASP.NET页面解析器进行的,并没有启动安全模式,所以站点页面将可以包含inline code和服务器控件。此时,如果该页面被定制,其状态将变成unghosted状态,下次访问该页面,SharePoint将自动切换到安全模式,如果这个页面仍然包含那些inline code和服务器控件,将无法正常运行。为了避免这个错误,建议永远不要在页面模板中添加inline code和服务器控件。
本文深入探讨了SharePoint2007的架构,包括ASP.NET托管环境、IIS功能增强、SharePoint扩展机制等内容。重点介绍了HTTP运行时管道、虚拟路径提供者、HTTP模块及处理器工厂等工作原理。
&spm=1001.2101.3001.5002&articleId=5535224&d=1&t=3&u=8c53bf2bf1404151916032d95de2a7f9)
907

被折叠的 条评论
为什么被折叠?



