引言

此篇是《【轮子狂魔】抛弃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

 interface HttpImplanter
{
void Start();
void Stop();
void MakeHttpPrefix(HttpListener server);
ReturnCode ProcessRequest(HttpListenerContext context);
byte[] CreateReturnResult(HttpListenerContext context, ReturnCode result);
}

改造HttpServer的一些执行细节

 /// <summary>
/// 可接收Http请求的服务器
/// </summary>
class HttpServer
{
Thread _httpListenThread; /// <summary>
/// HttpServer是否已经启动
/// </summary>
volatile bool _isStarted = false; /// <summary>
/// 线程是否已经结束
/// </summary>
volatile bool _terminated = false;
volatile bool _ready = false;
volatile bool _isRuning = false;
HttpImplanter _httpImplanter; public void Start(HttpImplanter httpImplanter)
{
if (!HttpListener.IsSupported)
{
Logger.Exit("不支持HttpListener!");
} if (_isStarted)
{
return;
}
_isStarted = true;
_ready = false;
_httpImplanter = httpImplanter; RunHttpServerThread(); while (!_ready) ;
} private void RunHttpServerThread()
{
_httpListenThread = new Thread(new ThreadStart(() =>
{
HttpListener server = new HttpListener();
try
{
_httpImplanter.MakeHttpPrefix(server);
server.Start();
}
catch (Exception ex)
{
Logger.Exit("无法启动服务器监听,请检查网络环境。");
} _httpImplanter.Start(); IAsyncResult result = null;
while (!_terminated)
{
while (result == null || result.IsCompleted)
{
result = server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
}
_ready = true;
Thread.Sleep();
} server.Stop();
server.Abort();
server.Close();
_httpImplanter.Stop();
}
)); _httpListenThread.IsBackground = true;
_httpListenThread.Start();
} private void ProcessHttpRequest(IAsyncResult iaServer)
{
HttpListener server = iaServer.AsyncState as HttpListener;
HttpListenerContext context = null;
try
{
context = server.EndGetContext(iaServer);
Logger.Info("接收请求" + context.Request.Url.ToString());
//判断上一个操作未完成,即返回服务器正忙,并开启一个新的异步监听
if (_isRuning)
{
Logger.Info("正在处理请求,已忽略请求" + context.Request.Url.ToString());
RetutnResponse(context, _httpImplanter.CreateReturnResult(context, new ReturnCode((int)CommandResult.ServerIsBusy, EnumHelper.GetEnumDescription(CommandResult.ServerIsBusy))));
server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
return;
} _isRuning = true;
server.BeginGetContext(new AsyncCallback(ProcessHttpRequest), server);
}
catch
{
Logger.Warning("服务器已关闭!");
return;
} string scriptName = new UrlHelper(context.Request.Url).ScriptName;
byte[] resultBytes = null;
if (scriptName.ToLower().EndsWith(".html")||scriptName == "favicon.ico")
{
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", scriptName);
if (File.Exists(filePath))
{
resultBytes = File.ReadAllBytes(filePath);
}
else
{
resultBytes = _httpImplanter.CreateReturnResult(context, new ReturnCode((int)CommandResult.FileNotExists, EnumHelper.GetEnumDescription(CommandResult.FileNotExists)));
}
}
else
{
ReturnCode result = _httpImplanter.ProcessRequest(context);
resultBytes = _httpImplanter.CreateReturnResult(context, result);
}
RetutnResponse(context, resultBytes);
_isRuning = false;
} private static void RetutnResponse(HttpListenerContext context, byte[] resultBytes)
{
context.Response.ContentLength64 = resultBytes.Length;
System.IO.Stream output = context.Response.OutputStream;
try
{
output.Write(resultBytes, , resultBytes.Length);
output.Close();
}
catch
{
Logger.Warning("客户端已经关闭!");
}
} public void Stop()
{
if (!_isStarted)
{
return;
} _terminated = true;
_httpListenThread.Join(); _isStarted = false;
} }
Url映射执行方法

1.继承HttpImplanter

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

3.创建返回结果

3.1 解析Url

3.2 定位访问的类

