ET服务器框架学习笔记(十九)

ET服务器框架学习笔记(十九)


前言

本篇将简单介绍下ET服务器框架中的HTTPServer。


提示:内容包含C#中的HTTP相关内容需要自行查阅相关API了

一、HttpComponent

ET服务器框架中对外提供HTTP服务的组件

1.初始化HTTP服务的配置

从StartConfigComponent获取配置

			StartConfig startConfig = StartConfigComponent.Instance.StartConfig;
			this.appType = startConfig.AppType;
			this.HttpConfig = startConfig.GetComponent<HttpConfig>();

2.注册路径与对应处理方法实例类

  • 从EventSystem中,拿到相关HttpHandlerAttribute特性标记的类,将路径与处理类的实例放入dispatcher进行管理。
    调用LoadMethod方法。
public void Load()
		{
			this.dispatcher = new Dictionary<string, IHttpHandler>();
			this.handlersMapping = new Dictionary<MethodInfo, IHttpHandler>();
			this.getHandlers = new Dictionary<string, MethodInfo>();
			this.postHandlers = new Dictionary<string, MethodInfo>();

			List<Type> types = Game.EventSystem.GetTypes(typeof(HttpHandlerAttribute));

			foreach (Type type in types)
			{
				object[] attrs = type.GetCustomAttributes(typeof(HttpHandlerAttribute), false);
				if (attrs.Length == 0)
				{
					continue;
				}

				HttpHandlerAttribute httpHandlerAttribute = (HttpHandlerAttribute)attrs[0];
				if (!httpHandlerAttribute.AppType.Is(this.appType))
				{
					continue;
				}

				object obj = Activator.CreateInstance(type);

				IHttpHandler ihttpHandler = obj as IHttpHandler;
				if (ihttpHandler == null)
				{
					throw new Exception($"HttpHandler handler not inherit IHttpHandler class: {obj.GetType().FullName}");
				}

				this.dispatcher.Add(httpHandlerAttribute.Path, ihttpHandler);

				LoadMethod(type, httpHandlerAttribute, ihttpHandler);
			}
		}
  • LoadMethod方法:
public void LoadMethod(Type type, HttpHandlerAttribute httpHandlerAttribute, IHttpHandler httpHandler)
		{
			// 扫描这个类里面的方法
			MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
			foreach (MethodInfo method in methodInfos)
			{
				object[] getAttrs = method.GetCustomAttributes(typeof(GetAttribute), false);
				if (getAttrs.Length != 0)
				{
					GetAttribute get = (GetAttribute)getAttrs[0];

					string path = method.Name;
					if (!string.IsNullOrEmpty(get.Path))
					{
						path = get.Path;
					}

					getHandlers.Add(httpHandlerAttribute.Path + path, method);
					//Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
				}

				object[] postAttrs = method.GetCustomAttributes(typeof(PostAttribute), false);
				if (postAttrs.Length != 0)
				{
					// Post处理方法
					PostAttribute post = (PostAttribute)postAttrs[0];

					string path = method.Name;
					if (!string.IsNullOrEmpty(post.Path))
					{
						path = post.Path;
					}

					postHandlers.Add(httpHandlerAttribute.Path + path, method);
					//Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
				}

				if (getAttrs.Length == 0 && postAttrs.Length == 0)
				{
					continue;
				}

				handlersMapping.Add(method, httpHandler);
			}
		}

通过类型信息,拿到处理类实例内的所有方法,1.获取Get特性标记的方法,拿到路径,与之前的处理实例的路径拼接起来,那么这个路径就对应了一个HTTP的Get访问路径。然后将这个路径,以及对应的方法信息(MethodInfo)注册到getHandlers中。2.同样的方式,将Post路径与方法注册到postHandlers中。3.最后将方法信息与处理类实例,都注册到handlersMapping中。

3.开启监听HTTP服务

  • 初始化一个HttpListener,通过初始化的HttpConfig,开启监听的前缀:
this.listener.Prefixes.Add(s);
  • 开始监听,并开启异步数据接收:
this.listener.Start();

this.Accept().Coroutine();
  • 开启接收数据,while(true),异步获取context,当HttpListener有新的请求时,会唤醒await。调用InvokeHandler等待异步处理,处理完毕后调用context.Response.Close(),发送回处理结果。
public async ETVoid Accept()
		{
			long instanceId = this.InstanceId;
			
			while (true)
			{
				if (this.InstanceId != instanceId)
				{
					return;
				}

				HttpListenerContext context = await this.listener.GetContextAsync();
				await InvokeHandler(context);
				context.Response.Close();
			}
		}
  • InvokeHandler,调用处理,1.通过context.Request.HttpMethod,区分是Get还是Post请求。
    2.通过context.Request.Url.AbsolutePath,获取对应的处理方法信息。3.再通过方法信息,拿到对应的处理类实例。
    稍有不同的是Post需要从InputStream中读取数据出来,放入到postbody中。
			context.Response.StatusCode = 404;

			MethodInfo methodInfo = null;
			IHttpHandler httpHandler = null;
			string postbody = "";
			switch (context.Request.HttpMethod)
			{
				case "GET":
					this.getHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
					if (methodInfo != null)
					{
						this.handlersMapping.TryGetValue(methodInfo, out httpHandler);
					}
					break;
				case "POST":
					this.postHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
					if (methodInfo != null)
					{
						this.handlersMapping.TryGetValue(methodInfo, out httpHandler);

						using (StreamReader sr = new StreamReader(context.Request.InputStream))
						{
							postbody = sr.ReadToEnd();
						}
					}
					break;
				default:
					context.Response.StatusCode = 405;
					break;
			}
  • InjectParameters方法,从获取的context,对应的方法信息,以及解析出来时PostBody数据,得到所有处理方法中的所有参数的实例对象。
    具体步骤:1.遍历方法信息中的参数信息;2.根据参数信息,分别处理;3.如果是HttpListenerRequest,HttpListenerResponse,直接从context中拿到Request,Response,实例对象存入args数组对应的位置中;4.根据context.Request.HttpMethod方法获取是Get,还是Post;5.如果是Post,则根据上面解析好的PostBody数据,利用JSON反序列化为一个对象,存入args中;6.如果是Get,则从context.Request.QueryString中拿到对应的请求参数字符串,然后通过类型转换拿到实例对象;7.最终返回args数组。
