轮子狂魔抛弃IIS,打造个性的Web Server - WebAPI/Lua/MVC(附带源码)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了轮子狂魔抛弃IIS,打造个性的Web Server - WebAPI/Lua/MVC(附带源码)相关的知识,希望对你有一定的参考价值。

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03


引言

此篇是《【轮子狂魔】抛弃IIS,向天借个HttpListener - 基础篇(附带源码)》的续篇,也可以说是提高篇,如果你对HttpListener不甚了解的话,建议先看下基础篇。

这次玩的东西有点多了,大致分为如下几个方向:

1.支持静态页面

2.Ur映射l执行方法

3.Url映射执行Lua脚本

4.模仿MVC中的C

 

这些东西有什么用?

支持静态页面:这个纯属玩具吧,只是基础篇作为引子的一个简单示例而已。

Url映射执行方法:类似Web API,可以提供一些基于Http协议的交互方式。

Url映射执行Lua脚本:与上面一样,不同的是,在某些场景下更合适,比如业务频繁变动、频繁发布。Lua脚本我就不细说了,不清楚的可以百度一下,是个很好玩的东西。

模仿MVC中的C:这个实例只是想说基于HttpListener我们可以做很多事情,如果你对ASP.NET、MVC很熟悉,你可以做个整整的Web Server出来,甚至做个Web框架都可以。

 

那么除了这些还可以做什么?

其实太多了,比如反向代理、负载均衡、黑名单等等,你都可以做。如果你有兴趣可以跟帖讨论 ^_^

 

改造HttpServer支持横向扩展

 抽离出一个接口:HttpImplanter

技术分享
1 interface HttpImplanter
2     {
3         void Start();
4         void Stop();
5         void MakeHttpPrefix(HttpListener server);
6         ReturnCode ProcessRequest(HttpListenerContext context);
7         byte[] CreateReturnResult(HttpListenerContext context, ReturnCode result);
8     }
View Code

改造HttpServer的一些执行细节

