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