輸出內容多樣性在webapi服務中比較普遍的,有的情況使用json,xml,圖片和二進位制流下載等等;為了適應用不同情況的需要,元件支援自定義內容輸出。接下來的主要描述元件在webapi如何定義各種內容輸出來滿足實際應用的需要。
規則
元件通過介面來規範自定義內容輸出:
public interface IResult { //指定輸出的ContentType IHeaderItem ContentType { get; } //http body長度,此值可以是零,當為零的時候元件會自動計算 int Length { get; set; } //輸出前用於設定相關http頭資訊 void Setting(HttpResponse response); //是否存在http body內容 bool HasBody { get; } //寫入Http body void Write(PipeStream stream, HttpResponse response); }
以上是定義內容輸出的介面規則,所有自定義輸出都必須實現這規則。以下是針對text/plain; charset=UTF-8的基礎類輸出抽象類
public abstract class ResultBase : IResult { public virtual IHeaderItem ContentType => ContentTypes.TEXT_UTF8; public virtual int Length { get; set; } public virtual bool HasBody => true; public virtual void Setting(HttpResponse response) {} public virtual void Write(PipeStream stream, HttpResponse response) {} }
在以上抽象類的基礎上,元件擴充套件了很多基礎的輸出類。
服務內部錯誤
public class InnerErrorResult : ResultBase { public InnerErrorResult(string code, string messge) { Code = code; Message = messge; } public InnerErrorResult(string message, Exception e, bool outputStackTrace) : this("500", message, e, outputStackTrace) { } public InnerErrorResult(string code, string message, Exception e, bool outputStackTrace) { Code = code; Message = message; Error = e.Message; if (outputStackTrace) SourceCode = e.StackTrace; else SourceCode = ""; } public string Message { get; set; } public string Error { get; set; } public string Code { get; set; } public string SourceCode { get; set; } public override bool HasBody => true; public override void Setting(HttpResponse response) { response.Code = Code; response.CodeMsg = Message; response.Request.ClearStream(); } public override void Write(PipeStream stream, HttpResponse response) { if (!string.IsNullOrEmpty(Error)) { stream.WriteLine(Error); } if (!string.IsNullOrEmpty(SourceCode)) { stream.WriteLine(SourceCode); } } }
以上是元件內部錯誤定義的輸出類,所有處理的內部異常響應都是使用這類進行輸出。
if (!mIPLimit.ValidateRPS(request)) { token.KeepAlive = false; InnerErrorResult innerErrorResult = new InnerErrorResult("400", $"{request.RemoteIPAddress} request limit!"); response.Result(innerErrorResult); return; }
以上是元件內部針對IP做的一個請求限制錯誤輸出。
JSON輸出
public class JsonResult : ResultBase { public JsonResult(object data, bool autoGzip = false) { Data = data; mAutoGzip = autoGzip; } public object Data { get; set; } private bool mAutoGzip = false; private ArraySegment<byte> mJsonData; [ThreadStatic] private static System.Text.StringBuilder mJsonText; private void OnSerialize(HttpResponse response) { if (mJsonText == null) mJsonText = new System.Text.StringBuilder(); mJsonText.Clear(); JsonSerializer serializer = response.JsonSerializer; System.IO.StringWriter writer = new System.IO.StringWriter(mJsonText); JsonTextWriter jsonTextWriter = new JsonTextWriter(writer); serializer.Serialize(jsonTextWriter, Data); var charbuffer = System.Buffers.ArrayPool<Char>.Shared.Rent(mJsonText.Length); mJsonText.CopyTo(0, charbuffer, 0, mJsonText.Length); try { var bytes = System.Buffers.ArrayPool<byte>.Shared.Rent(mJsonText.Length * 6); var len = System.Text.Encoding.UTF8.GetBytes(charbuffer, 0, mJsonText.Length, bytes, 0); mJsonData = new ArraySegment<byte>(bytes, 0, len); } finally { System.Buffers.ArrayPool<char>.Shared.Return(charbuffer); } } public override void Setting(HttpResponse response) { base.Setting(response); if (this.mAutoGzip) OnSerialize(response); if (mAutoGzip && mJsonData.Count > 1024 * 2) { response.Header.Add("Content-Encoding", "gzip"); } } public override IHeaderItem ContentType => ContentTypes.JSON; public override bool HasBody => true; public override void Write(PipeStream stream, HttpResponse response) { if (mAutoGzip) { try { if (mJsonData.Count > 1024 * 2) { using (stream.LockFree()) { using (var gzipStream = new GZipStream(stream, CompressionMode.Compress, true)) { gzipStream.Write(mJsonData.Array, mJsonData.Offset, mJsonData.Count); gzipStream.Flush(); } } } else { stream.Write(mJsonData.Array, mJsonData.Offset, mJsonData.Count); } } finally { System.Buffers.ArrayPool<byte>.Shared.Return(mJsonData.Array); } } else { using (stream.LockFree()) { response.JsonSerializer.Serialize(response.JsonWriter, Data); response.JsonWriter.Flush(); } } } }
以上是元件內部實現的Json輸出,不過這個JsonResult實現有些複雜,主要是可以根據內容大小來自動進行Gzip輸出。
Websocket升級響應
public class UpgradeWebsocketResult : ResultBase { public UpgradeWebsocketResult(string websocketKey) { WebsocketKey = websocketKey; } public string WebsocketKey { get; set; } public override bool HasBody => false; public override void Setting(HttpResponse response) { response.Code = "101"; response.CodeMsg = "Switching Protocols"; response.Header.Add(HeaderTypeFactory.CONNECTION, "Upgrade"); response.Header.Add(HeaderTypeFactory.UPGRADE, "websocket"); response.Header.Add(HeaderTypeFactory.SEC_WEBSOCKET_VERSION, "13"); SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] bytes_sha1_in = Encoding.UTF8.GetBytes(WebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); byte[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in); string str_sha1_out = Convert.ToBase64String(bytes_sha1_out); response.Header.Add(HeaderTypeFactory.SEC_WEBSOCKT_ACCEPT, str_sha1_out);
} }
以上是針對websocket握手升級響應的內容
檔案下載
在web服務中很多時候需要下載指定的檔案或二進位制內容,以下是轉門針對這些需求場制定的響應物件.
public class DownLoadResult : BeetleX.FastHttpApi.IResult { public DownLoadResult(string text, string fileName, IHeaderItem contentType) { mData = Encoding.UTF8.GetBytes(text); mFileName = System.Web.HttpUtility.UrlEncode(fileName); if (contentType != null) mContentType = contentType; } public DownLoadResult(byte[] data, string fileName, IHeaderItem contentType) { mData = data; mFileName = System.Web.HttpUtility.UrlEncode(fileName); if (contentType != null) mContentType = contentType; } private string mFileName; private byte[] mData; private IHeaderItem mContentType = ContentTypes.OCTET_STREAM; public IHeaderItem ContentType => mContentType; public int Length { get; set; } public bool HasBody => true; public void Setting(HttpResponse response) { response.Header.Add("Content-Disposition", $"attachment;filename={mFileName}"); } public virtual void Write(PipeStream stream, HttpResponse response) { stream.Write(mData); } }
應用示例
[Controller] class Webapi { public object Default() { return new { Name = "BeetleX", Email = "Admin@beetlex.io" }; } public object Json() { return new JsonResult(new { Name = "BeetleX", Email = "Admin@beetlex.io" }); } public object Download() { var txt = JsonConvert.SerializeObject(new { Name = "BeetleX", Email = "Admin@beetlex.io" }); return new DownLoadResult(txt, "json.txt", ContentTypes.JSON); } public object Image() { var str = "..."; var data = Convert.FromBase64String(str); return new ImageResult(new ArraySegment<byte>(data, 0, data.Length), "image/jpeg"); } }
下載示例
連結:https://pan.baidu.com/s/1GX2D-Qwo9ep1gHU-sPZkUA
提取碼:py6m