技术分享
  1 /// <summary>
  2     /// 可接收Http请求的服务器
  3     /// </summary>
  4     class HttpServer
  5     {
  6         Thread _httpListenThread;
  7 
  8         /// <summary>
  9         /// HttpServer是否已经启动
 10         /// </summary>
 11         volatile bool _isStarted = false;
 12 
 13         /// <summary>
 14         /// 线程是否已经结束
 15         /// </summary>
 16         volatile bool _terminated = false;
 17         volatile bool _ready = false;
 18         volatile bool _isRuning = false;
 19         HttpImplanter _httpImplanter;
 20 
 21         public void Start(HttpImplanter httpImplanter)
 22         {
 23             if (!HttpListener.IsSupported)
 24             {
 25                 Logger.Exit("不支持HttpListener!");
 26             }
 27 
 28             if (_isStarted)
 29             {
 30                 return;
 31             }
 32             _isStarted = true;
 33             _ready = false;
 34             _httpImplanter = httpImplanter; 
 35 
 36             RunHttpServerThread();
 37 
 38             while (!_ready) ;
 39         }
 40 
 41         private void RunHttpServerThread()
 42         {
 43             _httpListenThread = new Thread(new ThreadStart(() =>
 44             {
 45                 HttpListener server = new HttpListener();
 46                 try
 47                 {
 48                     _httpImplanter.MakeHttpPrefix(server);
 49                     server.Start();
 50                 }
 51                 catch (Exception ex)
 52                 {
 53                     Logger.Exit("无法启动服务器监听,请检查网络环境。");
 54                 }
 55 
 56                 _httpImplanter.Start();
 57 
 58                 IAsyncResult result = null;
 59                 while (!_terminated)
 60                 {
 61                     while (result == null || result.IsCompleted)
 62                     {
 63                         result = server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
 64                     }
 65                     _ready = true;
 66                     Thread.Sleep(10);
 67                 }
 68 
 69                 server.Stop();
 70                 server.Abort();
 71                 server.Close();
 72                 _httpImplanter.Stop();
 73             }
 74             ));
 75 
 76             _httpListenThread.IsBackground = true;
 77             _httpListenThread.Start();
 78         }
 79 
 80         private void ProcessHttpRequest(IAsyncResult iaServer)
 81         {
 82             HttpListener server = iaServer.AsyncState as HttpListener;
 83             HttpListenerContext context = null;
 84             try
 85             {
 86                 context = server.EndGetContext(iaServer);
 87                 Logger.Info("接收请求" + context.Request.Url.ToString());
 88                 //判断上一个操作未完成,即返回服务器正忙,并开启一个新的异步监听
 89                 if (_isRuning)
 90                 {
 91                     Logger.Info("正在处理请求,已忽略请求" + context.Request.Url.ToString());
 92                     RetutnResponse(context, _httpImplanter.CreateReturnResult(context, new ReturnCode((int)CommandResult.ServerIsBusy, EnumHelper.GetEnumDescription(CommandResult.ServerIsBusy))));
 93                     server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
 94                     return;
 95                 }
 96 
 97                 _isRuning = true;
 98                 server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
 99             }
100             catch
101             {
102                 Logger.Warning("服务器已关闭!");
103                 return;
104             }
105 
106             string scriptName = new UrlHelper(context.Request.Url).ScriptName;
107             byte[] resultBytes = null;
108             if (scriptName.ToLower().EndsWith(".html")||scriptName == "favicon.ico")
109             {
110                 string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", scriptName);
111                 if (File.Exists(filePath))
112                 {
113                     resultBytes = File.ReadAllBytes(filePath);
114                 }
115                 else
116                 {
117                     resultBytes = _httpImplanter.CreateReturnResult(context, new ReturnCode((int)CommandResult.FileNotExists, EnumHelper.GetEnumDescription(CommandResult.FileNotExists)));
118                 }
119             }
120             else
121             {
122                 ReturnCode result = _httpImplanter.ProcessRequest(context);
123                 resultBytes = _httpImplanter.CreateReturnResult(context, result);
124             }
125             RetutnResponse(context, resultBytes);
126             _isRuning = false;
127         }
128 
129         private static void RetutnResponse(HttpListenerContext context, byte[] resultBytes)
130         {
131             context.Response.ContentLength64 = resultBytes.Length;
132             System.IO.Stream output = context.Response.OutputStream;
133             try
134             {
135                 output.Write(resultBytes, 0, resultBytes.Length);
136                 output.Close();
137             }
138             catch
139             {
140                 Logger.Warning("客户端已经关闭!");
141             }
142         }
143 
144         public void Stop()
145         {
146             if (!_isStarted)
147             {
148                 return;
149             }
150 
151             _terminated = true;
152             _httpListenThread.Join();
153 
154             _isStarted = false;
155         }
156 
157     }
View Code

 

Url映射执行方法

1.继承HttpImplanter

2.增加监听前缀,用于过滤Url

3.创建返回结果

   3.1 解析Url

   3.2 定位访问的类

   3.3 执行Url所表示的方法

技术分享
 1 public class MethodHttpServer : HttpImplanter
 2     {
 3         #region HttpImplanter 成员
 4 
 5         public void Start()
 6         {
 7             //nothing to do
 8         }
 9 
10         public void Stop()
11         {
12             //nothing to do
13         }
14 
15         public void MakeHttpPrefix(System.Net.HttpListener server)
16         {
17             server.Prefixes.Clear();
18             server.Prefixes.Add("http://localhost:8081/");
19             Logger.Info("MethodHttpServer添加监听前缀:http://localhost:8081/");
20         }
21 
22         public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
23         {
24             return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
25         }
26 
27         public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
28         {
29             string responseString = string.Empty;
30             UrlHelper urlHelper = new UrlHelper(context.Request.Url);
31             var type = Type.GetType("HttpListenerDemo.Test." + urlHelper.ScriptName);
32             if (type != null)
33             {
34                 object obj = Activator.CreateInstance(type);
35                 responseString = obj.GetType().GetMethod(urlHelper.Parameters["method"]).Invoke(obj, new object[] { urlHelper.Parameters["param"] }) as string;
36             }
37 
38             return System.Text.Encoding.UTF8.GetBytes(responseString);
39         }
40 
41         #endregion
42     }
View Code

测试方法

技术分享
1 public class TestMethod
2     {
3         public string Test(string msg)
4         {
5             return "TestMethod:" + msg;
6         }
7     }
View Code

技术分享

 

Url映射执行Lua脚本

 

  使用Lua前要填坑

 首先需要注意的是使用Lua有个坑,我使用的是Lua51.dll,而这个是 .net 2.0版本,而我的程序是 4.0。且这个类库是32位,而我的系统是64位。所以遇到了2个问题。

