Skip to content

Commit

Permalink
添加 HttpServer, HttpServerAppBase 类
Browse files Browse the repository at this point in the history
  • Loading branch information
SALTWOOD committed Dec 9, 2023
1 parent f4dc336 commit e61ce64
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 1 deletion.
8 changes: 8 additions & 0 deletions Extension/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ public static void Dump<T>(this IEnumerable<T> a)
{
Console.WriteLine($"[{string.Join(", ", a)}");
}

public static void Merge<T>(this ICollection<T> left, IEnumerable<T> right)
{
foreach (T item in right)
{
left.Add(item);
}
}
}
}
70 changes: 70 additions & 0 deletions Network/Http/HttpClientRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using TeraIO.Data;

namespace TeraIO.Network.Http
{
public struct HttpClientRequest
{
public string? Url { get; set; }
public string? ContentType { get; set; }
public string Method { get; set; }
public byte[] Content { get; set; }
public HttpListenerRequest Request { get; set; }
public HttpListenerResponse Response { get; set; }
public Stream InputStream { get => this.Request.InputStream; }
public Stream OutputStream { get => this.Response.OutputStream; }
public int ResponseStatusCode { get => this.Response.StatusCode; set => this.Response.StatusCode = value; }

public T? TryParseTo<T>() where T : class
{
T? result = null;
try
{
result = JsonConvert.DeserializeObject<T>(this.Text);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return result;
}

public T ParseTo<T>() where T : class
{
T? result = JsonConvert.DeserializeObject<T>(this.Text);
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
return result;
}

public int Send(string content)
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
this.OutputStream.Write(bytes);
return bytes.Length;
}

public int Send(byte[] bytes)
{
this.OutputStream.Write(bytes);
return bytes.Length;
}

public string Text
{
get
{
return Encoding.UTF8.GetString(Content);
}
}
}
}
20 changes: 20 additions & 0 deletions Network/Http/HttpHandlerFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TeraIO.Network.Http
{
public class HttpHandlerFunction : Attribute
{
public string Route { get; set; }
public List<string> Methods { get; set; }

public HttpHandlerFunction(string route, string methods = "GET")
{
this.Route = route;
this.Methods = methods.Split(' ').ToList();
}
}
}
66 changes: 66 additions & 0 deletions Network/Http/HttpServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace TeraIO.Network.Http
{
public static class HttpServer
{
/// <summary>
/// 通过一个继承自 <see cref="HttpServerAppBase"/> 的子类来简单创建一个 <see cref="HttpServer"/>
/// </summary>
/// <param name="app"></param>
/// <returns>
/// 生成的 <see cref="HttpServerAppBase"/> 对象,调用 <see cref="HttpServerAppBase.Run"/> 来启动 <see cref="HttpServer"/>
/// </returns>
static HttpServerAppBase LoadNew(HttpServerAppBase app)
{
Type type = app.GetType();

// 获取所有被标记为HttpHandlerFunction的方法
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
Dictionary<HttpHandlerFunction, MethodInfo> methods = new();

// 遍历方法数组,检查每个方法是否被标记为HttpHandlerFunction
foreach (MethodInfo methodInfo in methodInfos)
{
HttpHandlerFunction? attr = Attribute.GetCustomAttribute(methodInfo, typeof(HttpHandlerFunction)) as HttpHandlerFunction;
ParameterInfo[] parameters = methodInfo.GetParameters();
if (attr != null && parameters.Length > 0 && parameters[0].ParameterType == typeof(HttpClientRequest))
{
methods.Add(attr, methodInfo);
}
}
app.methods = methods;
return app;
}

/// <summary>
/// 通过一个 <see cref="MethodInfo"/>[] 来简单创建一个 <see cref="HttpServer"/>
/// </summary>
/// <param name="app"></param>
/// <returns>
/// 生成的 <see cref="HttpServerAppBase"/> 对象,调用 <see cref="HttpServerAppBase.Run"/> 来启动 <see cref="HttpServer"/>
/// </returns>
static HttpServerAppBase LoadNew(IList<MethodInfo> methodInfos)
{
HttpServerAppBase app = new HttpServerAppBase();
Dictionary<HttpHandlerFunction, MethodInfo> methods = new();
// 遍历方法数组,检查每个方法是否被标记为HttpHandlerFunction
foreach (MethodInfo methodInfo in methodInfos)
{
HttpHandlerFunction? attr = Attribute.GetCustomAttribute(methodInfo, typeof(HttpHandlerFunction)) as HttpHandlerFunction;
ParameterInfo[] parameters = methodInfo.GetParameters();
if (attr != null && parameters.Length > 0 && parameters[0].ParameterType == typeof(HttpClientRequest))
{
methods.Add(attr, methodInfo);
}
}
app.methods = methods;
return app;
}
}
}
140 changes: 140 additions & 0 deletions Network/Http/HttpServerAppBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TeraIO.Extension;

