Skip to content

Commit

Permalink
update: 从 CSharp-OpenBMCLAPI 迁移了 SimpleWebServer
Browse files Browse the repository at this point in the history
  • Loading branch information
SALTWOOD committed May 12, 2024
1 parent 001ceb0 commit bcd8aff
Show file tree
Hide file tree
Showing 9 changed files with 678 additions and 0 deletions.
59 changes: 59 additions & 0 deletions Network/Http/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace CSharpOpenBMCLAPI.Modules.WebServer
{
public class Client : IDisposable
{
public TcpClient TcpClient;
Stream stream;

public Stream Stream { get => this.stream; }

public Client(TcpClient client, Stream stream)
{
this.TcpClient = client;
this.stream = stream;
}

public void Close()
{
this.stream.Close();
this.TcpClient.Close();
}

public void Dispose() => this.Close();

public async Task<byte[]> Read(int n = 1)
{
byte[] buffer = new byte[n];
long length = await this.stream.ReadAsync(buffer);
byte[] data = new byte[length];
Array.Copy(buffer, data, length);
return data;
}

public async Task Write(byte[] data)
{
await this.stream.WriteAsync(data);
await this.stream.FlushAsync();
}

public async Task CopyTo(Stream stream)
{
await this.stream.CopyToAsync(stream);
await this.stream.FlushAsync();
}

public async Task CopyFrom(Stream stream)
{
await stream.CopyToAsync(this.stream);
await stream.FlushAsync();
}
}
}
128 changes: 128 additions & 0 deletions Network/Http/Header.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpOpenBMCLAPI.Modules.WebServer
{
public class Header : IDictionary<string, string>
{
Dictionary<string, string> tables;

public Header(Dictionary<string, string> tables)
{
this.tables = tables;
}

public Header()
{
this.tables = new Dictionary<string, string>();
}

public static Header FromBytes(byte[][] headers)
{
Dictionary<string, string> tables = new Dictionary<string, string>();
foreach (var header in headers)
{
var h = WebUtils.SplitBytes(header, WebUtils.Encode(": "), 1).ToArray();
if (h.Length < 1)
{
continue;
}
tables.TryAdd(WebUtils.Decode(h[0]).ToLower(), WebUtils.Decode(h[1]));
}
return new Header(tables);
}

public object Get(string key, object defaultValue)
{
return tables.ContainsKey(key) ? tables[key] : defaultValue;
}

public void Set(string key, object value)
{
if (value == null)
{
if (tables.ContainsKey(key)) tables.Remove(key);
return;
}
tables.TryAdd(key, value + "");
}

public bool ContainsKey(string key) => tables.ContainsKey(key);

public override string ToString() => string.Join("\r\n", from kvp in this select $"{kvp.Key}: {kvp.Value}");

// Auto generated.

public string this[string key] { get => tables[key]; set => tables[key] = value; }

public ICollection<string> Keys => tables.Keys;

public ICollection<string> Values => tables.Values;

public int Count => tables.Count;

public bool IsReadOnly => ((ICollection<KeyValuePair<string, string>>)tables).IsReadOnly;

public void Add(string key, string value)
{
tables.Add(key, value);
}

public void Add(KeyValuePair<string, string> item)
{
((ICollection<KeyValuePair<string, string>>)tables).Add(item);
}

public void Clear()
{
tables.Clear();
}

public bool Contains(KeyValuePair<string, string> item)
{
return tables.Contains(item);
}

public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<string, string>>)tables).CopyTo(array, arrayIndex);
}

public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, string>>)tables).GetEnumerator();
}

public bool Remove(string key)
{
return tables.Remove(key);
}

public bool Remove(KeyValuePair<string, string> item)
{
return ((ICollection<KeyValuePair<string, string>>)tables).Remove(item);
}

public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value)
{
return tables.TryGetValue(key, out value);
}