1.需要修改app.config,增加startup节点,让程序运行时以.net 2.0来加载lua51和LuaInterface这两个程序集。

技术分享
 1 <startup useLegacyV2RuntimeActivationPolicy="true">
 2     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
 3     <runtime>
 4       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
 5         <dependentAssembly>
 6           <assemblyIdentity name="Lua51" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>
 7           <codeBase version="v2.0.50727" href="Lua51.dll"/>
 8         </dependentAssembly>
 9         <dependentAssembly>
10           <assemblyIdentity name="LuaInterface" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>
11           <codeBase version="v2.0.50727" href="LuaInterface.dll"/>
12         </dependentAssembly>
13       </assemblyBinding>
14     </runtime>
15   </startup>
View Code

2.设置项目属性->生成->目标平台-> x86

此时,坑已经填完,开始正式撸代码了。

 

  实现调用Lua和Url与Lua之间的映射

LuaApiRegister:这个类用于注册Lua虚拟机,告诉Lua虚拟机如果寻找提供给Lua使用的API。

技术分享
 1     public class LuaApiRegister
 2     {
 3         private Lua _luaVM = null;
 4 
 5         public LuaApiRegister(object luaAPIClass)
 6         {
 7             _luaVM = new Lua();//初始化Lua虚拟机
 8             BindClassToLua(luaAPIClass);
 9         }
10 
11         private void BindClassToLua(object luaAPIClass)
12         {
13             foreach (MethodInfo mInfo in luaAPIClass.GetType().GetMethods())
14             {
15                 foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo, false))
16                 {
17                     if (!attr.ToString().StartsWith("System.") && !attr.ToString().StartsWith("__"))
18                     {
19                         _luaVM.RegisterFunction((attr as LuaFunction).getFuncName(), luaAPIClass, mInfo);
20                     }
21                 }
22             }
23         }
24 
25         public void ExecuteFile(string luaFileName)
26         {
27             Logger.Info("开始执行脚本:" + luaFileName);
28             _luaVM.DoFile(luaFileName);
29         }
30 
31         public void ExecuteString(string luaCommand)
32         {
33             try
34             {
35                 _luaVM.DoString(luaCommand);
36             }
37             catch (Exception e)
38             {
39                 Console.WriteLine("执行lua脚本指令:" + e.ToString());
40             }
41         }
42     }
43  
44     public class LuaFunction : Attribute
45     {
46         private String FunctionName;
47 
48         public LuaFunction(String strFuncName)
49         {
50             FunctionName = strFuncName;
51         }
52 
53         public String getFuncName()
54         {
55             return FunctionName;
56         }
57     }
View Code

 

LuaScriptEngineer:这个类将会映射Url解析后的指令如何执行Lua脚本。其中包括定位Lua文件、设置变量、执行脚本和回收资源等。

