什麼是OWIN,我就不介紹了,請自行搜索,這裏主要是介紹自行開發的OWIN框架的特色和用法。因爲.NET的web框架都比較龐大,致使性能老是不高,因此我纔想到要在新出的OWIN協議基礎上,開發一個高性能的框架,追求極限的性能,毫不發展爲一個複雜低效的框架,這是這套框架的初衷。目前功能還不是很全,只能提供WebApi的功能,Razor模板要下個版本再考慮。linux
若是你是一個注重程序運行效率,而不是編程方便的人,這個框架會很適合你。它在提供最高效率的同時,也極大方便了編程開發。git
下載地址:https://github.com/qldsrx/OwinFrameworkgithub
我用過ServiceStack,看過Nancy的代碼風格,這兩個框架廣受好評(但性能不行),特別是編程風格上面,因而我效仿了它們。目前框架中的序列化部分,還有使用ServiceStack_V3的dll,但因爲ServiceStack很是不厚道,自從收費後,免費版本的最後一個版本刪了幾個經常使用方法,收費版本直接命名空間大變樣,升級直接致使項目出錯,所以最終會所有本身實現,不用ServiceStack的類庫。web
該框架最低要求.net4.0環境,由於須要Task類的支持。數據庫
調試的時候能夠用自承載方式,方法和Nancy的同樣,但win7以上系統須要管理員權限啓動,代碼以下:編程
static void Main(string[] args) { var url = "http://+:8080"; using (WebApp.Start<OwinLight.Startup>(url)) { Console.WriteLine("Running on {0}", url); Console.WriteLine("Press enter to exit"); Console.ReadLine(); } }
項目最終部署推薦使用Linux+Jexus,由於Jexus會優先處理靜態資源,而個人這個框架是不處理靜態資源的,若是用IIS部署,須要設置經典模式並自行配置靜態路徑的處理映射。json
* jexus部署方法:
* 一、編譯獲得OwinLight.dll(也能夠自行更名)。
* 二、將編譯獲得的dll連同Owin.dll、Microsoft.Owin.dll等文件
* 一同放置到網站的bin文件夾中
* 三、在對應網站的jws網站配置文件中加入一句,聲明要使用的適配器:
* OwinMain=OwinLight.dll,OwinLight.Startup
* 四、重啓Jexus讓配置生效。windows
* TinyFox(能夠不須要安裝mono)部署方法:
* 一、編譯獲得OwinLight.dll(也能夠自行更名)。
* 二、將編譯獲得的dll連同Owin.dll、Microsoft.Owin.dll等文件
* 一同放置到網站的bin文件夾中(site\wwwroot\bin)
* 三、默認配置文件爲TinyFox.exe.config
* 四、運行fox.bat或fox.sh(linux),端口在啓動腳本里。api
首先來看下這樣的寫法:
public Demo1()
{
//定義靜態路徑處理函數,Any表示任意請求類型,Get表示只對GET請求處理,Post表示只對POST請求處理
Any["/"] = GetRoot; }
這和Nancy裏面裏面是相似的,提供了Any、Get、Post三種屬性來定義路徑處理函數,不一樣的是,個人這個函數必須是Func<IOwinContext, Task>形式的,而不接受任何dynamic的參數。這裏IOwinContext是web請求響應上下文,包含了請求和響應的各類信息。
下面就看下GetRoot函數的示例:
public Task GetRoot(IOwinContext context)
{
var x = new TaskCompletionSource<object>(); x.SetResult(null); //調用SetResult後,這個服務即轉爲完成狀態 context.Response.ContentType = "text/html; charset=utf-8"; HttpHelper.WritePart(context, "<h1 style='color:red'>您好,Jexus是全球首款直接支持MS OWIN標準的WEB服務器!</h1>"); return x.Task; }
看到這裏,你也許會以爲,這樣寫太麻煩了。是的,可是某些特殊場合會用的到,所以提供了這樣的寫法。下面介紹POCO參數方式的編程,和ServiceStack很像,可是更實用。
public enum ddd : byte
{
aa = 1, bb = 2, cc = 3, } /// <summary> /// 普通屬性接收來自URL或FORM表單的數據,接口屬性存儲文件,若非POST請求或文件個數爲0,則接口屬性爲空。 /// </summary> [Route("/api/dog1", 65536)] public class DOG1 : IHasHttpFiles { public int? id { get; set; } public string name { get; set; } public Guid token { get; set; } public ddd dsc { get; set; } /// <summary> /// 實現IHasHttpFiles,接收來自Form表單提交的文件,可能爲空 /// </summary> public List<HttpFile> HttpFiles { get; set; } }
這裏咱們經過Route這個特性,來告訴框架,有這麼一個地址/api/dog1的路由請求,請求內容要被自動封裝到這個DOG1裏面,這裏DOG1還繼承了IHasHttpFiles接口,這樣還能夠接受來自Form表單上傳的文件。若是不繼承IHasHttpFiles接口,文件內容則不會被接收。DOG1裏面的其它屬性,能夠來自Query也能夠來自Form,或者來自Xml和Json的序列化(此時不存在HttpFiles數據)。另外這裏的Route特性裏,定義了一個65536的數字,意思是,若是POST的數據超過了65536字節數,就忽略請求不處理,用來防攻擊的。默認最多接收4M字節。
下面看下這樣一個參數類型定義:
/// <summary>
/// 普通屬性接收來自URL的數據,POST的內容存入接口屬性,若非POST請求或POST內容爲空,則接口屬性也爲空
/// </summary>
[Route("/api/dog2", 65536)]
public class DOG2 : IHasRequestStream { public int? id { get; set; } public string name { get; set; } public Guid token { get; set; } public ddd dsc { get; set; } /// <summary> /// 實現IHasRequestStream,接收來自POST請求的數據 /// </summary> public Stream RequestStream { get; set; } }
也許咱們會想本身處理POST請求的數據,同時又要判斷URL附帶的參數,那麼就能夠定義這樣的類型,集成IHasRequestStream接口後,全部的POST數據再也不處理,而是直接將網絡原始流設置到這個RequestStream屬性上面,注意,它不是緩存的,所以沒法訪問Length屬性,也不能修改Position,更不能直接原樣返回,由於Http請求數據不接收完畢,是沒法寫入響應的。
另外這個Route不僅僅能夠對自定義類進行設置,還能夠設置到某個函數上面,來看下這個例子:
public class Demo2 : BaseService
{
public object Any(DOG1 request) { return request; } public object Post(DOG2 request) { if (request.RequestStream == null) return null; StreamReader sr = new StreamReader(request.RequestStream); return sr.ReadToEnd(); } [Route("/api/test1", 1024)] public object testString(String request) { return request; } [Route("/api/test2", "POST", int.MaxValue)] public Stream testStream(Stream request) { if (request == null) return null; MemoryStream ms = new MemoryStream(); request.CopyTo(ms); ms.Position = 0; return ms; } }
DOG1和DOG2就是前面提到的POCO類型,這裏Any方法匹配任意請求,而Post方法則只處理POST請求(還有Get方法類同)。而testString和testStream函數上面也出現了Route定義,這裏是針對非自定義類型參數作的路由處理,String和Stream是系統自帶的,卻每每很是有用。有時候,咱們會忽略任何參數,定義一個路徑請求,那麼直接用testString函數的形式便可,不處理GET請求的URL參數。若是是POST請求,則會封送爲String傳入函數,Stream的場合,則直接傳遞原始POST請求的網絡流,不緩存。響應也能夠爲一個不緩存的流,例如FileStream。
[Rewrite("/api/FileGet/{fileid}/{filename}")] public class FileGet { public int fileid { get; set; } public string filename { get; set; } public bool download { get; set; } }
Rewrite特性代替Route,用來定義僞靜態響應。僞靜態通常用在對url格式有很是高的要求的場合,例如linux下面的wget命令下載文件,只能經過url來識別要保存的文件名。不建議用僞靜態,但必定要用的話,這裏提供一個很是高效率的支持,處理速度至關快,由於沒有用正則。不少框架都是爛在僞靜態下面的,正則處理到一些莫名其妙的請求,致使CPU負荷很重,響應變慢。
我所支持的僞靜態,只作徹底匹配,不支持使用通配符*,且一旦遇到了{xxx}的結構,那麼後面的內容都做爲參數處理,而不作匹配要求。例如這樣的寫法:[Rewrite("/api/FileGet/{fileid}/{filename}/Get")],雖然最後還有一個Get的固定路徑,但我也認爲它是參數,只是一個不須要傳遞的參數,只有前面的/api/FileGet/才用來區分請求地址用。這樣作的好處是,匹配速度很是快。
僞靜態的實現,經過函數名Rewrite,若是函數名不對,將不加載。
public class Demo2 : BaseService, IDisposable { public string Rewrite(FileGet request) { Response.ContentType = "text/html; charset=utf-8"; return string.Format("<h1 style='color:red'>fileid:{0}<br/>filename:{1}</h1>", request.fileid, request.filename); } }
在前面靜態路由的示例中已經出現過,你定義的POCO類型,繼承IHasHttpFiles接口的狀況下,纔會自動幫你解析請求進來的文件,不然,你只能用Stream做爲參數,本身處理Post請求的原始數據流。IHasHttpFiles接口有個HttpFiles屬性,存放Form表單所提交的所有文件。
在前面靜態路由的示例中已經出現過,你定義的POCO類型,繼承IHasRequestStream接口的狀況下,其接口屬性RequestStream將爲Post的原始網絡流,這對於大數據的流式接收頗有利。POCO類型的其它屬性來自url附屬的參數,這個ServiceStack是不支持的,曾經爲了這個網上搜索了下答案,官方給的答案是「不必支持」,本身從請求的參數裏面抓——垃圾。
若是沒有url的附屬參數,你能夠直接用Stream做爲函數參數,Route特性定義在函數上面,前面已經給出過示例,也能夠到項目代碼裏看。
該框架能處理的請求類型有:application/x-www-form-urlencoded、multipart/form-data、application/xml、application/json,若是請求不指定Content-Type,則會讀取url的參數format,但format只處理xml、json類型,若是都沒有指定,則視爲json數據處理。而相應的內容目前只能是json格式,若是但願更改成xml或其餘格式,請設置相應爲String或Stream類型,並自行序列化。響應文檔格式的多樣化會在從此的版本考慮添加。
對於使用靜態或僞靜態路由特性的返回值,支持任意類型,能夠是object。若是是繼承Stream的類型,還會在處理結束時,自動調用Close和Dispose方法,因此不用擔憂直接返回FileStream後的文件關閉問題。對於其餘非字符串類型,會進行Json序列化後輸出,目前不提供多種輸出,但你能夠自行序列化後,以字符串返回,這樣就不會再次爲你序列化了。
響應的文檔類型,若是用戶設置,則採用用戶設置的文檔類型,不然自動按照函數返回值類型自動添加。用戶還能夠自行設置IService接口的IsHeadersSended屬性爲true,來阻止框架最後自行設置文檔長度。默認的文檔長度是計算返回值對象的產生的字節長度,可是若是在返回對象以前,用戶已經有往響應裏寫入內容了,那文檔長度就不可預知了,由於這裏的響應流是不緩存的,這也是爲了提升效率,否則輸出一個大文件也緩存,服務器吃不消。備註:若是是IIS下面跑,響應只能是緩存的,微軟提供的IIS下使用的Owin適配器帶輸出緩存,還沒法關閉。
框架中定義了這樣一個基類,但你也能夠本身從新定義,繼承IService接口便可:
public abstract class BaseService : IService { public IOwinRequest Request { get; set; } IOwinResponse _Response; public IOwinResponse Response { get { return _Response; } set { _Response = value; _Response.OnSendingHeaders(t => { IsHeadersSended = true; }, null); } } public bool IsHeadersSended { get; set; } }
這個基類中的請求、響應上下文屬性,將會在請求時自動賦值,咱們能夠直接訪問最原始的請求和響應對象,用來控制Headers。而那個IsHeadersSended屬性,也會在響應首次輸出後,變爲true,框架在自動處理函數返回值時,會用來判斷是否還須要設置文檔長度了。固然,你也能夠強制設置爲true,另外IIS下面帶緩存,所以你只能本身設置true,不然永遠不會變爲true。
當沒有路由規則匹配時,默認的處理函數,能夠爲空,系統在GET請求是自動返回404,而POST請求則直接取消任務,取消任務的場合,看不一樣的Owin服務器是如何處理的,TinyFox已經能夠配合中斷Socket了,而微軟的幾個宿主則沒有這麼作。取消任務是很是重要的功能,對於攻擊防禦頗有意義,若是你不打算處理POST請求了,那麼網絡I/O就應該馬上釋放,而不是等到數據接收完畢,再給一個響應,這樣I/O的佔用對服務器來講是個不小的損失。經過設置Startup.NotFountFun委託,你能夠本身定義匹配不到時的處理方式,後面在框架的擴展裏,會給出一個靜態內容的支持示例,用的就是這個默認處理函數支持。
HttpHelper類裏面:
GetMapPath,得到當前絕對路徑,同時兼容windows和linux,.NET自帶的Path.Combine方法,到了linux下面的表現就和windows不一樣了,由於linux 的"/"表明了系統的根,和網站的根衝突了,可是我這裏是要表明網站的根,因此Owin下面沒有一個可用的函數,只能本身寫。這個函數還支持「../」的寫法,查找上級,這樣咱們在使用相對路徑時會很方便。
Escape,Unescape,對url參數進行編碼或解碼,因爲Owin不須要System.Web,因此咱們不必再引用過多的dll進來,因而我封裝了這兩個方法,支持參數爲null的狀況,這兩個方法自己也設置爲了擴展方法,支持null時的調用,調用更方便。補充:對象在調用方法時,若是爲空,通常會報「未將對象引用設置到對象實例」的錯誤,可是擴展方法能夠避免這樣的報錯。
AddHttpRangeResponseHeaders,設置分段響應頭,當咱們要提供斷點續傳時,這個函數能夠方便的設置響應頭。
ParseFormData,解析Form表單數據,若是你要以Stream或String接收Post響應,能夠調用這個函數來解析Form表單數據。
除了簡單的POCO類型外,自定義類的屬性能夠是某個Model類型,這種就是複合類型,此時用來接收POST請求提交的Json數據,同時,url參數也會在Json數據接收後,被添加到自定義類型的簡單屬性上,確保Query的參數也被自動填充。
你也能夠用Dictionary<string,object>類型做爲請求的參數,此時將Route特性定義處處理函數上便可,POST傳遞的Json數據,將自動反序列化到這個字典類型上。
框架是開源的,能夠隨便更改。若是但願有後期的支持,請提交需求讓我來修改,或者記住本身的改動部分,以便下次合併修改。
HttpHelper類是框架的核心處理類,其靜態函數裏面定義了大量的類型轉換函數,這裏能夠本身添加不存在的類型,或者修改已經存在的類型處理。類型分爲兩類,數組和非數組,其傳遞的參數是不一樣的,源碼中有示例,這裏不展開了。源碼中有段「#if DEBUG」代碼,用來設置是否對參數處理過程發生的異常作記錄,若是項目是在Debug下面編譯的,就會記錄,此時若是POST請求的數據不是xml或json格式,卻要強制進行轉換,就會記錄到異常。應用層的異常自行決定是否要捕獲,前面的是框架處理過程的異常。另外還有一個Debug類,裏面就一個方法Write,框架中用來輸入各類異常都會調用它。你能夠修改輸出的路徑,也能夠對日誌輸出進行一些緩存處理,再或者直接不作任何處理,屏蔽掉處理代碼。
下面給一個經過默認處理函數,添加靜態頁響應的示例,僅供測試用
public class Class1:BaseRoute { public Class1() { Startup.NotFountFun = GetStaticFile; } /// <summary> /// 靜態頁提供示例,未作任何緩存和304響應處理,僅測試用。 /// </summary> public Task GetStaticFile(IOwinContext context) { if (context.Request.Method == "GET" && context.Request.Path.HasValue) { string path = HttpHelper.GetMapPath(context.Request.Path.Value); FileInfo fi = new FileInfo(path); var response = context.Response; if (fi.Exists) { response.ContentType = MimeTypes.GetMimeType(fi.Extension); response.ContentLength = fi.Length; response.StatusCode = 200; using (FileStream fs = fi.OpenRead()) { fs.CopyTo(response.Body); } } else { response.StatusCode = 404; response.Write("<h1 style='color:red'>很抱歉,出現了404錯誤。</h1>"); } return HttpHelper.completeTask; } else { return HttpHelper.cancelTask; } } }
IE8+、谷歌、火狐等瀏覽器均支持,不考慮IE6的話,可使用這個功能
<appSettings> <!--僞靜態路徑最大深度--> <add key="rewritedepth" value="10"/> <!--自定義響應頭,key-value用冒號隔開,多個頭用封號隔開--> <add key="responseheaders" value="Access-Control-Allow-Origin:*;Access-Control-Allow-Methods:GET,POST;Access-Control-Allow-Headers:Content-Type"/> </appSettings>
在配置文件裏添加以下代碼,註釋已經很清楚了。而Owin自己沒要求配置文件,因此,你的宿主可能不提供配置文件,那麼你只是在你的網站根目錄下面放一個web.config,我框架會優先查找這個文件的配置,查找不到的狀況纔會去用應用程序默認的配置文件。HttpHelper.AppSettings用來訪問web.config下面的appSettings節點,HttpHelper.ConnectionStrings用來訪問web.config下面的connectionStrings節點。
BaseRoute類通常不多會用,也沒什麼好說的。BaseService類用的最多,用它能夠極大的提升開發效率,同時你也能夠繼承該類實現更多的便利,下面我再給一段示例,演示如何本身繼承BaseService或IService來減小編碼。能夠將這個MyService做爲基類,再派生出相關處理下面的類。
/// <summary> /// 自定義服務類示例,添加令牌、數據庫支持。 /// </summary> public class MyService : BaseService, IDisposable { static readonly string connectionString = HttpHelper.ConnectionStrings["demo"].ConnectionString; private IDbConnection db; public virtual IDbConnection Db { get { if (db == null) { db = new MySql.Data.MySqlClient.MySqlConnection(connectionString); db.Open(); } return db; } } private Guid token; public Guid Token { get { if (token != Guid.Empty) return token; string loginheader = Request.Headers.Get("token"); if (loginheader != null) return token = new Guid(loginheader); else if ((loginheader = Request.Cookies["token"]) != null) { return token = new Guid(loginheader); } else throw new Exception("用戶登陸信息未傳遞"); } } public void Dispose() { if (db != null) db.Dispose(); } }
ORM操做推薦用Dapper,性能最高。這裏Db屬性有了,Dapper的使用就很是簡單了,由於Dapper的操做都是基於鏈接類的擴展方法。
RouteAttribute類也是能夠繼承的,當你但願某些路由下用特定的響應頭,你能夠派生出本身的RouteAttribute繼承類,將Headers屬性定死。框架在檢測到你設置過Headers屬性後,就再也不讀取配置文件裏面的響應頭設置了。
這是初版框架,因爲工做繁忙,只能先實現這麼多功能,其它功能下個版本添加。計劃要增長Session處理和Razor引擎。