public string? TryGetValue(string key)
{
string? value;
tables.TryGetValue(key, out value);
return value;
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)tables).GetEnumerator();
}
}
}
16 changes: 16 additions & 0 deletions Network/Http/HttpContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace CSharpOpenBMCLAPI.Modules.WebServer
{
public class HttpContext
{
public required Request Request { get; set; }
public required Response Response { get; set; }
public required EndPoint RemoteIPAddress { get; set; }
}
}
53 changes: 53 additions & 0 deletions Network/Http/Request.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpOpenBMCLAPI.Modules.WebServer
{
public class Request
{
internal string? QueryString = "";

public string Method { get; set; }
public string Path { get; set; }
public string Protocol { get; set; }
public Header Header { get; set; }
public int BodyLength { get; set; }
public Client Client { get; set; }
public byte[] BodyData { get; set; }

public Request(Client client, byte[] data)
{
this.Client = client;
byte[][] temp = WebUtils.SplitBytes(data, WebUtils.Encode("\r\n\r\n"), 2).ToArray();
this.BodyData = temp[1];
byte[][] requestHeader = WebUtils.SplitBytes(temp[0], WebUtils.Encode("\r\n")).ToArray();
temp = WebUtils.SplitBytes(requestHeader[0], WebUtils.Encode(" "), 3).ToArray();
(Method, Path, Protocol) = (WebUtils.Decode(temp[0]), WebUtils.Decode(temp[1]), WebUtils.Decode(temp[2]));
Header = Header.FromBytes(requestHeader[1..]);
BodyLength = int.Parse(Header.Get("content-length", 0) + "");
}

public async Task SkipContent()
{
await this.Client.Read(BodyLength - BodyData.Length);
}

public override string ToString()
{
string result = $"""
{Method} {Path} {Protocol}
Headers:
{string.Join("\n ", this.Header.Select(f => $"{f.Key}: {f.Value}"))}
Data:
{Convert.ToHexString(this.BodyData)}
""";
return result;
}
}
}
122 changes: 122 additions & 0 deletions Network/Http/Response.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TeraIO.Extension;