技术分享
  1     public class LuaScriptEngineer
  2     {
  3         public string _scriptRoot;
  4         private string _throwMessage = "";
  5         private ReturnCode _returnCode = null;
  6 
  7         public void ExecuteScript(string scriptName, NameValueCollection parameters)
  8         {
  9             if (!File.Exists(Path.Combine(_scriptRoot, scriptName + ".lua")))
 10             {
 11                 throw new FileNotFoundException();
 12             }
 13 
 14             LuaApiRegister luaHelper = new LuaApiRegister(new TestLuaApiInterface());
 15 
 16             InitLuaGlobalParameter(luaHelper, parameters);
 17 
 18             ExecuteFile(luaHelper, Path.Combine(_scriptRoot, scriptName + ".lua"));
 19 
 20         }
 21 
 22         private void InitLuaGlobalParameter(LuaApiRegister luaHelper, NameValueCollection parameters)
 23         {
 24             foreach (var item in parameters.AllKeys)
 25             {
 26                 luaHelper.ExecuteString("a_" + item.Trim() + " = \\"" + parameters[item].Replace("\\\\", "\\\\\\\\") + "\\";");
 27             }
 28         }
 29 
 30         private void ExecuteFile(LuaApiRegister luaHelper, string luaFileName)
 31         {
 32             try
 33             {
 34                 _throwMessage = "";
 35                 _returnCode = null;
 36                 luaHelper.ExecuteFile(luaFileName);
 37             }
 38             catch (ReturnCode returnCode)
 39             {
 40                 _returnCode = returnCode;
 41             }
 42             catch (Exception ex)
 43             {
 44                 _throwMessage = ex.Message;
 45             }
 46 
 47             if (_returnCode != null)
 48             {
 49                 throw _returnCode;
 50             }
 51             else if (string.IsNullOrEmpty(_throwMessage))
 52             {
 53                 Logger.Info("脚本执行完毕:" + luaFileName);
 54             }
 55             else if (!string.IsNullOrEmpty(_throwMessage))
 56             {
 57                 Logger.Error(_throwMessage);
 58                 throw new Exception(_throwMessage);
 59             }
 60 
 61         }
 62 
 63         public void Start()
 64         {
 65             _scriptRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Script");
 66 
 67             Logger.Info("脚本路径-" + _scriptRoot);
 68             if (!Directory.Exists(_scriptRoot))
 69             {
 70                 Logger.Error("脚本根路径不存在!");
 71             }
 72 
 73             if (File.Exists(_scriptRoot + "Startup.lua"))
 74             {
 75                 Logger.Info("开始执行初始化脚本!");
 76                 try
 77                 {
 78                     ExecuteScript("Startup", new NameValueCollection());
 79                 }
 80                 catch
 81                 {
 82                     Logger.Error("启动初始化脚本失败!");
 83                 }
 84             }
 85         }
 86 
 87         public void Stop()
 88         {
 89             try
 90             {
 91                 Logger.Info("开始执行回收资源脚本!");
 92                 ExecuteScript("Cleanup", new NameValueCollection());//清空所调用的资源
 93             }
 94             catch
 95             {
 96                 Logger.Warning("回收资源过程出错");
 97             }
 98         }
 99 
100     }
View Code

 

LuaHttpServer:这个类做了一些简单的Url校验和调用LuaScriptEnginner。

技术分享
 1     class LuaHttpServer : HttpImplanter
 2     {
 3         LuaScriptEngineer _luaScriptEngineer = new LuaScriptEngineer();
 4 
 5         #region HttpImplanter 成员
 6 
 7         public void Start()
 8         {
 9             _luaScriptEngineer.Start();
10         }
11 
12         public void Stop()
13         {
14             _luaScriptEngineer.Stop();
15         }
16 
17         public void MakeHttpPrefix(System.Net.HttpListener server)
18         {
19             server.Prefixes.Clear();
20             server.Prefixes.Add("http://localhost:8082/");
21             Logger.Info("LuaHttpServer添加监听前缀:http://localhost:8082/");
22         }
23 
24         public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
25         {
26             UrlHelper urlHelper = new UrlHelper(context.Request.Url);
27             CommandResult result = urlHelper.ParseResult;
28             if (urlHelper.ParseResult == CommandResult.Success)
29             {
30                 try
31                 {
32                     _luaScriptEngineer.ExecuteScript(urlHelper.ScriptName, urlHelper.Parameters);
33                     return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
34                 }
35                 catch (FileNotFoundException fileNotFoundException)
36                 {
37                     return new ReturnCode((int)CommandResult.NoExistsMethod, EnumHelper.GetEnumDescription(CommandResult.NoExistsMethod));
38                 }
39                 catch (ReturnCode returnCode)
40                 {
41                     return returnCode;
42                 }
43                 catch (Exception ex)
44                 {
45                     return new ReturnCode((int)CommandResult.ExcuteFunctionFailed, EnumHelper.GetEnumDescription(CommandResult.ExcuteFunctionFailed));
46                 }
47             }
48             return new ReturnCode((int)result, EnumHelper.GetEnumDescription(result));
49         }
50 
51         public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
52         {
53             string responseString = string.Format("code={0}&msg={1}&request={2}",
54                 result.Code,
55                 result.Message,
56                 context.Request.Url.ToString()
57                 );
58 
59             return System.Text.Encoding.UTF8.GetBytes(responseString);
60         }
61 
62         #endregion
63     }
View Code

 

TestLuaApiInterface:提供给Lua可以调用的接口。这里主要是因为C#本身比Lua的可操作性要高。固定的一些功能还是要转回C#来做,Lua只做一些业务组合。

技术分享
1     public class TestLuaApiInterface
2     {
3         [LuaFunction("Test")]
4         public void Test(string msg)
5         {
6             Logger.Info("TestLuaApiInterface:" + msg);
7         }
8     }
View Code

 

