BeetleX之webapi自定義響應內容

beetlex發表於2020-10-13

        輸出內容多樣性在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 

相關文章