private static object[] InjectParameters(HttpListenerContext context, MethodInfo methodInfo, string postbody)
		{
			context.Response.StatusCode = 200;
			ParameterInfo[] parameterInfos = methodInfo.GetParameters();
			object[] args = new object[parameterInfos.Length];
			for (int i = 0; i < parameterInfos.Length; i++)
			{
				ParameterInfo item = parameterInfos[i];

				if (item.ParameterType == typeof(HttpListenerRequest))
				{
					args[i] = context.Request;
					continue;
				}

				if (item.ParameterType == typeof(HttpListenerResponse))
				{
					args[i] = context.Response;
					continue;
				}

				try
				{
					switch (context.Request.HttpMethod)
					{
						case "POST":
							if (item.Name == "postBody") // 约定参数名称为postBody,只传string类型。本来是byte[],有需求可以改。
							{
								args[i] = postbody;
							}
							else if (item.ParameterType.IsClass && item.ParameterType != typeof(string) && !string.IsNullOrEmpty(postbody))
							{
								object entity = JsonHelper.FromJson(item.ParameterType, postbody);
								args[i] = entity;
							}

							break;
						case "GET":
							string query = context.Request.QueryString[item.Name];
							if (query != null)
							{
								object value = Convert.ChangeType(query, item.ParameterType);
								args[i] = value;
							}

							break;
						default:
							args[i] = null;
							break;
					}
				}
				catch (Exception e)
				{
					Log.Error(e);
					args[i] = null;
				}
			}

			return args;
		}
  • 通过InjectParameters与对应的处理方法信息,解析出所有处理方法需要的实例对象后,调用方法的Invoke方法,传入处理类实例,以及对应的参数数组,获取到恢复数据。特殊处理:如果回复类型是一个ETTASK,说明需要异步得到结果,那么就需要通过await,等待唤醒后,从结果中拿到真正的回复数据
				object[] args = InjectParameters(context, methodInfo, postbody);

				// 自动把返回值,以json方式响应。
				object resp = methodInfo.Invoke(httpHandler, args);
				object result = resp;
				if (resp is ETTask<HttpResult> t)
				{
					await t;
					result = t.GetType().GetProperty("Result").GetValue(t, null);
				}
  • 实例化一个流写入类,通过回复的数据类型,判断是字符串,还是一个对象,将结果通过ToString()方法,或者JsonHelper.ToJson方法转成字符串,然后写入到context.Response.OutputStream流中(格式为UTF8)
			if (result != null)
				{
					using (StreamWriter sw = new StreamWriter(context.Response.OutputStream,Encoding.UTF8))
					{
						if (result.GetType() == typeof(string))
						{
							sw.Write(result.ToString());
						}
						else
						{
							sw.Write(JsonHelper.ToJson(result));
						}
					}
				}
  • 当处理结果已经存入到流中之后,调用context.Response.Close();发送结果给客户端,并结束这次访问。

总结

Http服务模块相对而言比较简单,就不做实例演示了,具体的使用方法,可以查看HttpTest类。
贴一下代码:

[HttpHandler(AppType.Gate, "/")]
	public class HttpTest : AHttpHandler
	{
		[Get] // url-> /Login?name=11&age=1111
		public string Login(string name, int age, HttpListenerRequest req, HttpListenerResponse resp)
		{
			Log.Info(name);
			Log.Info($"{age}");
			return "ok";
		}

		[Get("t")] // url-> /t
		public int Test()
		{
			System.Console.WriteLine("");
			return 1;
		}

		[Post] // url-> /Test1
		public int Test1(HttpListenerRequest req)
		{
			return 1;
		}

		[Get] // url-> /Test2
		public int Test2(HttpListenerResponse resp)
		{
			return 1;
		}

		[Get] // url-> /GetRechargeRecord
		public async ETTask<HttpResult> GetRechargeRecord(long id)
		{
			// var db = Game.Scene.GetComponent<DBProxyComponent>();

			// var info = await db.Query<RechargeRecord>(id);

			await Task.Delay(1000); // 用于测试

			object info = null;
			if (info != null)
			{
				return Ok(data: info);
			}
			else
			{
				return Error("ID不存在!");
			}
		}
	}

HttpTest类比较简单,但是基本上包含了所有的处理方式。实际的处理方式,需要在中间做一次先登录请求一个TOKEN,然后后续都拿这个TOKEN进行访问的处理。这个得自己实现了。
下一篇应该是ET服务器基础篇中的最后一篇了,梳理一下ET服务器框架中的压力测试模块。
不过在ET6.0中猫大改造了通信方式,压测模块应该都搞好了,所以下篇就简单介绍一下5.0中的压测方式吧。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值