Test.lua:这只是个测试脚本,很简单,只执行了一下Test方法,注意这里的参数param前面有 a_ ,是因为区别外部参数与Lua内部参数的一种手段,并非一定要这样。这个a_也是在LuaApiScripEngineer里自动添加的。

Test(a_param);

技术分享

模仿MVC中的C

 其实也不只是C,有一点点Route的东西,因为这里需要解析Url定位Controller。并且使用的是 vNext 的方式,类名以Controller结尾即可,不用继承任何类。

1.创建一个Route

技术分享
1     public class TestMVCRoute
2     {
3         public List<string> RegisterRoutes()
4         {
5             return new List<string>() { "{controller}/{action}" };
6         }
7     }
View Code

2.创建一个Controller

技术分享
1     public class TestMVCController
2     {
3         public string Test()
4         {
5             return "TestMVCController";
6         }
7     }
View Code

3.扩展一个 MVCHttpServer

  需要注意的是,Start方法中处理了Route(偷懒所以只取FirstOrDefault),只是检索出controller和action的位置而已。

  CreateReturnResult方法则是真正的映射逻辑。

技术分享
 1     class MVCHttpServer : HttpImplanter
 2     {
 3         string _route = null;
 4         int _controllerIndex = -1;
 5         int _actionIndex = -1;
 6 
 7         #region HttpImplanter 成员
 8 
 9         public void Start()
10         {
11             _route = new TestMVCRoute().RegisterRoutes().FirstOrDefault();
12 
13             var routes = _route.Split(/);
14             for (int i = 0; i < routes.Length; i++)
15             {
16                 if (routes[i] == "{controller}")
17                 {
18                     _controllerIndex = i;
19                 }
20                 else if (routes[i] == "{action}")
21                 {
22                     _actionIndex = i;
23                 }
24             }
25         }
26 
27         public void Stop()
28         {
29             //nothing to do
30         }
31 
32         public void MakeHttpPrefix(System.Net.HttpListener server)
33         {
34             server.Prefixes.Clear();
35             server.Prefixes.Add("http://localhost:8083/");
36             Logger.Info("MVCHttpServer添加监听前缀:http://localhost:8083/");
37         }
38 
39         public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
40         {
41             return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
42         }
43 
44         public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
45         {
46             string responseString = string.Empty;
47             var splitedPath = context.Request.Url.AbsolutePath.Split(new char[] { / }, StringSplitOptions.RemoveEmptyEntries);
48             var controllerName = splitedPath[_controllerIndex] + "Controller";
49             var actionName = splitedPath[_actionIndex];
50 
51             var type = Type.GetType("HttpListenerDemo.Test." + controllerName);
52             if (type != null)
53             {
54                 object obj = Activator.CreateInstance(type);
55                 responseString = obj.GetType().GetMethod(actionName).Invoke(obj, null) as string;
56             }
57 
58             return System.Text.Encoding.UTF8.GetBytes(responseString);
59         }
60 
61         #endregion
62     }
View Code

技术分享

 

我有一个想法

由于自己一直从事C/S方面的工作,所以我想做个Web项目,可以应用到各种技术,并且是最新的,这期间肯定会遇到重重阻碍,但这并不能影响我前进的步伐。

目前计划使用到的技术包括并不仅限于 ASP.NET MVC 6、SignalR、Web API 3.0、Boostrap、jQuery、Redis。

当然其中一定会有一些创新或者好玩的东西出现。

如果你想加入或者你对此感兴趣,欢迎联系我。

 

最后,又到了大家最喜爱的公布源码环节 ^_^

http://git.oschina.net/doddgu/WebServerDemo

参考页面:http://qingqingquege.cnblogs.com/p/5933752.html

以上是关于轮子狂魔抛弃IIS,打造个性的Web Server - WebAPI/Lua/MVC(附带源码)的主要内容,如果未能解决你的问题,请参考以下文章

第27篇 重复造轮子---模拟IIS服务器

[Windows Server 2008] IIS配置伪静态方法(Web.config模式的IIS rewrite)

Web Server 在iis下部署php网站在iis下

Windows server 2016 搭建IIS(web)服务

Windows Server 2019Web服务 IIS 配置与管理—— IIS 的安装与基本配置 Ⅲ

Windows Server 2003安装IIS服务并配置WEB站点