BeetleX之webapi自定义响应内容

        输出内容多样性在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 

posted @ 2020-10-13 08:07  beetlex  阅读(588)  评论(1编辑  收藏  举报