The Open Web Interface for .NET (OWIN) 原始碼閱讀
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
主要做的工作了。
相關文章
- AFNetworking 原始碼閱讀原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager原始碼
- 原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager原始碼IndicatorORM
- 原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization原始碼
- 原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization原始碼
- 原始碼閱讀:AFNetworking(四)——AFSecurityPolicy原始碼
- 原始碼閱讀:AFNetworking(六)——AFURLSessionManager原始碼Session
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 原始碼閱讀:AFNetworking(九)——AFImageDownloader原始碼
- 原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache原始碼
- 原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager原始碼HTTPSession
- ReactorKit原始碼閱讀React原始碼
- AQS原始碼閱讀AQS原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- NGINX原始碼閱讀Nginx原始碼
- Mux 原始碼閱讀UX原始碼
- HashMap原始碼閱讀HashMap原始碼
- fuzz原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- express 原始碼閱讀Express原始碼
- muduo原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- 原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking原始碼UIWebView
- 原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking原始碼UIView
- 原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking原始碼UIView
- 原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking原始碼UIIndicatorView
- .Net Core Logging模組原始碼閱讀原始碼
- 原始碼閱讀:AFNetworking(一)——從使用入手原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- 原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking原始碼UI
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- gin原始碼閱讀之一 – net/http的大概流程原始碼HTTP