katana
开源许久,网上仍未搜索到对其源码的阅读总结,本人在工作中正好遇到数据处理流程框架设计,想来跟服务器处理request
和response
差不多,遂起了阅读katana
源码,并借鉴其设计的想法,磕磕碰碰,困难重重,所幸有一些收获,与大家交流交流。
katana源码 https://katanaproject.codeplex.com/
owin官网 http://owin.org/
两个最重要的数据结构
1. Environment
IDictionary<string, object>
官方解释:
This data structure is responsible for storing all of the state necessary for processing an HTTP request and response, as well as any relevant server state. An OWIN-compatible Web server is responsible for populating the environment dictionary with data such as the body streams and header collections for an HTTP request and response. It is then the responsibility of the application or framework components to populate or update the dictionary with additional values and write to the response body stream.
Environment
是在pipeline
中流动的数据,代表着一个具体的request
和response
,后文会介绍在每个pipeline stage
中会对这个dictionary
中自己关心的数据进行处理,并在进入下一个stage
的时候丢弃引用,采用的是原子操作,因而每个Environment
只存在一个pipeline stage
中。数据举例:
Required | Key Name | Value Description |
---|---|---|
Yes | owin.RequestBody | A Stream with the request body, if any. Stream.Null MAY be used as a placeholder if there is no request body. See Request Body. |
Yes | owin.RequestHeaders | An IDictionary<string, string[]> of request headers. See Headers. |
Yes | owin.RequestMethod | A string containing the HTTP request method of the request (e.g., “GET”, “POST”). |
Yes | owin.RequestPath | A string containing the request path. The path MUST be relative to the “root” of the application delegate. See Paths. |
Yes | owin.RequestPathBase | A string containing the portion of the request path corresponding to the “root” of the application delegate; see Paths. |
Yes | owin.RequestProtocol | A string containing the protocol name and version (e.g. “HTTP/1.0 ” or “HTTP/1.1 “). |
Yes | owin.RequestQueryString | A string containing the query string component of the HTTP request URI, without the leading “?” (e.g., “foo=bar&baz=quux “). The value may be an empty string. |
Yes | owin.RequestScheme | A string containing the URI scheme used for the request (e.g., “http”, “https”); see URI Scheme. |
2.AppFunc
Func<IDictionary<string, object>, Task>;
官方解释:
The second key element of OWIN is the application delegate. This is a function signature which serves as the primary interface between all components in an OWIN application. The definition for the application delegate is as follows:
The application delegate then is simply an implementation of the Func delegate type where the function accepts the environment dictionary as input and returns a Task. This design has several implications for developers:
- There are a very small number of type dependencies required in order to write OWIN components. This greatly increases the accessibility of OWIN to developers.
- The asynchronous design enables the abstraction to be efficient with its handling of computing resources, particularly in more I/O intensive operations.
- Because the application delegate is an atomic unit of execution and because the environment dictionary is carried as a parameter on the delegate, OWIN components can be easily chained together to create complex HTTP processing pipelines.
这就是middleware
,也是每个pipeline stage
中具体的处理方法,采用异步调用的方式,由StartUp
类进行注册,并生成一条链,实际上就是压进一个List
中。
源码阅读,能学到很多东西,肯定有很多理解有偏差的地方,欢迎指正,我将从一个具体的middleware
注册和StartUp
的执行切入,大致勾勒一个pipeline
的构造和流动过程。
OWIN中Environment
初始化
按照官方文档的解释,Microsoft.Owin.Host.SystemWeb
在启动的时候会进行一系列的初始化,具体的入口点隐藏太深无法寻找,我们假定现在流程已经到了Microsoft.Owin.Host.SystemWeb.OwinHttpHandler
这里,这里将进行Environment
和pipeline
的初始化。本文所涉及的class
大都在Microsoft.Owin.Host.SystemWeb
命名空间下。
先看Environment
的初始化,源码进行了精简,只留下重要部分,建议参考完整源码。
OwinHttpHandler
被实例化,参考OwinHttpHandlerTests
var httpHandler = new OwinHttpHandler(string.Empty, OwinBuilder.Build(WasCalledApp));
先不考虑OwinBuilder.Build
具体操作,httpHandler
将开始处理request
,即 public IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object extraData)
将被调用,用于初始化一个基本的OwinAppContext
,并根据httpContext
参数初始化一个RequestContext
,再将request
信息合并进入OwinAppContext
,从上下文开始执行
public IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object extraData)
{
…
try
{
OwinAppContext appContext = _appAccessor.Invoke();
//初始化基础OwinAppContext
Contract.Assert(appContext != null);
// REVIEW: the httpContext.Request.RequestContext may be used here if public property unassigned?
RequestContext requestContext = _requestContext ?? new RequestContext(httpContext, new RouteData());
string requestPathBase = _pathBase;
string requestPath = _requestPath ?? httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(1) + httpContext.Request.PathInfo;
OwinCallContext callContext = appContext.CreateCallContext(
//完善OwinAppContext信息
requestContext,
requestPathBase,
requestPath,
callback,
extraData);
try
{
callContext.Execute();
// 这是本文的重点,一个request进来之后,OWIN的处理流程已经开始
// 启动以后的处理的源码都是可见的
}
…
return callContext.AsyncResult;
}
…
}
而Excecute
方法中进行Environment
的再次初始化
internal void Execute()
{
CreateEnvironment();
//再次初始化Environment
…
}
这次初始化主要将上下文信息中将要进入pipeline
流动的数据存入AspNetDictionary
中,这其实是一个扩展的
IDictionary<string, object>
,也就是前文所介绍的第一个最重要数据结构,之后middleware
开始Invoke
,也就是pipeline
的第一个AppFunc
开始执行,参数就是初始化完成的Environment
。
那么很自然有个问题,pipeline
的第一个AppFunc
是怎么来的呢?pipeline
又是如何串起来的呢?这很自然就涉及到了前文所提到的第二个重要内容Func<IDictionary<string, object>, Task>
。Pipeline
定义为接收Environment
,返回一个Task
的Func
,当第一个AppFunc
(也就是Func<IDictionary<string, object>, Task>
)执行之后返回的其实又是一个AppFunc
,而这种链接关系是由OwinBuilder
建立的。
OwinBuilder
所做的事情主要寻找程序集中的StartUp
方法,并根据其中注册pipeline
的顺序将各个AppFunc
串起来,这将是一个浩大的工程。
OwinBuilder源码阅读
源码参见Microsoft.Owin.Host.SystemWeb.OwinBuilder
通过前文知道,Build
方法将被调用,其做的第一件事儿就是寻找Startup
方法
internal static OwinAppContext Build()
{
Action<IAppBuilder> startup = GetAppStartup();
return Build(startup);
}
而GetAppStartup
方法主要完成从当前Assembly
中寻找AppStartup
方法,这也是为什么申明Startup
,使其被调用有两种方法:
[assembly: OwinStartup(typeof(XX.Startup))] //利用OwinStartupAtrribute来指导Startup
<appSettings>
<add key="owin:appStartup" value="StartupDemo.ProductionStartup" />
</appSettings>
//在webconfig中定义owin:appStartup键值对,以下将对负责搜索Startup方法的DefaultLoader的源码进行分析,来了解如何定位Startup方法的
internal static Action<IAppBuilder> GetAppStartup()
{
string appStartup = ConfigurationManager.AppSettings[Constants.OwinAppStartup];
var loader = new DefaultLoader(new ReferencedAssembliesWrapper());
IList<string> errors = new List<string>();
Action<IAppBuilder> startup = loader.Load(appStartup ?? string.Empty, errors);
if (startup == null)
{
throw new EntryPointNotFoundException(Resources.Exception_AppLoderFailure
+ Environment.NewLine + " - " + string.Join(Environment.NewLine + " - ", errors)
+ (IsAutomaticAppStartupEnabled ? Environment.NewLine + Resources.Exception_HowToDisableAutoAppStartup : string.Empty)
+ Environment.NewLine + Resources.Exception_HowToSpecifyAppStartup);
}
return startup;
}
上面源码展示了调用DefaultLoader
的Load
方法来搜索Startup
,而Startup
是一个Action方法,即接受一个实现了IAppBuilder
接口的实例作为参数,返回值为void
的Action
public Action<IAppBuilder> Load(string startupName, IList<string> errorDetails)
{
return LoadImplementation(startupName, errorDetails) ?? _next(startupName, errorDetails);
}
Load
方法实际上是对LoadImplementation
的一个封装,如果寻找失败则使用_next
进行寻找(实际上这会返回null
,这不是重点)
private Action<IAppBuilder> LoadImplementation(string startupName, IList<string> errorDetails)
{
Tuple<Type, string> typeAndMethod = null;
startupName = startupName ?? string.Empty;
// Auto-discovery or Friendly name?
if (!startupName.Contains(','))
{
typeAndMethod = GetDefaultConfiguration(startupName, errorDetails); //通常会进入这一流程,如果startupName中包含逗号,则对应另一种申明方式
}
if (typeAndMethod == null && !string.IsNullOrWhiteSpace(startupName)) //这种申明方式为StartupName = “startupName,assemblyName”
{
typeAndMethod = GetTypeAndMethodNameForConfigurationString(startupName, errorDetails); //对startupName和assemblyName进行分离,并找到对应的assembly加载
//其中的startupName
}
if (typeAndMethod == null)
{
return null;
}
Type type = typeAndMethod.Item1;
// default to the "Configuration" method if only the type name was provided //如果只提供了startup的type,则默认调用其中的Configuration方法
string methodName = !string.IsNullOrWhiteSpace(typeAndMethod.Item2) ? typeAndMethod.Item2 : Constants.Configuration;
Action<IAppBuilder> startup = MakeDelegate(type, methodName, errorDetails); //直接调用startup方法或者做为一个middleware压入List中,后文会讲到具体实现
if (startup == null)
{
return null;
}
return builder => //再对startup进行一次delegate封装,传入参数为builder,供上层调用
{
if (builder == null)
{
throw new ArgumentNullException("builder");
}
object value;
if (!builder.Properties.TryGetValue(Constants.HostAppName, out value) ||
String.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture)))
{
builder.Properties[Constants.HostAppName] = type.FullName; //获取并记录HostAppName
}
startup(builder); //开始构造
};
}
由于参数startupName
为最初定义的常量,其值为Constants.OwinAppStartup = "owin:AppStartup";
所以很明显会调用GetDefaultConfiguration(startupName, errorDetails)
方法进一步处理。
private Tuple<Type, string> GetDefaultConfiguration(string friendlyName, IList<string> errors)
{
friendlyName = friendlyName ?? string.Empty;
bool conflict = false;
Tuple<Type, string> result = SearchForStartupAttribute(friendlyName, errors, ref conflict);
if (result == null && !conflict && string.IsNullOrEmpty(friendlyName))
{
result = SearchForStartupConvention(errors);
}
return result;
}
这个方法又是对SearchForStartupAttribute
的一个封装
先了解一下OwinStartupAttribute
看上文使用到的构造函数
public OwinStartupAttribute(Type startupType)
: this(string.Empty, startupType, string.Empty)
{
}
public OwinStartupAttribute(string friendlyName, Type startupType, string methodName)
{
if (friendlyName == null)
{
throw new ArgumentNullException("friendlyName");
}
if (startupType == null)
{
throw new ArgumentNullException("startupType");
}
if (methodName == null)
{
throw new ArgumentNullException("methodName");
}
FriendlyName = friendlyName;
StartupType = startupType;
MethodName = methodName;
}
这里默认将FriendlyName
和MethodName
设置为空,即只记录了Startup
类的Type
,下面的SearchForStartupAttribute
主要也是通过寻找OwinStartupAttribute
中的StartupType
来获取Startup
的。
private Tuple<Type, string> SearchForStartupAttribute(string friendlyName, IList<string> errors, ref bool conflict)
{
friendlyName = friendlyName ?? string.Empty;
bool foundAnyInstances = false;
Tuple<Type, string> fullMatch = null;
Assembly matchedAssembly = null;
foreach (var assembly in _referencedAssemblies) // 遍历程序集
{
object[] attributes;
try
{
attributes = assembly.GetCustomAttributes(inherit: false); // 获取程序集的所有自定义Attribute
}
catch (CustomAttributeFormatException)
{
continue;
}
foreach (var owinStartupAttribute in attributes.Where(attribute => attribute.GetType().Name.Equals(Constants.OwinStartupAttribute, StringComparison.Ordinal))) // 对获取到的Attribute进行过滤,只遍历OwinStartupAttribute,即是优先会 //对上文所说的第一种 Startup申明进行调用
{
Type attributeType = owinStartupAttribute.GetType(); //采用反射机制,先获取Type
foundAnyInstances = true;
// Find the StartupType property.
PropertyInfo startupTypeProperty = attributeType.GetProperty(Constants.StartupType, typeof(Type)); //寻找属性名是StartupType,属性类型是Type的属性
if (startupTypeProperty == null) //寻找失败,记录错误
{
errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyMissing,
attributeType.AssemblyQualifiedName, assembly.FullName));
continue;
}
var startupType = startupTypeProperty.GetValue(owinStartupAttribute, null) as Type; //获取StartupType属性的值,并转换为Type,为反射做准备
if (startupType == null) //获取或者转换失败,记录错误
{
errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyEmpty, assembly.FullName));
continue;
}
// FriendlyName is an optional property.
string friendlyNameValue = string.Empty; //FriendlyName是可选项,作为对Startup类的别称,不是重点
PropertyInfo friendlyNameProperty = attributeType.GetProperty(Constants.FriendlyName, typeof(string));
if (friendlyNameProperty != null)
{
friendlyNameValue = friendlyNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty;
}
if (!string.Equals(friendlyName, friendlyNameValue, StringComparison.OrdinalIgnoreCase)) //如果未定义FriendlyName则默认是Empty,否则记录错误
{
errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.FriendlyNameMismatch,
friendlyNameValue, friendlyName, assembly.FullName));
continue;
}
// MethodName is an optional property.
string methodName = string.Empty; 同理MethodName也是可选项,如果为定义默认是Empty
PropertyInfo methodNameProperty = attributeType.GetProperty(Constants.MethodName, typeof(string));
if (methodNameProperty != null)
{
methodName = methodNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty;
}
if (fullMatch != null) //表明已经寻找到一个Startup类,则冲突了,说明有重复申明Startup类
{
conflict = true;
errors.Add(string.Format(CultureInfo.CurrentCulture,
LoaderResources.Exception_AttributeNameConflict,
matchedAssembly.GetName().Name, fullMatch.Item1, assembly.GetName().Name, startupType, friendlyName));
}
else //尚未寻找到Startup类,将StartupType和MethodName存为二元组,记录程序集
{
fullMatch = new Tuple<Type, string>(startupType, methodName);
matchedAssembly = assembly;
}
}
}
if (!foundAnyInstances) //未寻找到申明Startup的程序集,记录错误
{
errors.Add(LoaderResources.NoOwinStartupAttribute);
}
if (conflict) //如果有冲突,返回null
{
return null;
}
return fullMatch; //返回结果
}
前文讲到MakeDelegate(Type type, string methodName, IList<string> errors)
主要作用是将寻找到的startup
方法作为一个middleware
压入List
中,看其源码
private Action<IAppBuilder> MakeDelegate(Type type, string methodName, IList<string> errors)
{
MethodInfo partialMatch = null;
foreach (var methodInfo in type.GetMethods())
{
if (!methodInfo.Name.Equals(methodName))
{
continue;
}
// void Configuration(IAppBuilder app) //检测Startup类中的Configuration方法的参数和返回值,这种为默认的方法,也是新建MVC时默认的方法
if (Matches(methodInfo, false, typeof(IAppBuilder))) //方法无返回值(void),参数为(IAppBuilder)
{
object instance = methodInfo.IsStatic ? null : _activator(type); //如果为静态方法,则不需要实例,否则实例化一个Startup对象
return builder => methodInfo.Invoke(instance, new[] { builder }); //返回一个Lambda形式的delegate,实际上就是调用Startup的Configuration(IAppBuilder)方法
}
// object Configuration(IDictionary<string, object> appProperties) //另一种Configuration方法,参数为Environment,返回object
if (Matches(methodInfo, true, typeof(IDictionary<string, object>)))
{
object instance = methodInfo.IsStatic ? null : _activator(type); //由于传入参数为Dictionary,所以将这个Configuration方法压入middleware的List中
return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[] { builder.Properties })));
} //builder.Use传入参数是一个Func<object,object>的delegate,实际上就是一个middleware,不过因为在初始化阶段,所以不需要进入下一个stage
// object Configuration() //无参数,返回object
if (Matches(methodInfo, true))
{
object instance = methodInfo.IsStatic ? null : _activator(type);
return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[0])));
}
partialMatch = partialMatch ?? methodInfo; //记录找到但不符合三种定义的Configuration方法
}
if (partialMatch == null) //未找到的Configuration,记录错误
{
errors.Add(string.Format(CultureInfo.CurrentCulture,
LoaderResources.MethodNotFoundInClass, methodName, type.AssemblyQualifiedName));
}
else 找到Configuration,但不符合三种定义,记录错误
{
errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.UnexpectedMethodSignature,
methodName, type.AssemblyQualifiedName));
}
return null;
}
总结:OwinBuilder
主要完成对Startup
类的寻找,并调用其中的Configuration
方法,Configuration
有三种签名(传入参数与返回结果),将其封装成一个方法返回给上层,供上层调用。接下来就是最重要的工作,调用Startup
中的Configuration
具体做了什么,每个middleware
是如何注入到pipeline
中的,这就是AppBuilder
主要做的工作了。