3.3 执行Url所表示的方法

 public class MethodHttpServer : HttpImplanter
{
#region HttpImplanter 成员 public void Start()
{
//nothing to do
} public void Stop()
{
//nothing to do
} public void MakeHttpPrefix(System.Net.HttpListener server)
{
server.Prefixes.Clear();
server.Prefixes.Add("http://localhost:8081/");
Logger.Info("MethodHttpServer添加监听前缀:http://localhost:8081/");
} public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
{
return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
} public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
{
string responseString = string.Empty;
UrlHelper urlHelper = new UrlHelper(context.Request.Url);
var type = Type.GetType("HttpListenerDemo.Test." + urlHelper.ScriptName);
if (type != null)
{
object obj = Activator.CreateInstance(type);
responseString = obj.GetType().GetMethod(urlHelper.Parameters["method"]).Invoke(obj, new object[] { urlHelper.Parameters["param"] }) as string;
} return System.Text.Encoding.UTF8.GetBytes(responseString);
} #endregion
}

测试方法

 public class TestMethod
{
public string Test(string msg)
{
return "TestMethod:" + msg;
}
}

Url映射执行Lua脚本
  使用Lua前要填坑

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

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

 <startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Lua51" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>
<codeBase version="v2.0.50727" href="Lua51.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="LuaInterface" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>
<codeBase version="v2.0.50727" href="LuaInterface.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</startup>

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

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

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

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

     public class LuaApiRegister
{
private Lua _luaVM = null; public LuaApiRegister(object luaAPIClass)
{
_luaVM = new Lua();//初始化Lua虚拟机
BindClassToLua(luaAPIClass);
} private void BindClassToLua(object luaAPIClass)
{
foreach (MethodInfo mInfo in luaAPIClass.GetType().GetMethods())
{
foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo, false))
{
if (!attr.ToString().StartsWith("System.") && !attr.ToString().StartsWith("__"))
{
_luaVM.RegisterFunction((attr as LuaFunction).getFuncName(), luaAPIClass, mInfo);
}
}
}
} public void ExecuteFile(string luaFileName)
{
Logger.Info("开始执行脚本:" + luaFileName);
_luaVM.DoFile(luaFileName);
} public void ExecuteString(string luaCommand)
{
try
{
_luaVM.DoString(luaCommand);
}
catch (Exception e)
{
Console.WriteLine("执行lua脚本指令:" + e.ToString());
}
}
} public class LuaFunction : Attribute
{
private String FunctionName; public LuaFunction(String strFuncName)
{
FunctionName = strFuncName;
} public String getFuncName()
{
return FunctionName;
}
}

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

     public class LuaScriptEngineer
{
public string _scriptRoot;
private string _throwMessage = "";
private ReturnCode _returnCode = null; public void ExecuteScript(string scriptName, NameValueCollection parameters)
{
if (!File.Exists(Path.Combine(_scriptRoot, scriptName + ".lua")))
{
throw new FileNotFoundException();
} LuaApiRegister luaHelper = new LuaApiRegister(new TestLuaApiInterface()); InitLuaGlobalParameter(luaHelper, parameters); ExecuteFile(luaHelper, Path.Combine(_scriptRoot, scriptName + ".lua")); } private void InitLuaGlobalParameter(LuaApiRegister luaHelper, NameValueCollection parameters)
{
foreach (var item in parameters.AllKeys)
{
luaHelper.ExecuteString("a_" + item.Trim() + " = \"" + parameters[item].Replace("\\", "\\\\") + "\";");
}
} private void ExecuteFile(LuaApiRegister luaHelper, string luaFileName)
{
try
{
_throwMessage = "";
_returnCode = null;
luaHelper.ExecuteFile(luaFileName);
}
catch (ReturnCode returnCode)
{
_returnCode = returnCode;
}
catch (Exception ex)
{
_throwMessage = ex.Message;
} if (_returnCode != null)
{
throw _returnCode;
}
else if (string.IsNullOrEmpty(_throwMessage))
{
Logger.Info("脚本执行完毕:" + luaFileName);
}
else if (!string.IsNullOrEmpty(_throwMessage))
{
Logger.Error(_throwMessage);
throw new Exception(_throwMessage);
} } public void Start()
{
_scriptRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Script"); Logger.Info("脚本路径-" + _scriptRoot);
if (!Directory.Exists(_scriptRoot))
{
Logger.Error("脚本根路径不存在!");
} if (File.Exists(_scriptRoot + "Startup.lua"))
{
Logger.Info("开始执行初始化脚本!");
try
{
ExecuteScript("Startup", new NameValueCollection());
}
catch
{
Logger.Error("启动初始化脚本失败!");
}
}
} public void Stop()
{
try
{
Logger.Info("开始执行回收资源脚本!");
ExecuteScript("Cleanup", new NameValueCollection());//清空所调用的资源
}
catch
{
Logger.Warning("回收资源过程出错");
}
} }

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

     class LuaHttpServer : HttpImplanter
{
LuaScriptEngineer _luaScriptEngineer = new LuaScriptEngineer(); #region HttpImplanter 成员 public void Start()
{
_luaScriptEngineer.Start();
} public void Stop()
{
_luaScriptEngineer.Stop();
} public void MakeHttpPrefix(System.Net.HttpListener server)
{
server.Prefixes.Clear();
server.Prefixes.Add("http://localhost:8082/");
Logger.Info("LuaHttpServer添加监听前缀:http://localhost:8082/");
} public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
{
UrlHelper urlHelper = new UrlHelper(context.Request.Url);
CommandResult result = urlHelper.ParseResult;
if (urlHelper.ParseResult == CommandResult.Success)
{
try
{
_luaScriptEngineer.ExecuteScript(urlHelper.ScriptName, urlHelper.Parameters);
return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
}
catch (FileNotFoundException fileNotFoundException)
{
return new ReturnCode((int)CommandResult.NoExistsMethod, EnumHelper.GetEnumDescription(CommandResult.NoExistsMethod));
}
catch (ReturnCode returnCode)
{
return returnCode;
}
catch (Exception ex)
{
return new ReturnCode((int)CommandResult.ExcuteFunctionFailed, EnumHelper.GetEnumDescription(CommandResult.ExcuteFunctionFailed));
}
}
return new ReturnCode((int)result, EnumHelper.GetEnumDescription(result));
} public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
{
string responseString = string.Format("code={0}&msg={1}&request={2}",
result.Code,
result.Message,
context.Request.Url.ToString()
); return System.Text.Encoding.UTF8.GetBytes(responseString);
} #endregion
}

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

     public class TestLuaApiInterface
{
[LuaFunction("Test")]
public void Test(string msg)
{
Logger.Info("TestLuaApiInterface:" + msg);
}
}

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