namespace CSharpOpenBMCLAPI.Modules.WebServer
{
public class Response
{
public int StatusCode { get; set; } = 200;
public Header Header { get; set; } = new Header();
public Stream Stream { get; set; } = new MemoryStream();

public async Task Call(Client client, Request request)
{
if (!Header.ContainsKey("Content-Length")) Header.Set("Content-Length", Stream.Length);
Header.Set("X-Powered-By", "CSharp-SaltWood");
string responseHeader = $"HTTP/1.1 {StatusCode} {GetStatusMsg(StatusCode)}\r\n{Header}\r\n\r\n";
await client.Write(responseHeader.Encode());
Stream.CopyTo(client.Stream);
client.Stream.Flush();
Stream.Close();
}

/// <summary>
/// HTTP 状态码
/// </summary>
public static readonly Dictionary<int, string> STATUS_CODES = new Dictionary<int, string>
{
{ 100, "Continue" }, // 继续
{ 101, "Switching Protocols" }, // 切换协议
{ 102, "Processing" }, // 处理中
{ 103, "Early Hints" }, // 预加载资源
{ 200, "OK" }, // 请求成功
{ 201, "Created" }, // 成功创建资源
{ 202, "Accepted" }, // 收到请求
{ 203, "Non-Authoritative Information" }, // 服务器成功处理请求,但返回信息可能不是原始服务器上的有效集合,可能是本地或第三方拷贝
{ 204, "No Content" }, // 没有 body
{ 205, "Reset Content" }, // 重置文档
{ 206, "Partial Content" }, // 部分资源
{ 207, "Multi-Status" }, // 不知道选哪个状态码,所以干脆全都丢给你
{ 208, "Already Reported" }, // 在 DAV 里面使用 <dav:propstat> 响应元素以避免重复枚举多个绑定的内部成员到同一个集合
{ 226, "IM Used" }, // 服务器已经完成了对资源的GET请求,并且响应是对当前实例应用的一个或多个实例操作结果的表示
{ 300, "Multiple Choices" }, // 不知道你要哪个,干脆全部丢给你,让你自己选
{ 301, "Moved Pemanently" }, // 资源永久迁移啦
{ 302, "Found" }, // 暂时搬走了,去这里找;以后有啥更改,还是在老地方通知
{ 303, "See Other" }, // 用 GET 方法去这个地方找资源,别来我这儿
{ 304, "Not Modified" }, // 改都没改
{ 305, "Use Proxy" }, // 你还是开个代理吧
{ 306, "Unused" }, // 鬼知道有什么用
{ 307, "Temporary Redirect" }, // 跟 302 一样,但你用的什么方法原来是什么,现在也必须也用什么
{ 308, "308 Permanent Redirect" }, // 跟 301 一样,但你用的是方法原来是什么,现在也必须也用什么
{ 400, "Bad Request" }, // 你的锅,给我整不会了
{ 401, "Unauthorized" }, // 你谁啊?
{ 402, "Payment Required" }, // V 我 50
{ 403, "Forbidden" }, // 禁 止 访 问
{ 404, "Not Found" }, // 没 有
{ 405, "Method Not Allowed" }, // 方 法 不 对
{ 406, "Not Acceptable" }, // 当 web 服务器在执行服务端驱动型内容协商机制后,没有发现任何符合用户代理给定标准的内容时,就会发送此响应
{ 407, "Proxy Authentication Required" }, // 你代理谁啊?
{ 408, "Request Time-out" }, // 超 时 了
{ 409, "Conflict" }, // 公 交 车(划)冲 突 了
{ 410, "Gone" }, // 资 源 死 了 , 删 了 吧
{ 411, "Length Required" }, // 你要多长,你不告诉我,我咋知道
{ 412, "Precondition Failed" }, // 不是不报,条件未满足
{ 413, "Payload Too Large" }, // 请 求 实 体 太 大
{ 414, "URI Too Long" }, // 链 接 太 长
{ 415, "Unsupported Media Type" }, // 请求数据的媒体格式不支持
{ 416, "Range Not Satisfiable" }, // 你范围太大了
{ 417, "Expectation Failed" }, // 对不起,我做不到(指 Expect)
{ 418, "I'm a teapot" }, // 我 是 茶 壶
{ 421, "Misdirected Request" }, // 请求被定向到无法生成响应的服务器
{ 422, "Unprocessable Entity" }, // 格式正确但语义错误
{ 423, "Locked" }, // 锁上了
{ 424, "Failed Dependency" }, // 多米诺骨牌倒了,上一个请求炸了,这个也不行
{ 425, "Too Early" }, // 你来的真早!当前服务器不愿意冒风险处理请求,请稍等几分钟
{ 426, "Upgrade Required" }, // 发 现 新 版 本
{ 428, "Precondition Required" }, // 我是有条件嘀~
{ 429, "Too Many Requests" }, // 太快了,停下来!
{ 431, "Request Header Fields Too Large" }, // 请求头太大了
{ 451, "Unavailable For Legal Reasons" }, // 法 律 封 锁
{ 500, "Internal Server Eror" }, // 给服务器整不会了
{ 501, "Not Implemented" }, // 我 不 会
{ 502, "Bad Gateway" }, // 网关炸了
{ 503, "Service Unavailable" }, // 伺 服 器 維 護 中 , 將 結 束 應 用 程 式 。
{ 504, "Gateway Time-out" }, // 网关超时
{ 505, "HTTP Version not supported" }, // HTTP 版本不支持
{ 506, "Variant Also Negotiates" }, // 服务器配置文件炸了
{ 507, "Insufficient Storage" }, // 无法在资源上执行该方法
{ 508, "Loop Detected" }, // 死 循 环 辣
{ 510, "Not Extended" }, // 你得扩展扩展
{ 511, "Network Authentication Required" } // 登 录 校 园 网
};

public static string GetStatusMsg(int status)
{
return STATUS_CODES.ContainsKey(status) ? STATUS_CODES[status] : STATUS_CODES[status / 100 * 100];
}

public ValueTask SendFile(string filePath)
{
this.Stream = File.OpenRead(filePath);
return ValueTask.CompletedTask;
}

public Task WriteAsync(byte[] buffer, int offset, int count)
{
return this.Stream.WriteAsync(buffer, offset, count);
}

public Task WriteAsync(byte[] buffer)
{
return this.Stream.WriteAsync(buffer, 0, buffer.Length);
}

public Task WriteAsync(string data) => this.WriteAsync(Encoding.UTF8.GetBytes(data));

public void ResetStreamPosition() => this.Stream.Position = 0;
}
}
Loading

0 comments on commit bcd8aff

Please sign in to comment.