namespace TeraIO.Network.Http
{
/// <summary>
/// <see cref="HttpServer"/> 返回的 HttpServer 实例。调用 <see cref="HttpServerAppBase.Run"/> 来启动一个简单的 Http 服务器
/// 正常情况下,你不应该创建这个类的实例,而是由 <see cref="HttpServer"/> 创建或者通过它的子类来获得它的实例!
/// </summary>
public class HttpServerAppBase
{

public string HOST => "127.0.0.1";
public int PORT => 1280;

public List<string> UriPrefixes { get; set; }
CancellationToken cancellationToken;

Check warning on line 23 in Network/Http/HttpServerAppBase.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

The field 'HttpServerAppBase.cancellationToken' is assigned but its value is never used

Check warning on line 23 in Network/Http/HttpServerAppBase.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

The field 'HttpServerAppBase.cancellationToken' is assigned but its value is never used

Check warning on line 23 in Network/Http/HttpServerAppBase.cs

View workflow job for this annotation

GitHub Actions / build (Release)

The field 'HttpServerAppBase.cancellationToken' is assigned but its value is never used

Check warning on line 23 in Network/Http/HttpServerAppBase.cs

View workflow job for this annotation

GitHub Actions / build (Release)

The field 'HttpServerAppBase.cancellationToken' is assigned but its value is never used
public Dictionary<HttpHandlerFunction, MethodInfo> methods;

public HttpServerAppBase(Dictionary<HttpHandlerFunction, MethodInfo>? methods = null)
{
this.cancellationToken = new CancellationToken();
if (methods == null)
{
this.methods = new Dictionary<HttpHandlerFunction, MethodInfo>();
}
else
{
this.methods = methods;
}
this.UriPrefixes = new List<string>();
}

public void Run()
{
HttpListener listener = new HttpListener();
listener.Prefixes.Merge(this.UriPrefixes);
listener.Start();
HandleHttpConnection(listener);
}

public HttpClientRequest ParseHttpRequest(HttpListenerContext context)
{
HttpClientRequest result = new HttpClientRequest
{
Method = context.Request.HttpMethod,
Content = GetBytesFromStream(context.Request.InputStream),
ContentType = context.Request.ContentType,
Url = context.Request.RawUrl,
Request = context.Request,
Response = context.Response
};
return result;
}

public void HandleHttpConnection(HttpListener listener)
{
while (true)
{
HttpListenerContext context = listener.GetContext();
Thread task = new Thread(() => HandleHttpConnectionSingle(context));
task.Start();
}
}

public void HandleHttpConnectionSingle(HttpListenerContext context)
{
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
//Console.WriteLine(IsCorrectRoute(request.RawUrl, "/index"));
MethodInfo[] methods = this.methods.Where(kvp => IsCorrectRoute(request.RawUrl, kvp.Key.Route)).Select(kvp => kvp.Value).OrderBy(f => f.Name.Length).Reverse().ToArray();
if (methods != null)
{
foreach (MethodInfo method in methods)
{
if (method != null)
{
//var a = Convert.ChangeType(this, this.GetType());
object[] parameters = new object[] { ParseHttpRequest(context) };
method.Invoke(this, parameters);
break;
}
}
}
response.Close();
}

public string GetTemplateString(string name)
{
StreamReader sr = new StreamReader($"./templates/{name}");
string t = sr.ReadToEnd();
return t;
}

public byte[] GetTemplateByteArray(string name)
{
StreamReader sr = new StreamReader($"./templates/{name}");
Stream t = sr.BaseStream;
return GetBytesFromStream(t);
}

private bool IsCorrectRoute(string? raw, string? route)
{
if (string.IsNullOrWhiteSpace(raw))
{
return false;
}
if (string.IsNullOrWhiteSpace(route))
{
return false;
}
if (raw.EndsWith('/') && raw.StartsWith(route.EndsWith('/') ? route : $"{route}/"))
{
return true;
}
if ((!raw.EndsWith('/')) && raw.StartsWith(route))
{
return true;
}
return false;
}

public byte[] GetBytesFromStream(Stream s)
{
byte[] buffer = new byte[4096];
List<byte> result = new List<byte>();
while (s.Read(buffer, 0, 4096) > 0)
{
result = result.Concat(buffer).ToList();
}
return result.ToArray();
}
}
}
5 changes: 4 additions & 1 deletion TeraIO.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@

<ItemGroup>
<Folder Include="Compression\" />
<Folder Include="Network\" />
<Folder Include="Minecraft\" />
<Folder Include=".github\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<None Update="LICENSE">
<Pack>True</Pack>
Expand Down

0 comments on commit e61ce64

Please sign in to comment.