# SimpleHttp **Repository Path**: welcome2jcSpace/simple-http ## Basic Information - **Project Name**: SimpleHttp - **Description**: 【开源】 在线工具解决方案: jquery + js + c# - **Primary Language**: C# - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-11-22 - **Last Updated**: 2022-05-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # html页面接受文件拖拽 ```javascript enableDropEvent: function (dropHandler, node = null) { var el = node || document; el.addEventListener('dragleave', e => { e.preventDefault(); } ); el.addEventListener('dragover', e => { e.preventDefault(); } ); el.addEventListener('dragenter', e => { e.preventDefault(); } ); el.addEventListener('drop', e => { e.preventDefault(); let files = e.dataTransfer.files; dropHandler && dropHandler(files); }); } ``` # 利于时间戳的实时性 和 PI的不确定性 进行验证 > 在将待处理文件POST到后端服务器时 我们需要同时传入 时间戳 和 8位secretKey > 后端在处理的时候 需要考虑 网络的延迟等情况 > 所以后端需要对 传入的时间戳 和 当前时间进行 2秒左右的 误差判定 具体时长按项目判定 如果你上传处理的文件较大 那这个时间就需要被加长 ==**通过WebAssembly隐藏关键代码**== [https://blog.csdn.net/qq_39162566/article/details/121425016](https://blog.csdn.net/qq_39162566/article/details/121425016) [webassembly studio 在线编辑器](https://blog.csdn.net/qq_39162566/article/details/121425016) ==**SercertUtility.wasm 源码**== 由于 跨平台每个系统之间 PI的精度是存在误差的 这里我固定选取了 PI后10000~10180位作为种子 根据自己项目的需要 可以选择随意 PI后指定位置 指定长度的种子 我这里就用180位作为测试了 然后secert key长度选定的是8位 你也可以选择32位 ==随君所愿== ```c #include #define WASM_EXPORT __attribute__((visibility("default"))) WASM_EXPORT unsigned long GetSecret(unsigned long ts) { static int pi10000[] = { 3,0,1,3,0,5,2,7,9,3,2,0,5,4,2,7,4,6,2,8,6,5,4,0,3,6,0,3,6,7,4,5,3,2,8,6,5,1,0,5,7,0,6,5,8,7,4,8,8,2,2,5,6,9,8,1,5,7,9,3,6,7,8,9,7,6,6,9,7,4,2,2,0,5,7,5,0,5,9,6,8,3,4,4,0,8,6,9,7,3,5,0,2,0,1,4,1,0,2,0,6,7,2,3,5,8,5,0,2,0,0,7,2,4,5,2,2,5,6,3,2,6,5,1,3,4,1,0,5,5,9,2,4,0,1,9,0,2,7,4,2,1,6,2,4,8,4,3,9,1,4,0,3,5,9,9,8,9,5,3,5,3,9,4,5,9,0,9,4,4,0,7,0,4,6,9,1,2,0,9 }; int arrLen = sizeof(pi10000) / sizeof(pi10000[0]); unsigned long ID = 0; for (int i = 0; i < 8; i++) { int index = fmod(ts, pow(10, i + 1)); index %= arrLen; ID += pi10000[index] * pow(10, i); } return ID; } ``` ==**fetch 加载.wasm文件**== ```javascript fetch('../SercertUtility.wasm').then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes)).then(lib => { const GetSecret = lib.instance.exports.GetSecret; //将加载的函数指针存于 一个全局的对象中 方便调用 您也用可以使用 window.GetSecret 来缓存 self.jcUtils.GetSecret = () => { let ts = Date.now(); let secret = GetSecret(ts); return [ts, secret]; } }).catch(console.error); ``` # FormData ```javascript var formData = new FormData(); formData.append('file', dropFile); let data = jcUtils.GetSecret(); //jcUtils 是我定义的一个工具对象 里面包含了一些工具方法 其中 就包含 加载 .wasm文件 并缓存内部方法的实现 formData.append('ts', data[0]); formData.append('secret', data[1]); ``` # ajax上传文件到后台 ```javascript $.ajax({ url: 'http://www.geek7.ltd:8081/BuildPlayable/', type: 'POST', data: formData, mimeType: "multipart/form-data", cache: false, processData: false, contentType: false, responseType: 'arraybuffer', success: function (data) { try { let r = JSON.parse(data); if (r.succeed) { let url = r.url; (function (url) { let name = url.substring(url.lastIndexOf("/") + 1); let objectURL = window.URL.createObjectURL(new Blob([url])); let a = document.createElement('a') a.href = objectURL a.download = name a.click() a.remove() })(url) } } catch (e) { console.log(e); } }, }); ``` # C# http 服务后端 ## SimpleHttp:基于HttpListener二次封装 ```csharp class SimpleHttp : IDisposable { private readonly HttpListener _listener; // HTTP 协议侦听器 private readonly Thread _listenerThread; // 监听线程 private readonly Thread[] _workers; // 工作线程组 private readonly ManualResetEvent _stop, _ready; // 通知停止、就绪 private Queue _queue; // 请求队列 //POST方法字典 Dictionary _route_pool = new Dictionary(); //构造 ( 最大多线程数 ) public SimpleHttp(int maxThreads) { _workers = new Thread[maxThreads]; _queue = new Queue(); _stop = new ManualResetEvent(false); _ready = new ManualResetEvent(false); _listener = new HttpListener(); _listenerThread = new Thread(HandleRequests); } //启动Http服务器 public void Start(int port) { // 启动Http服务 _listener.Prefixes.Add(String.Format("http://*:{0}/", port)); _listener.Start(); _listenerThread.Start(); // 启动工作线程 for (int i = 0; i < _workers.Length; i++) { _workers[i] = new Thread(Worker); _workers[i].Start(); } } //响应 public void Response(HttpListenerContext ctx, string res, int state = 200) { byte[] buffer = Encoding.UTF8.GetBytes(res); Response(ctx, buffer, state); } //响应 public void Response(HttpListenerContext ctx, byte[] buffer, int state = 200) { //返回信息 ctx.Response.StatusCode = state; ctx.Response.Headers.Add("Access-Control-Allow-Origin", "*");//跨域 ctx.Response.ContentLength64 = buffer.Length; ctx.Response.OutputStream.Write(buffer, 0, buffer.Length); ctx.Response.OutputStream.Close(); ctx.Response.Close(); } //添加处理方法 public void AddRouter(string method, Action handler, string fileKey = "file") { _route_pool.Add(method, new RouteInfo() { _handFunc = handler, _fileKey = fileKey }); } // 释放资源 public void Dispose() { Stop(); } // 停止服务 public void Stop() { _stop.Set(); _listenerThread.Join(); foreach (Thread worker in _workers) { worker.Join(); } _listener.Stop(); } // 处理请求 private void HandleRequests() { while (_listener.IsListening) { var context = _listener.BeginGetContext(ContextReady, null); if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle })) { return; } } } // 请求就绪加入队列 private void ContextReady(IAsyncResult ar) { try { lock (_queue) { _queue.Enqueue(_listener.EndGetContext(ar)); _ready.Set(); } } catch (Exception e) { Console.WriteLine(string.Format("[HttpServerBase::ContextReady]err:{0}", e.Message)); } } // 处理一个任务 private void Worker() { WaitHandle[] wait = new[] { _ready, _stop }; while (0 == WaitHandle.WaitAny(wait)) { HttpListenerContext context; lock (_queue) { if (_queue.Count > 0) context = _queue.Dequeue(); else { _ready.Reset(); continue; } } try { ProcessHttpRequest(context); } catch (Exception e) { Console.WriteLine(string.Format("[HttpServerBase::Worker]err:{0}", e.Message)); Error(context); //返回前端消息 防止前端锁死 } } } // 请求处理函数 protected void ProcessHttpRequest(HttpListenerContext ctx) { var url = ctx.Request.RawUrl.ToString(); HttpListenerRequest request = ctx.Request; HttpListenerResponse response = ctx.Response; if (request.HttpMethod == "POST") { //获取调用方法名 int index = url.IndexOf('/', 1); if (index != -1) { string method = url.Substring(1, index - 1); RouteInfo router = null; //获取函数指针 if (!_route_pool.TryGetValue(method, out router)) { Error(ctx); return; } //获取Body var hmp = new HttpMultipartParser(request.InputStream, router._fileKey); //获取参数 string param = index + 1 < url.Length ? url.Substring(index + 1, url.Length - 1 - index) : null; //处理 router._handFunc(ctx, param, hmp); } else { Error(ctx, $"post error!!! example:\n\thttp://{ctx.Request.UserHostAddress}/hello/"); } } else { Error(ctx, "Only the POST method is supported !"); } } //默认错误处理 public void Error(HttpListenerContext context, string errContent = "Not Found Method!") { Response(context, errContent, 444); } } /// /// 路由信息 /// internal class RouteInfo { //处理方法 public Action _handFunc = null; //文件指定KEY public string _fileKey = "file"; //TODO 密钥 } /// /// multipart/form-data的解析器 /// internal class HttpMultipartParser { /// /// 参数集合 /// public IDictionary Parameters = new Dictionary(); /// /// 上传文件部分参数 /// public string FilePartName { get; } /// /// 是否解析成功 /// public bool Success { get; private set; } /// /// 请求类型 /// public string ContentType { get; private set; } /// /// 上传的文件名 /// public string Filename { get; private set; } /// /// 上传的文件内容 /// public byte[] FileContents { get; private set; } /// /// 解析multipart/form-data格式的文件请求,默认编码为utf8 /// /// /// public HttpMultipartParser(Stream stream, string filePartName) { FilePartName = filePartName; Parse(stream, Encoding.UTF8); } /// /// 解析multipart/form-data格式的字符串 /// /// public HttpMultipartParser(string content) { var array = Encoding.UTF8.GetBytes(content); var stream = new MemoryStream(array); Parse(stream, Encoding.UTF8); } /// /// 解析multipart/form-data格式的文件请求 /// /// /// 编码 /// public HttpMultipartParser(Stream stream, Encoding encoding, string filePartName) { FilePartName = filePartName; Parse(stream, encoding); } private void Parse(Stream stream, Encoding encoding) { Success = false; var data = ToByteArray(stream); var content = encoding.GetString(data); var delimiterEndIndex = content.IndexOf("\r\n", StringComparison.Ordinal); if (delimiterEndIndex > -1) { var delimiter = content.Substring(0, content.IndexOf("\r\n", StringComparison.Ordinal)).Trim(); var sections = content.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); foreach (var s in sections) { if (s.Contains("Content-Disposition")) { var nameMatch = new Regex(@"(?<=name\=\"")(.*?)(?=\"")").Match(s); var name = nameMatch.Value.Trim().ToLower(); if (name == FilePartName && !string.IsNullOrEmpty(FilePartName)) { var re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)"); var contentTypeMatch = re.Match(content); re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")"); var filenameMatch = re.Match(content); if (contentTypeMatch.Success && filenameMatch.Success) { ContentType = contentTypeMatch.Value.Trim(); Filename = filenameMatch.Value.Trim(); var startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length; var delimiterBytes = encoding.GetBytes("\r\n" + delimiter); var endIndex = IndexOf(data, delimiterBytes, startIndex); var contentLength = endIndex - startIndex; var fileData = new byte[contentLength]; Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength); FileContents = fileData; } } else if (!string.IsNullOrWhiteSpace(name)) { var startIndex = nameMatch.Index + nameMatch.Length + "\r\n\r\n".Length; Parameters.Add(name, s.Substring(startIndex).TrimEnd('\r', '\n').Trim()); } } } if (FileContents != null || Parameters.Count != 0) { Success = true; } } } public static int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex) { var index = 0; var startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex); if (startPos != -1) { while (startPos + index < searchWithin.Length) { if (searchWithin[startPos + index] == serachFor[index]) { index++; if (index == serachFor.Length) { return startPos; } } else { startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index); if (startPos == -1) { return -1; } index = 0; } } } return -1; } public static byte[] ToByteArray(Stream stream) { var buffer = new byte[32768]; using (var ms = new MemoryStream()) { while (true) { var read = stream.Read(buffer, 0, buffer.Length); if (read <= 0) { return ms.ToArray(); } ms.Write(buffer, 0, read); } } } } ``` ## Zip Tool ==对文件解压和回传加压== ```csharp /// /// 适用与ZIP压缩 /// public class ZipHelper { #region 压缩指定文件 但不需要保存到本地 /// /// 功能:压缩文件 /// /// 指定文件 /// 压缩文件的字节流 /// 是否压缩成功 public static bool ZipFile(string file, string zipName, out byte[] buffer) { try { byte[] byteArray = File.ReadAllBytes(file); MemoryStream ms = new MemoryStream(); using (ZipOutputStream zips = new ZipOutputStream(ms)) { zips.SetLevel(9); // 0 - store only to 9 - means best compression var entry = new ZipEntry(zipName); entry.DateTime = DateTime.Now; zips.PutNextEntry(entry); zips.Write(byteArray, 0, byteArray.Length); zips.Finish(); } buffer = ms.ToArray(); //这里是一个zip压缩的文件流 return true; } catch (Exception ex) { throw ex; } } #endregion #region 解压 /// /// 功能:解压zip格式的文件。 /// /// 压缩文件路径 /// 解压文件存放路径,为空时默认与压缩文件同一级目录下,跟压缩文件同名的文件夹 /// 出错信息 /// 解压是否成功 public static bool UnZipFile(string zipFilePath, string unZipDir) { try { //解压文件夹为空时默认与压缩文件同一级目录下,跟压缩文件同名的文件夹 if (unZipDir == string.Empty) unZipDir = zipFilePath.Replace(Path.GetFileName(zipFilePath), Path.GetFileNameWithoutExtension(zipFilePath)); if (!unZipDir.EndsWith("//")) unZipDir += "//"; if (!Directory.Exists(unZipDir)) Directory.CreateDirectory(unZipDir); using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipFilePath))) { ZipEntry theEntry; while ((theEntry = s.GetNextEntry()) != null) { string directoryName = Path.GetDirectoryName(theEntry.Name); string fileName = Path.GetFileName(theEntry.Name); if (directoryName.Length > 0) { Directory.CreateDirectory(unZipDir + directoryName); } if (!directoryName.EndsWith("//")) directoryName += "//"; if (fileName != String.Empty) { using (FileStream streamWriter = File.Create(unZipDir + theEntry.Name)) { int size = 2048; byte[] data = new byte[2048]; while (true) { size = s.Read(data, 0, data.Length); if (size > 0) { streamWriter.Write(data, 0, size); } else { break; } } } } }//while } } catch (Exception ex) { Console.WriteLine(ex); return false; } return true; }//解压结束 #endregion } ``` ## Base64工具类封装 ==一些图片,文件的base64处理== ```csharp class Base64Helper { public static string Img2base64(string img_path) { if (string.IsNullOrEmpty(img_path)) return ""; //图片后缀格式 int index = img_path.LastIndexOf('.'); Image img = Image.FromFile(img_path); if (img != null && index != -1) { var suffix = img_path.Substring(index + 1, img_path.Length - 1 - index).ToLower(); var format = ImageFormat.Png; switch (suffix) { case "jpg": case "jpeg": format = ImageFormat.Jpeg; break; case "bmp": format = ImageFormat.Bmp; break; case "gif": format = ImageFormat.Gif; break; } using (MemoryStream ms = new MemoryStream()) { img.Save(ms, format); byte[] data = ms.ToArray(); return $"data:image/{suffix};base64," + Convert.ToBase64String(data); } } return ""; } public static Image Base642Image(string base64) { if (string.IsNullOrEmpty(base64)) { throw new ArgumentNullException("base64参数不能为空"); } byte[] bytes = Convert.FromBase64String(base64); Image img = null; using (MemoryStream memStream = new MemoryStream(bytes)) { img = Image.FromStream(memStream); } return img; } public static string File2base64(string file) { return Convert.ToBase64String(File.ReadAllBytes(file)); } } ``` ## 基于PI的时钟加密key ==**由于 跨平台每个系统之间 PI的精度是存在误差的 这里我选取了 PI后10000位处的一段数组**== ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Geek7Utils { class Sercert { static int[] pi10000 = new int[] { 3,0,1,3,0,5,2,7,9,3,2,0,5,4,2,7,4,6,2,8,6,5,4,0,3,6,0,3,6,7,4,5,3,2,8,6,5,1,0,5,7,0,6,5,8,7,4,8,8,2,2,5,6,9,8,1,5,7,9,3,6,7,8,9,7,6,6,9,7,4,2,2,0,5,7,5,0,5,9,6,8,3,4,4,0,8,6,9,7,3,5,0,2,0,1,4,1,0,2,0,6,7,2,3,5,8,5,0,2,0,0,7,2,4,5,2,2,5,6,3,2,6,5,1,3,4,1,0,5,5,9,2,4,0,1,9,0,2,7,4,2,1,6,2,4,8,4,3,9,1,4,0,3,5,9,9,8,9,5,3,5,3,9,4,5,9,0,9,4,4,0,7,0,4,6,9,1,2,0,9 }; /// /// /// /// 时间戳 /// public static int GetSercert(int ts) { ts = ts < 0 ? 0 : ts; int[] cells = new int[8]; int len = cells.Length; for (int i = 0; i < len; i++) { cells[i] = ts % (int)Math.Pow(10, i + 1); } int arrLen = pi10000.Length; int ID = 0; for (int i = 0; i < len; i++) { int index = cells[i] % arrLen; ID += pi10000[index] * (int)Math.Pow(10, i); } return ID; } /// /// 获取指定时间的 时间戳 毫秒级 /// /// 当前时间 你可以传入 本地时间 或者 UTC /// 时间戳 毫秒级 public static Int64 GetTimeStamp(DateTime now) { TimeSpan ts = now - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalMilliseconds); } /// /// 将指定时间戳转换成DateTime对象 /// /// 时间戳 毫秒级 /// DateTime对象 public static DateTime Convert2DateTime(Int64 milllTimeStamp ) { return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddMilliseconds(milllTimeStamp); } /// /// 验证包 /// /// /// /// public static bool Verify(string ts, string secret) { Int64 timestamp = Convert.ToInt64(ts); var time = Convert2DateTime(timestamp); if (Math.Abs(DateTime.Now.Second - time.Second) < 5) { return GetSercert((int)timestamp) == Convert.ToInt32(secret); } return false; } } } ``` # 仓库地址 [Gitee https://gitee.com/welcome2jcSpace/simple-http](https://gitee.com/welcome2jcSpace/simple-http) ![在这里插入图片描述](https://img-blog.csdnimg.cn/141895abd6f0484a9799ebd234b16b47.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5p6B5a6i5LiD,size_20,color_FFFFFF,t_70,g_se,x_16)