Test(a_param);

模仿MVC中的C

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

1.创建一个Route

     public class TestMVCRoute
{
public List<string> RegisterRoutes()
{
return new List<string>() { "{controller}/{action}" };
}
}

2.创建一个Controller

     public class TestMVCController
{
public string Test()
{
return "TestMVCController";
}
}

3.扩展一个 MVCHttpServer

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

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

     class MVCHttpServer : HttpImplanter
{
string _route = null;
int _controllerIndex = -;
int _actionIndex = -; #region HttpImplanter 成员 public void Start()
{
_route = new TestMVCRoute().RegisterRoutes().FirstOrDefault(); var routes = _route.Split('/');
for (int i = ; i < routes.Length; i++)
{
if (routes[i] == "{controller}")
{
_controllerIndex = i;
}
else if (routes[i] == "{action}")
{
_actionIndex = i;
}
}
} public void Stop()
{
//nothing to do
} public void MakeHttpPrefix(System.Net.HttpListener server)
{
server.Prefixes.Clear();
server.Prefixes.Add("http://localhost:8083/");
Logger.Info("MVCHttpServer添加监听前缀:http://localhost:8083/");
} public ReturnCode ProcessRequest(System.Net.HttpListenerContext context)
{
return new ReturnCode((int)CommandResult.Success, EnumHelper.GetEnumDescription(CommandResult.Success));
} public byte[] CreateReturnResult(System.Net.HttpListenerContext context, ReturnCode result)
{
string responseString = string.Empty;
var splitedPath = context.Request.Url.AbsolutePath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var controllerName = splitedPath[_controllerIndex] + "Controller";
var actionName = splitedPath[_actionIndex]; var type = Type.GetType("HttpListenerDemo.Test." + controllerName);
if (type != null)
{
object obj = Activator.CreateInstance(type);
responseString = obj.GetType().GetMethod(actionName).Invoke(obj, null) as string;
} return System.Text.Encoding.UTF8.GetBytes(responseString);
} #endregion
}

我有一个想法

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

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

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

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

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

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

