【輪子狂魔】拋棄IIS,打造個性的Web Server - WebAPI/Lua/MVC(附帶源碼)


引言

此篇是《【輪子狂魔】拋棄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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM