wcf虽然功能多、扩展性强但是也面临配置忒多,而且restful的功能相当怪异,并且目前没法移植。asp.net core虽然支持webapi,但是功能也相对繁多、配置复杂。就没有一个能让码农们安安心心的写webapi,无需考虑性能、配置、甚至根据问题场景自行设计、改造等问题的方案么?
当然不是,特别是在dnc2.0已经相当强大的此时,完全可以自行设计一套简洁、高效的webapi框架!说到自行写一套框架,很多码农们就可能会想到开发工作量难以想像,事实真的如此么?java因为开源众多,很多对mvc稍有了解的都可以拿这个拿那个拼出一个自已的mvc框架;而面对日益强大的dnc,本人觉得C#根本无需东拼西凑这么麻烦,完全可以根据自已的需求简单快速的写出一个来,不服就开干!
设计的编码思路就是仿asp.net mvc,原因就是asp.net mvc成功发展了这么多年,有着大量的C#码农习惯了这套优良的编码方式;至于spring mvc、spring boot那些,站在使用者的角度来说,光配置和注解都能敲死人,如要要说简洁快速,asp.net mvc比他强多了,更别提ruby on rails。不扯远了,下面就按C#经典来。那么需要考虑的问题有tcp、http、request、response、server、controller、actionresult、routetable等,下面就一一来解决这个问题。
一、Tcp:这个是实现传输通信的底层,当然采用IOCP来提高吞吐量和性能,本人之前在做Redis Client等的时候就使用这个IOCP Socket的框架,此时正好也可以用上
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Http.Net
*文件名: ServerSocket
*版本号: V1.0.0.0
*唯一标识:ab912b9a-c7ed-44d9-8e48-eef0b6ff86a2
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/8 17:11:15
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/8 17:11:15
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Sockets.Core;
using SAEA.Sockets.Interface;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text; namespace SAEA.WebAPI.Http.Net
{
class ServerSocket : BaseServerSocket
{
public event Action<IUserToken, string> OnRequested; public ServerSocket(int bufferSize = * , int count = ) : base(new HContext(), bufferSize, true, count)
{ } protected override void OnReceiveBytes(IUserToken userToken, byte[] data)
{
HCoder coder = (HCoder)userToken.Coder; coder.GetRequest(data, (result) =>
{
OnRequested?.Invoke(userToken, result);
});
} public void Reply(IUserToken userToken, byte[] data)
{
base.Send(userToken, data);
base.Disconnected(userToken);
}
}
}
二、Http:这个是个应用协议,本人了解下来至少有3个版本,完全熟悉的话估计没个半年都搞不定;但是只需要关键,比如说http1.1的工作模式、传输格式、常见异常code、常见mime类型、js跨域支持等,这些基本能覆盖绝大部分日常场景,至于更多的那些细枝末节的理它作甚,本人的做法就是用Chrome的开发人员工具来查看相关network详情,这样的话就可以清楚http这个协议的具体编码解码了。
public void GetRequest(byte[] data, Action<string> onUnpackage)
{
lock (_locker)
{
var str = Encoding.UTF8.GetString(data); var index = str.IndexOf(ENDSTR); if (index > -)
{
var s = str.Substring(, index); _result.Append(s); onUnpackage.Invoke(_result.ToString()); _result.Clear(); if (str.Length > index + )
{
_result.Append(str.Substring(index + ));
}
}
else
{
_result.Append(str);
}
}
}
经过分析后http的内容格式其实就是字符回车分隔,再加上一些约定生成的分隔符bound完成的。
public HttpRequest(Stream stream)
{
this._dataStream = stream;
var data = GetRequestData(_dataStream);
var rows = Regex.Split(data, Environment.NewLine); //Request URL & Method & Version
var first = Regex.Split(rows[], @"(\s+)")
.Where(e => e.Trim() != string.Empty)
.ToArray();
if (first.Length > ) this.Method = first[];
if (first.Length > )
{
this.Query = first[]; if (this.Query.Contains("?"))
{
var qarr = this.Query.Split("?");
this.URL = qarr[];
this.Params = GetRequestParameters(qarr[]);
}
else
{
this.URL = this.Query;
} var uarr = this.URL.Split("/"); if (long.TryParse(uarr[uarr.Length - ], out long id))
{
this.URL = this.URL.Substring(, this.URL.LastIndexOf("/"));
this.Params.Set("id", id.ToString());
}
}
if (first.Length > ) this.Protocols = first[]; //Request Headers
this.Headers = GetRequestHeaders(rows); //Request "GET"
if (this.Method == "GET")
{
this.Body = GetRequestBody(rows);
} //Request "POST"
if (this.Method == "POST")
{
this.Body = GetRequestBody(rows);
var contentType = GetHeader(RequestHeaderType.ContentType);
var isUrlencoded = contentType == @"application/x-www-form-urlencoded";
if (isUrlencoded) this.Params = GetRequestParameters(this.Body);
}
}
看到上面,有人肯定会说你这个传文件咋办?一个呢本人这个是针对webapi;另外一个,如真有这个场景,可以用Chrome的开发人员工具来查看相关network详情,也可以使用httpanalyzerstd、httpwatch等众多工具分析下,其实也就是使用了一些约定的分隔符bound完成,每个浏览器还不一样,有兴趣的完全可以自行扩展一个。
三、Reponse这个是webapi服务端相当重要的一个组件,本人也是尽可能方便并且按尽量按asp.net mvc的命名来实现,另外这里加入支持js跨域所需大部分场景heads,如果还有特殊的heads,完全可以自已添加。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Http
*文件名: HttpResponse
*版本号: V1.0.0.0
*唯一标识:2e43075f-a43d-4b60-bee1-1f9107e2d133
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/8 16:46:40
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/8 16:46:40
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Commom;
using SAEA.Sockets.Interface;
using SAEA.WebAPI.Http.Base;
using SAEA.WebAPI.Mvc;
using System.Collections.Generic;
using System.Net;
using System.Text; namespace SAEA.WebAPI.Http
{
public class HttpResponse : BaseHeader
{
public HttpStatusCode Status { get; set; } = HttpStatusCode.OK; public byte[] Content { get; private set; } internal HttpServer HttpServer { get; set; } internal IUserToken UserToken { get; set; }
/// <summary>
/// 创建一个HttpRequest实例
/// </summary>
/// <param name="httpServer"></param>
/// <param name="userToken"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static HttpResponse CreateInstance(HttpServer httpServer, IUserToken userToken)
{
HttpResponse httpResponse = new HttpResponse("");
httpResponse.HttpServer = httpServer;
httpResponse.UserToken = userToken;
return httpResponse;
} /// <summary>
/// 设置回复内容
/// </summary>
/// <param name="httpResponse"></param>
/// <param name="result"></param>
internal static void SetResult(HttpResponse httpResponse, ActionResult result)
{
httpResponse.Content_Encoding = result.ContentEncoding.EncodingName;
httpResponse.Content_Type = result.ContentType;
httpResponse.Status = result.Status; if (result is EmptyResult)
{
return;
} if (result is FileResult)
{
var f = result as FileResult; httpResponse.SetContent(f.Content); return;
} httpResponse.SetContent(result.Content);
} public HttpResponse(string content) : this(content, "UTF-8", "application/json; charset=utf-8", HttpStatusCode.OK)
{ } public HttpResponse(string content, string encoding, string contentType, HttpStatusCode status)
{
this.Content_Encoding = encoding;
this.Content_Type = contentType;
this.Status = status;
this.SetContent(content);
} internal HttpResponse SetContent(byte[] content, Encoding encoding = null)
{
this.Content = content;
this.Encoding = encoding != null ? encoding : Encoding.UTF8;
this.Content_Length = content.Length.ToString();
return this;
} internal HttpResponse SetContent(string content, Encoding encoding = null)
{
//初始化内容
encoding = encoding != null ? encoding : Encoding.UTF8;
return SetContent(encoding.GetBytes(content), encoding);
} public string GetHeader(ResponseHeaderType header)
{
return base.GetHeader(header);
} public void SetHeader(ResponseHeaderType header, string value)
{
base.SetHeader(header, value);
} /// <summary>
/// 构建响应头部
/// </summary>
/// <returns></returns>
protected string BuildHeader()
{
StringBuilder builder = new StringBuilder();
builder.Append(Protocols + SPACE + Status.ToNVString() + ENTER);
builder.AppendLine("Server: Wenli's Server");
builder.AppendLine("Keep-Alive: timeout=20");
builder.AppendLine("Date: " + DateTimeHelper.Now.ToFString("r")); if (!string.IsNullOrEmpty(this.Content_Type))
builder.AppendLine("Content-Type:" + this.Content_Type); //支持跨域
builder.AppendLine("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
builder.AppendLine("Access-Control-Allow-Origin: *");
builder.AppendLine("Access-Control-Allow-Headers: Content-Type,X-Requested-With,Accept,yswenli");//可自行增加额外的header
builder.AppendLine("Access-Control-Request-Methods: GET, POST, PUT, DELETE, OPTIONS"); if (this.Headers != null && this.Headers.Count > )
{
foreach (var key in Headers.Names)
{
builder.AppendLine($"{key}: {Headers[key]}");
}
} return builder.ToString();
} /// <summary>
/// 生成数据
/// </summary>
private byte[] ToBytes()
{
List<byte> list = new List<byte>();
//发送响应头
var header = BuildHeader();
byte[] headerBytes = this.Encoding.GetBytes(header);
list.AddRange(headerBytes); //发送空行
byte[] lineBytes = this.Encoding.GetBytes(System.Environment.NewLine);
list.AddRange(lineBytes); //发送内容
list.AddRange(Content); return list.ToArray();
} public void Write(string str)
{
SetContent(str);
} public void BinaryWrite(byte[] data)
{
SetContent(data);
} public void Clear()
{
this.Write("");
} public void End()
{
HttpServer.Replay(UserToken, this.ToBytes());
HttpServer.Close(UserToken);
} }
}
四、HttpServer:这个就是承载webapi的容器;有人说不是有IIS和Apache么?本人想说的是:有self-host方便么?有无需安装,无需配置、随便高性能开跑好么?asp.net core里面都有了这个,没这个就没有逼格....(此处省略一万字),前面还研究tcp、http这个当然不能少了
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Http
*文件名: HttpServer
*版本号: V1.0.0.0
*唯一标识:914acb72-d4c4-4fa1-8e80-ce2f83bd06f0
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/10 13:51:50
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/10 13:51:50
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Sockets.Interface;
using SAEA.WebAPI.Common;
using SAEA.WebAPI.Http.Net;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; namespace SAEA.WebAPI.Http
{
class HttpServer
{
ServerSocket _serverSocket; public HttpServer()
{
_serverSocket = new ServerSocket();
_serverSocket.OnRequested += _serverSocket_OnRequested;
} public void Start(int port = )
{
_serverSocket.Start(port);
} private void _serverSocket_OnRequested(IUserToken userToken, string htmlStr)
{
var httpContext = HttpContext.CreateInstance(this, userToken, htmlStr); var response = httpContext.Response; response.End();
} internal void Replay(IUserToken userToken, byte[] data)
{
_serverSocket.Reply(userToken, data);
} internal void Close(IUserToken userToken)
{
_serverSocket.Disconnected(userToken);
} }
}
五、Controller:为了实现类似于mvc的效果Controller这个大名鼎鼎的当然不能少了,其在C#中使用非常少量的代码即可实现
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Mvc
*文件名: Controller
*版本号: V1.0.0.0
*唯一标识:a303db7d-f83c-4c49-9804-032ec2236232
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/10 13:58:08
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/10 13:58:08
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/ using SAEA.WebAPI.Http; namespace SAEA.WebAPI.Mvc
{
/// <summary>
/// WebApi控制器
/// </summary>
public abstract class Controller
{
public HttpContext HttpContext { get; set; } /// <summary>
/// 返回Json
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected JsonResult Json(object data)
{
return new JsonResult(data);
}
/// <summary>
/// 自定义内容
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected ContentResult Content(string data)
{
return new ContentResult(data);
} /// <summary>
/// 小文件
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
protected FileResult File(string filePath)
{
return new FileResult(filePath);
} /// <summary>
/// 空结果
/// </summary>
/// <returns></returns>
protected EmptyResult Empty()
{
return new EmptyResult();
}
}
}
六、ActionResult:是mvc里面针对reponse结果进行了一个http格式的封装,本人主要实现了ContentResult、JsonResult、FileResult三个,至于其他的在WebAPI里基本上用不到。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Mvc
*文件名: JsonResult
*版本号: V1.0.0.0
*唯一标识:340c3ef0-2e98-4f25-998f-2bb369fa2794
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/10 16:48:06
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/10 16:48:06
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.WebAPI.Common;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text; namespace SAEA.WebAPI.Mvc
{
public class JsonResult : ActionResult
{
public JsonResult(object model) : this(SerializeHelper.Serialize(model))
{ }
public JsonResult(string json) : this(json, Encoding.UTF8)
{ } public JsonResult(string json, HttpStatusCode status)
{
this.Content = json;
this.ContentEncoding = Encoding.UTF8;
this.ContentType = "application/json; charset=utf-8";
this.Status = status;
} public JsonResult(string json, Encoding encoding, string contentType = "application/json; charset=utf-8")
{
this.Content = json;
this.ContentEncoding = encoding;
this.ContentType = contentType;
}
}
}
七、RouteTable:MVC里面有一个相当重要的概念叫约定优先,即为Controller、Action的名称是按某种规则来写编码的,其中将URL与自定义Controller对应起来的缓存映射就是RouteTable,并且作为缓存,也能极大的提升访问性能。当然这里并没有严格按照asp.net mvc里面的routetable来设计,而是根据只是实现webapi,并使用缓存反射结构能来实现的,并且只有约定,没有配置。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPI.Mvc
*文件名: RouteTable
*版本号: V1.0.0.0
*唯一标识:1ed5d381-d7ce-4ea3-b8b5-c32f581ad49f
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/12 10:55:31
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/12 10:55:31
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text; namespace SAEA.WebAPI.Mvc
{
/// <summary>
/// SAEA.WebAPI路由表
/// </summary>
public static class RouteTable
{
static object _locker = new object(); static List<Routing> _list = new List<Routing>(); /// <summary>
/// 获取routing中的缓存
/// 若不存在则创建
/// </summary>
/// <param name="controllerType"></param>
/// <param name="controllerName"></param>
/// <param name="actionName"></param>
/// <param name="isPost"></param>
/// <returns></returns>
public static Routing TryGet(Type controllerType, string controllerName, string actionName, bool isPost)
{
lock (_locker)
{
var list = _list.Where(b => b.ControllerName.ToLower() == controllerName.ToLower() && b.ActionName.ToLower() == actionName.ToLower() && b.IsPost == isPost).ToList(); if (list == null || list.Count == )
{
var routing = new Routing()
{
ControllerName = controllerName,
ActionName = actionName,
IsPost = isPost
}; var actions = controllerType.GetMethods().Where(b => b.Name.ToLower() == actionName.ToLower()).ToList(); if (actions == null || actions.Count == )
{
throw new Exception($"{controllerName}/{actionName}找不到此action!");
}
else if (actions.Count > )
{
throw new Exception($"{controllerName}/{actionName}有多个重复的的action!");
}
else
{
routing.Instance = System.Activator.CreateInstance(controllerType); //类上面的过滤
var attrs = controllerType.GetCustomAttributes(true); if (attrs != null)
{
var attr = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").FirstOrDefault(); routing.Atrr = attr; }
else
{
routing.Atrr = null;
} routing.Action = actions[]; //action上面的过滤
if (routing.Atrr == null)
{
attrs = actions[].GetCustomAttributes(true); if (attrs != null)
{
var attr = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").FirstOrDefault(); routing.Atrr = attr; }
else
{
routing.Atrr = null;
}
}
}
_list.Add(routing);
return routing;
}
else if (list.Count > )
{
throw new Exception("");
}
return list.FirstOrDefault();
}
}
} }
在MVC的思想里面ActionFilterAtrribute的这个AOP设计也一直伴随左右,比如记日志、黑名单、权限、验证、限流等等功能,所以路由的时候也会缓存这个。至此一些关键性的地方都已经弄的差不多了,为了更好的了解上面说的这些,下面是vs2017中项目的结构截图:
纯粹干净单码,无任何晦涩内容,如果对mvc有一定了解的,这个差不多可以NoNotes,接下来就是按asp.net mvc命名方式,写个测试webapi看看情况,首先还是测试项目结构图:
HomeController里面按asp.net mvc的习惯来编写代码:
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPITest.Controllers
*文件名: HomeController
*版本号: V1.0.0.0
*唯一标识:e00bb57f-e3ee-4efe-a7cf-f23db767c1d0
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/10 16:43:26
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/10 16:43:26
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.WebAPI.Mvc;
using SAEA.WebAPITest.Attrubutes;
using SAEA.WebAPITest.Model; namespace SAEA.WebAPITest.Controllers
{
/// <summary>
/// 测试实例代码
/// </summary>
//[LogAtrribute]
public class HomeController : Controller
{
/// <summary>
/// 日志拦截
/// 内容输出
/// </summary>
/// <returns></returns>
//[Log2Atrribute]
public ActionResult Index()
{
return Content("Hello,I'm SAEA.WebAPI!");
}
/// <summary>
/// 支持基本类型参数
/// json序列化
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public ActionResult Get(int id)
{
return Json(new { Name = "yswenli", Sex = "男" });
}
/// <summary>
/// 底层对象调用
/// </summary>
/// <returns></returns>
public ActionResult Show()
{
var response = HttpContext.Response; response.Content_Type = "text/html; charset=utf-8"; response.Write("<h3>测试一下那个response对象使用情况!</h3>参考消息网4月12日报道外媒称,法国一架“幻影-2000”战机意外地对本国一家工厂投下了..."); response.End(); return Empty();
} [HttpGet]
public ActionResult Update(int id)
{
return Content($"HttpGet Update id:{id}");
}
/// <summary>
/// 基本类型参数、实体混合填充
/// </summary>
/// <param name="isFemale"></param>
/// <param name="userInfo"></param>
/// <returns></returns>
[HttpPost]
public ActionResult Update(bool isFemale, UserInfo userInfo = null)
{
return Json(userInfo);
}
[HttpPost]
public ActionResult Test()
{
return Content("httppost test");
}
/// <summary>
/// 文件输出
/// </summary>
/// <returns></returns>
public ActionResult Download()
{
return File(HttpContext.Server.MapPath("/Content/Image/c984b2fb80aeca7b15eda8c004f2e0d4.jpg"));
}
}
}
增加一个LogAtrribute打印一些内容:
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.WebAPITest.Common
*文件名: LogAtrribute
*版本号: V1.0.0.0
*唯一标识:2a261731-b8f6-47de-b2e4-aecf2e0e0c0f
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/4/11 13:46:42
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/4/11 13:46:42
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Commom;
using SAEA.WebAPI.Http;
using SAEA.WebAPI.Mvc; namespace SAEA.WebAPITest.Attrubutes
{
public class LogAtrribute : ActionFilterAttribute
{
/// <summary>
/// 执行前
/// </summary>
/// <param name="httpContext"></param>
/// <returns>返回值true为继续,false为终止</returns>
public override bool OnActionExecuting(HttpContext httpContext)
{
return true;
} /// <summary>
/// 执行后
/// </summary>
/// <param name="httpContext"></param>
/// <param name="result"></param>
public override void OnActionExecuted(HttpContext httpContext, ActionResult result)
{
ConsoleHelper.WriteLine($"请求地址:{httpContext.Request.Query},回复内容:{result.Content}");
}
}
}
program.cs Main中启动一下服务:
1 MvcApplication mvcApplication = new MvcApplication();
2
3 mvcApplication.Start();
最后F5跑起来看看效果:
使用Apache ab.exe压测一下性能如何:
至此,一个简洁、高效的WebApi就初步完成了!
转载请标明本文来源:http://www.cnblogs.com/yswenli/p/8858669.html
更多内容欢迎star作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~