【轮子狂魔】抛弃IIS,打造个性的Web Server - WebAPI/Lua/MVC(附带源码)的更多相关文章

  1. Web电子商务网(三层)V2.0源码

    Web电子商务网(三层)V2.0源码 源码描述: 一.源码特点     采用三层架构开发,购物车功能 二.功能介绍 前台集成了产品在线展示,用户注册.在线调查.在线投稿 后台有类别管理\图书管理\订单 ...

  2. 如何快速为团队打造自己的组件库(上)—— Element 源码架构

    文章已收录到 github,欢迎 Watch 和 Star. 简介 详细讲解了 ElementUI 的源码架构,为下一步基于 ElementUI 打造团队自己的组件库打好坚实的基础. 如何快速为团队打 ...

  3. 【轮子狂魔】抛弃IIS,向天借个HttpListener - 基础篇(附带源码)

    这一次我们要玩什么? 先声明一下,由于这篇是基础篇主要是通过这篇文章让大家对使用HttpListener响应Http请求有个大概了解,所以正式的花样轮子在下一篇推出,敬请期待 ^_^ 嗯哼,还有,我标 ...

  4. Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]

    目录 前言 现象 源码分析 实战例子 总结 参考资料 前言 今天研究了一下tomcat上web.xml配置文件中url-pattern的问题. 这个问题其实毕业前就困扰着我,当时忙于找工作. 找到工作 ...

  5. MVC中使用SignalR打造酷炫实用的即时通讯功能附源码

    前言,现在这世道写篇帖子没个前言真不好意思发出来.本贴的主要内容来自于本人在之前项目中所开发的一个小功能,用于OA中的即时通讯.由于当时走的太急,忘记把代码拿出来.想想这已经是大半年前的事情了,时间过 ...

  6. PHP和MySQL Web开发 原书第4版 高清文字版,有目录,附带源码

    PHP和MySQL Web开发  原书第4版:http://yunpan.cn/QCWIS25zmYTAn  提取码 fd9b PHP和MySQL Web开发  原书第4版源码:http://yunp ...

  7. 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  8. web服务器学习1---httpd-2.4.29源码手动编译安装

    环境准备: 系统:CentOS 7.4 软件:httpd-2.4.29 一  Apache主要特点 apache服务器在功能,性能和安全性等方面表现比较突出,可以较好地满足web服务器地应用需求.主要 ...

  9. NFS, web,负载均衡,Nginx yum 源码安装

    作业一:nginx服务1.二进制安装nginx 2.作为web服务修改配置文件 3.让配置生效,验证配置  [root@localhost ~]# systemctl stop firewalld.s ...

随机推荐

  1. 记Git报错-Everything up-to-date

    文:铁乐与猫 今天git push 到github远程仓库的时候,出现报错"Everything up-to-date",严格来说也不算报错,它只是在告诉你,提交区所有的东西都是最 ...

  2. php 开源项目汇总

    WordPress是最热门的开源个人信息发布系统(Blog)之一,基于PHP+MySQL构建.WordPress提供的功能包括:1.文章发布.分类.归档. 2.提供文章.评论.分类等多种形式的RSS聚 ...

  3. Python运算符之三元运算符

    三元运算符:也称之为条件表达式 [条件为真的结果] if 条件 else [条件为假的结果] 如: ium01 = 100 if100 > 200 else200 print(num01) #三 ...

  4. 手写阻塞队列(Condition实现)

    自己实现阻塞队列的话可以采用Object下的wait和notify方法,也可以使用Lock锁提供的Condition来实现,本文就是自己手撸的一个简单的阻塞队列,部分借鉴了JDK的源码.Ps:最近看面 ...

  5. Golang channel 用法简介

    channel 是 golang 里相当有趣的一个功能,大部分时候 channel 都是和 goroutine 一起配合使用.本文主要介绍 channel 的一些有趣的用法. 通道(channel), ...

  6. 【洛谷】【动态规划/二维背包】P1855 榨取kkksc03

    [题目描述:] ... (宣传luogu2的内容被自动省略) 洛谷的运营组决定,如果...,那么他可以浪费掉kkksc03的一些时间的同时消耗掉kkksc03的一些金钱以满足自己的一个愿望. Kkks ...

  7. 安卓预览报错 Failed to load AppCompat ActionBar with unknown error

    报错信息 : Render ProblemFailed to load AppCompat ActionBar with unknown error. Failed to instantiate on ...

  8. Sequelize-nodejs-9-Scopes

    Scopes作用域 Scoping allows you to define commonly used queries that you can easily use later. Scopes c ...

  9. openshift 入门 部署 openshift-origin-server-v3.7.0

    OpenShift是一个基于容器技术的云平台,这里的容器技术指的就是docker和kubernetes. Openshift 错误解决 错误信息 failed to run Kubelet: fail ...

  10. EF 解除属性映射到数据库中 NotMappedAttribute无效解决办法

    可以通过NotMappedAttribute标记模型某个属性可以使该属性不必映射到数据库. public class Unicorn { public int Id { get; set; } [No ...