好的,如果您需要关于C语言编程的帮助,比如代码示例、算法实现、编程问题解答等,请告诉我具体的需求,我会尽力提供帮助。

摘要:using MiniGin;创建引擎(类似 gin.Default()) var app = Gin.Default();启用 Swagger app.UseSwagger("Mini Gin AP
using MiniGin; // 创建引擎(类似 gin.Default()) var app = Gin.Default(); // 启用 Swagger app.UseSwagger("Mini Gin API", "v1"); // 全局中间件 app.Use( Middleware.CORS(), Middleware.RequestId() ); // 根路由 app.GET("/", async ctx => await ctx.String(200, "Mini Gin is ready!")); app.GET("/ping", async ctx => await ctx.JSON(new { message = "pong" })); // API 分组 var api = app.Group("/api"); api.Use(ctx => { ctx.Header("X-Api-Version", "1.0"); return Task.CompletedTask; }); // RESTful 风格路由 api.GET("/users", async ctx => { var page = ctx.Query<int>("page") ?? 1; var size = ctx.Query<int>("size") ?? 10; await ctx.JSON(new { users = new[] { new { id = 1, name = "Alice" }, new { id = 2, name = "Bob" } }, page, size }); }); api.GET("/users/:id", async ctx => { var id = ctx.Param("id"); await ctx.JSON(new { id, name = $"User_{id}" }); }); api.POST("/users", async ctx => { var user = await ctx.BindAsync<CreateUserRequest>(); if (user == null) { await ctx.BadRequest(new { error = "Invalid request body" }); return; } await ctx.Created(new { id = 1, name = user.Name, email = user.Email }); }); api.PUT("/users/:id", async ctx => { var id = ctx.Param("id"); var user = await ctx.BindAsync<UpdateUserRequest>(); await ctx.OK(new { id, updated = true, name = user?.Name }); }); api.DELETE("/users/:id", async ctx => { var id = ctx.Param("id"); await ctx.OK(new { id, deleted = true }); }); // 嵌套分组 var admin = api.Group("/admin"); admin.Use(Middleware.BasicAuth((user, pass) => user == "admin" && pass == "123456")); admin.GET("/dashboard", async ctx => { var user = ctx.Get<string>("user"); await ctx.JSON(new { message = $"Welcome {user}!", role = "admin" }); }); // 启动服务器 await app.Run("http://localhost:5000/"); // 请求模型 record CreateUserRequest(string Name, string Email); record UpdateUserRequest(string? Name, string? Email); 1. Context using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace MiniGin; /// <summary> /// 请求上下文 - 封装 HTTP 请求/响应的所有操作 /// </summary> public sealed class Context { private readonly JsonSerializerOptions _jsonOptions; private readonly Dictionary<string, string> _params; private readonly Dictionary<string, object> _items = new(); private bool _responseSent; private string? _cachedBody; internal Context(HttpListenerContext httpContext, Dictionary<string, string> routeParams, JsonSerializerOptions jsonOptions) { HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext)); _params = routeParams ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); _jsonOptions = jsonOptions; } #region 基础属性 /// <summary>原始 HttpListenerContext</summary> public HttpListenerContext HttpContext { get; } /// <summary>HTTP 请求对象</summary> public HttpListenerRequest Request => HttpContext.Request; /// <summary>HTTP 响应对象</summary> public HttpListenerResponse Response => HttpContext.Response; /// <summary>请求路径</summary> public string Path => Request.Url?.AbsolutePath ?? "/"; /// <summary>请求方法</summary> public string Method => Request.HttpMethod ?? "GET"; /// <summary>完整 URL</summary> public string FullUrl => Request.Url?.ToString() ?? ""; /// <summary>客户端 IP</summary> public string ClientIP => Request.RemoteEndPoint?.Address?.ToString() ?? ""; /// <summary>Content-Type</summary> public string? ContentType => Request.ContentType; /// <summary>是否已中止</summary> public bool IsAborted { get; private set; } #endregion #region 路由参数 /// <summary>获取路由参数</summary> public string? Param(string key) => _params.TryGetValue(key, out var value) ? value : null; /// <summary>获取路由参数(带默认值)</summary> public string Param(string key, string defaultValue) => _params.TryGetValue(key, out var value) ? value : defaultValue; /// <summary>获取所有路由参数</summary> public IReadOnlyDictionary<string, string> Params => _params; #endregion #region 查询参数 /// <summary>获取查询参数</summary> public string? Query(string key) => Request.QueryString[key]; /// <summary>获取查询参数(带默认值)</summary> public string Query(string key, string defaultValue) => Request.QueryString[key] ?? defaultValue; /// <summary>获取查询参数并转换类型</summary> public T? Query<T>(string key) where T : struct { var value = Request.QueryString[key]; if (string.IsNullOrEmpty(value)) return null; try { return (T)Convert.ChangeType(value, typeof(T)); } catch { return null; } } /// <summary>获取所有查询参数的 key</summary> public string[] QueryKeys => Request.QueryString.AllKeys!; #endregion #region 请求头 /// <summary>获取请求头</summary> public string? GetHeader(string key) => Request.Headers[key]; /// <summary>获取请求头(带默认值)</summary> public string GetHeader(string key, string defaultValue) => Request.Headers[key] ?? defaultValue; #endregion #region 请求体 /// <summary>读取原始请求体</summary> public async Task<string> GetRawBodyAsync() { if (_cachedBody != null) return _cachedBody; if (!Request.HasEntityBody) return _cachedBody = string.Empty; using var reader = new StreamReader(Request.InputStream, Request.ContentEncoding ?? Encoding.UTF8); return _cachedBody = await reader.ReadToEndAsync(); } /// <summary>绑定 JSON 请求体到对象</summary> public async Task<T?> BindAsync<T>() where T : class { var body = await GetRawBodyAsync(); if (string.IsNullOrWhiteSpace(body)) return null; return JsonSerializer.Deserialize<T>(body, _jsonOptions); } /// <summary>绑定 JSON 请求体到对象(带默认值)</summary> public async Task<T> BindAsync<T>(T defaultValue) where T : class { var result = await BindAsync<T>(); return result ?? defaultValue; } /// <summary>必须绑定成功,否则抛异常</summary> public async Task<T> MustBindAsync<T>() where T : class { var result = await BindAsync<T>(); return result ?? throw new InvalidOperationException($"Failed to bind request body to {typeof(T).Name}"); } #endregion #region 上下文数据 /// <summary>设置上下文数据</summary> public void Set(string key, object value) => _items[key] = value; /// <summary>获取上下文数据</summary> public T? Get<T>(string key) where T : class => _items.TryGetValue(key, out var value) ? value as T : null; /// <summary>获取上下文数据(带默认值)</summary> public T Get<T>(string key, T defaultValue) where T : class => _items.TryGetValue(key, out var value) && value is T typed ? typed : defaultValue; /// <summary>是否存在上下文数据</summary> public bool Has(string key) => _items.ContainsKey(key); #endregion #region 响应方法 /// <summary>中止请求处理</summary> public void Abort() => IsAborted = true; /// <summary>设置响应头</summary> public Context Header(string key, string value) { Response.Headers[key] = value; return this; } /// <summary>设置状态码并结束响应</summary> public Task Status(int statusCode) { if (!TryStartResponse()) return Task.CompletedTask; Response.StatusCode = statusCode; Response.ContentLength64 = 0; Response.OutputStream.Close(); return Task.CompletedTask; } /// <summary>返回纯文本</summary> public Task String(int statusCode, string content) { if (!TryStartResponse()) return Task.CompletedTask; var bytes = Encoding.UTF8.GetBytes(content); Response.StatusCode = statusCode; Response.ContentType = "text/plain; charset=utf-8"; Response.ContentLength64 = bytes.Length; return WriteAndCloseAsync(bytes); } /// <summary>返回 HTML</summary> public Task HTML(int statusCode, string html) { if (!TryStartResponse()) return Task.CompletedTask; var bytes = Encoding.UTF8.GetBytes(html); Response.StatusCode = statusCode; Response.ContentType = "text/html; charset=utf-8"; Response.ContentLength64 = bytes.Length; return WriteAndCloseAsync(bytes); } /// <summary>返回 JSON</summary> public Task JSON(int statusCode, object? data) { if (!TryStartResponse()) return Task.CompletedTask; var bytes = JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions); Response.StatusCode = statusCode; Response.ContentType = "application/json; charset=utf-8"; Response.ContentLength64 = bytes.Length; return WriteAndCloseAsync(bytes); } /// <summary>返回 JSON(200 状态码)</summary> public Task JSON(object? data) => JSON(200, data); /// <summary>返回原始字节</summary> public Task Data(int statusCode, string contentType, byte[] data) { if (!TryStartResponse()) return Task.CompletedTask; Response.StatusCode = statusCode; Response.ContentType = contentType; Response.ContentLength64 = data.Length; return WriteAndCloseAsync(data); } /// <summary>重定向</summary> public Task Redirect(int statusCode, string location) { if (!TryStartResponse()) return Task.CompletedTask; Response.StatusCode = statusCode; Response.RedirectLocation = location; Response.ContentLength64 = 0; Response.OutputStream.Close(); return Task.CompletedTask; } /// <summary>重定向(302)</summary> public Task Redirect(string location) => Redirect(302, location); #endregion #region 快捷响应方法 /// <summary>200 OK</summary> public Task OK(object? data = null) => data == null ? Status(200) : JSON(200, data); /// <summary>201 Created</summary> public Task Created(object? data = null) => data == null ? Status(201) : JSON(201, data); /// <summary>204 No Content</summary> public Task NoContent() => Status(204); /// <summary>400 Bad Request</summary> public Task BadRequest(object? error = null) => JSON(400, error ?? new { error = "Bad Request" }); /// <summary>401 Unauthorized</summary> public Task Unauthorized(object? error = null) => JSON(401, error ?? new { error = "Unauthorized" }); /// <summary>403 Forbidden</summary> public Task Forbidden(object? error = null) => JSON(403, error ?? new { error = "Forbidden" }); /// <summary>404 Not Found</summary> public Task NotFound(object? error = null) => JSON(404, error ?? new { error = "Not Found" }); /// <summary>500 Internal Server Error</summary> public Task InternalServerError(object? error = null) => JSON(500, error ?? new { error = "Internal Server Error" }); /// <summary>中止并返回状态码</summary> public Task AbortWithStatus(int statusCode) { Abort(); return Status(statusCode); } /// <summary>中止并返回 JSON 错误</summary> public Task AbortWithJSON(int statusCode, object error) { Abort(); return JSON(statusCode, error); } #endregion #region 私有方法 private bool TryStartResponse() { if (_responseSent) return false; _responseSent = true; return true; } private async Task WriteAndCloseAsync(byte[] bytes) { await Response.OutputStream.WriteAsync(bytes, 0, bytes.Length); Response.OutputStream.Close(); } #endregion } 2.Engine using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace MiniGin; /// <summary> /// Gin 风格的 HTTP 引擎 - 核心入口 /// </summary> public class Engine : RouterGroup { private readonly List<Route> _routes = new(); private readonly JsonSerializerOptions _jsonOptions; private HttpListener? _listener; private bool _swaggerEnabled; private string _swaggerTitle = "MiniGin API"; private string _swaggerVersion = "v1"; /// <summary> /// 创建新的引擎实例 /// </summary> public Engine() : this(new JsonSerializerOptions(JsonSerializerDefaults.Web)) { } /// <summary> /// 创建新的引擎实例(自定义 JSON 选项) /// </summary> public Engine(JsonSerializerOptions jsonOptions) : base(null!, "") { _jsonOptions = jsonOptions; SetEngine(this); } private void SetEngine(Engine engine) { var field = typeof(RouterGroup).GetField("_engine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); field?.SetValue(this, engine); } #region 配置 /// <summary> /// 启用 Swagger UI 和 OpenAPI 文档 /// </summary> /// <param name="title">API 标题</param> /// <param name="version">API 版本</param> public Engine UseSwagger(string title = "MiniGin API", string version = "v1") { _swaggerEnabled = true; _swaggerTitle = title; _swaggerVersion = version; return this; } /// <summary> /// 获取所有已注册的路由 /// </summary> public IReadOnlyList<Route> Routes => _routes; /// <summary> /// JSON 序列化选项 /// </summary> public JsonSerializerOptions JsonOptions => _jsonOptions; #endregion #region 路由注册(内部) internal void AddRoute(string method, string path, HandlerFunc[] handlers) { if (string.IsNullOrWhiteSpace(method)) throw new ArgumentException("HTTP method is required.", nameof(method)); if (handlers == null || handlers.Length == 0) throw new ArgumentException("At least one handler is required.", nameof(handlers)); var pattern = RoutePattern.Parse(path); var route = new Route(method.ToUpperInvariant(), path, pattern, handlers); _routes.Add(route); _routes.Sort((a, b) => b.Pattern.LiteralCount.CompareTo(a.Pattern.LiteralCount)); } #endregion #region 运行 /// <summary> /// 启动 HTTP 服务器 /// </summary> /// <param name="address">监听地址,如 http://localhost:5000/</param> public Task Run(string address = "http://localhost:5000/") => Run(address, CancellationToken.None); /// <summary> /// 启动 HTTP 服务器(支持取消) /// </summary> /// <param name="address">监听地址</param> /// <param name="cancellationToken">取消令牌</param> public async Task Run(string address, CancellationToken cancellationToken) { if (!address.EndsWith("/")) address += "/"; _listener = new HttpListener(); _listener.Prefixes.Add(address); _listener.Start(); Console.WriteLine($"[MiniGin] Listening on {address}"); if (_swaggerEnabled) Console.WriteLine($"[MiniGin] Swagger UI: {address}swagger"); try { while (!cancellationToken.IsCancellationRequested) { try { var httpContext = await _listener.GetContextAsync(); _ = Task.Run(() => HandleRequestAsync(httpContext), cancellationToken); } catch (Exception ex) when (!(ex is HttpListenerException)) { Console.WriteLine($"[MiniGin] Error accepting connection: {ex.Message}"); } } } catch (HttpListenerException) when (cancellationToken.IsCancellationRequested) { // 正常关闭 } catch (Exception ex) { Console.WriteLine($"[MiniGin] Fatal error: {ex.Message}"); Console.WriteLine(ex.StackTrace); throw; } finally { _listener.Stop(); _listener.Close(); } } /// <summary> /// 停止服务器 /// </summary> public void Stop() { _listener?.Stop(); } private async Task HandleRequestAsync(HttpListenerContext httpContext) { try { var path = httpContext.Request.Url?.AbsolutePath ?? "/"; var method = httpContext.Request.HttpMethod ?? "GET"; // 处理 Swagger if (_swaggerEnabled && await TryHandleSwaggerAsync(httpContext, path)) return; // 查找路由 var (route, routeParams) = FindRoute(method, path); if (route == null) { await WriteNotFound(httpContext.Response); return; } // 创建上下文 var ctx = new Context(httpContext, routeParams, _jsonOptions); // 执行处理器链 await ExecuteHandlers(ctx, route.Handlers); } catch (Exception ex) { await WriteError(httpContext.Response, ex); } } private async Task ExecuteHandlers(Context ctx, HandlerFunc[] handlers) { foreach (var handler in handlers) { if (ctx.IsAborted) break; await handler(ctx); } } private (Route? route, Dictionary<string, string> routeParams) FindRoute(string method, string path) { foreach (var route in _routes) { if (!string.Equals(route.Method, method, StringComparison.OrdinalIgnoreCase)) continue; if (route.Pattern.TryMatch(path, out var routeParams)) return (route, routeParams); } return (null, new Dictionary<string, string>()); } #endregion #region Swagger private async Task<bool> TryHandleSwaggerAsync(HttpListenerContext context, string path) { if (path.Equals("/swagger", StringComparison.OrdinalIgnoreCase) || path.Equals("/swagger/", StringComparison.OrdinalIgnoreCase)) { var html = GenerateSwaggerHtml(); await WriteResponse(context.Response, 200, "text/html; charset=utf-8", html); return true; } if (path.Equals("/swagger/v1/swagger.json", StringComparison.OrdinalIgnoreCase)) { var doc = GenerateOpenApiDoc(); var json = JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); await WriteResponse(context.Response, 200, "application/json; charset=utf-8", json); return true; } return false; } private object GenerateOpenApiDoc() { var paths = new Dictionary<string, object>(); foreach (var routeGroup in _routes.GroupBy(r => r.OpenApiPath)) { var operations = new Dictionary<string, object>(); foreach (var route in routeGroup) { operations[route.Method.ToLowerInvariant()] = new { operationId = $"{route.Method}_{route.Path.Replace("/", "_").Replace(":", "")}", parameters = route.PathParameters.Select(p => new { name = p, @in = "path", required = true, schema = new { type = "string" } }).ToArray(), responses = new Dictionary<string, object> { ["200"] = new { description = "OK" } } }; } paths[routeGroup.Key] = operations; } return new { openapi = "3.0.1", info = new { title = _swaggerTitle, version = _swaggerVersion }, paths }; } private static string GenerateSwaggerHtml() => @"<!doctype html> <html> <head> <meta charset=""utf-8"" /> <meta name=""viewport"" content=""width=device-width, initial-scale=1"" /> <title>Swagger UI</title> <link rel=""stylesheet"" href=""https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"" /> </head> <body> <div id=""swagger-ui""></div> <script src=""https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js""></script> <script> window.onload = () => { SwaggerUIBundle({ url: '/swagger/v1/swagger.json', dom_id: '#swagger-ui', }); }; </script> </body> </html>"; #endregion #region 响应辅助 private static async Task WriteResponse(HttpListenerResponse response, int statusCode, string contentType, string body) { var bytes = Encoding.UTF8.GetBytes(body); response.StatusCode = statusCode; response.ContentType = contentType; response.ContentLength64 = bytes.Length; await response.OutputStream.WriteAsync(bytes, 0, bytes.Length); response.OutputStream.Close(); } private static Task WriteNotFound(HttpListenerResponse response) => WriteResponse(response, 404, "application/json", "{\"error\":\"Not Found\"}"); private static Task WriteError(HttpListenerResponse response, Exception ex) => WriteResponse(response, 500, "application/json", $"{{\"error\":\"{ex.Message.Replace("\"", "\\\"")}\"}}"); #endregion } 3.Gin namespace MiniGin; /// <summary> /// MiniGin 工厂方法 /// </summary> public static class Gin { /// <summary> /// 创建默认引擎(包含 Logger 和 Recovery 中间件) /// </summary> public static Engine Default() { var engine = new Engine(); engine.Use(Middleware.Logger(), Middleware.Recovery()); return engine; } /// <summary> /// 创建空白引擎(不包含任何中间件) /// </summary> public static Engine New() { return new Engine(); } } 4.Interface namespace MiniGin; /// <summary> /// MiniGin 工厂方法 /// </summary> public static class Gin { /// <summary> /// 创建默认引擎(包含 Logger 和 Recovery 中间件) /// </summary> public static Engine Default() { var engine = new Engine(); engine.Use(Middleware.Logger(), Middleware.Recovery()); return engine; } /// <summary> /// 创建空白引擎(不包含任何中间件) /// </summary> public static Engine New() { return new Engine(); } } 5.Middleware using System; using System.Diagnostics; using System.Threading.Tasks; namespace MiniGin; /// <summary> /// 内置中间件集合 /// </summary> public static class Middleware { /// <summary> /// 请求日志中间件 /// </summary> /// <param name="logger">自定义日志输出(默认 Console.WriteLine)</param> public static HandlerFunc Logger(Action<string>? logger = null) { logger ??= Console.WriteLine; return ctx => { var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); logger($"[{timestamp}] {ctx.Method} {ctx.Path} from {ctx.ClientIP}"); return Task.CompletedTask; }; } /// <summary> /// 请求计时中间件 /// </summary> /// <param name="callback">计时回调</param> public static HandlerFunc Timer(Action<Context, long>? callback = null) { callback ??= (ctx, ms) => Console.WriteLine($"[Timer] {ctx.Method} {ctx.Path} - {ms}ms"); return ctx => { var sw = Stopwatch.StartNew(); ctx.Set("__timer_start", sw); ctx.Set("__timer_callback", (Action)(() => { sw.Stop(); callback(ctx, sw.ElapsedMilliseconds); })); return Task.CompletedTask; }; } /// <summary> /// 错误恢复中间件 /// </summary> /// <param name="showStackTrace">是否显示堆栈跟踪</param> public static HandlerFunc Recovery(bool showStackTrace = false) { return async ctx => { try { // 预留用于自定义错误处理 } catch (Exception ex) { var message = showStackTrace ? ex.ToString() : ex.Message; await ctx.JSON(500, new { error = true, message, timestamp = DateTime.UtcNow }); ctx.Abort(); } }; } /// <summary> /// CORS 中间件 /// </summary> /// <param name="config">CORS 配置</param> public static HandlerFunc CORS(CorsConfig? config = null) { config ??= new CorsConfig(); return async ctx => { ctx.Header("Access-Control-Allow-Origin", config.AllowOrigins) .Header("Access-Control-Allow-Methods", config.AllowMethods) .Header("Access-Control-Allow-Headers", config.AllowHeaders); if (config.AllowCredentials) ctx.Header("Access-Control-Allow-Credentials", "true"); if (config.MaxAge > 0) ctx.Header("Access-Control-Max-Age", config.MaxAge.ToString()); // 预检请求直接返回 if (ctx.Method == "OPTIONS") { await ctx.Status(204); ctx.Abort(); } }; } /// <summary> /// HTTP Basic 认证中间件 /// </summary> /// <param name="validator">用户名密码验证器</param> /// <param name="realm">认证域</param> public static HandlerFunc BasicAuth(Func<string, string, bool> validator, string realm = "Authorization Required") { return async ctx => { var authHeader = ctx.GetHeader("Authorization"); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic ")) { ctx.Header("WWW-Authenticate", $"Basic realm=\"{realm}\""); await ctx.Unauthorized(new { error = "Unauthorized" }); ctx.Abort(); return; } try { var encoded = authHeader["Basic ".Length..]; var decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encoded)); var parts = decoded.Split(':', 2); if (parts.Length != 2 || !validator(parts[0], parts[1])) { await ctx.Unauthorized(new { error = "Invalid credentials" }); ctx.Abort(); } else { ctx.Set("user", parts[0]); } } catch { await ctx.Unauthorized(new { error = "Invalid authorization header" }); ctx.Abort(); } }; } /// <summary> /// API Key 认证中间件 /// </summary> /// <param name="headerName">请求头名称</param> /// <param name="validator">API Key 验证器</param> public static HandlerFunc ApiKey(string headerName, Func<string?, bool> validator) { return async ctx => { var apiKey = ctx.GetHeader(headerName); if (!validator(apiKey)) { await ctx.Unauthorized(new { error = "Invalid API Key" }); ctx.Abort(); } }; } /// <summary> /// 请求 ID 中间件 /// </summary> /// <param name="headerName">请求头名称</param> public static HandlerFunc RequestId(string headerName = "X-Request-ID") { return ctx => { var requestId = ctx.GetHeader(headerName); if (string.IsNullOrEmpty(requestId)) requestId = Guid.NewGuid().ToString("N"); ctx.Set("RequestId", requestId); ctx.Header(headerName, requestId); return Task.CompletedTask; }; } /// <summary> /// 自定义响应头中间件 /// </summary> /// <param name="headers">响应头键值对</param> public static HandlerFunc Headers(params (string key, string value)[] headers) { return ctx => { foreach (var (key, value) in headers) ctx.Header(key, value); return Task.CompletedTask; }; } /// <summary> /// 静态文件中间件(简单实现) /// </summary> /// <param name="urlPrefix">URL 前缀</param> /// <param name="rootPath">文件系统根路径</param> public static HandlerFunc Static(string urlPrefix, string rootPath) { return async ctx => { if (!ctx.Path.StartsWith(urlPrefix, StringComparison.OrdinalIgnoreCase)) return; var relativePath = ctx.Path[urlPrefix.Length..].TrimStart('/'); var filePath = System.IO.Path.Combine(rootPath, relativePath); if (!System.IO.File.Exists(filePath)) { await ctx.NotFound(); ctx.Abort(); return; } var contentType = GetContentType(filePath); var bytes = await System.IO.File.ReadAllBytesAsync(filePath); await ctx.Data(200, contentType, bytes); ctx.Abort(); }; } private static string GetContentType(string filePath) { var ext = System.IO.Path.GetExtension(filePath).ToLowerInvariant(); return ext switch { ".html" or ".htm" => "text/html; charset=utf-8", ".css" => "text/css; charset=utf-8", ".js" => "application/javascript; charset=utf-8", ".json" => "application/json; charset=utf-8", ".png" => "image/png", ".jpg" or ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".svg" => "image/svg+xml", ".ico" => "image/x-icon", ".woff" => "font/woff", ".woff2" => "font/woff2", ".ttf" => "font/ttf", ".pdf" => "application/pdf", ".xml" => "application/xml", _ => "application/octet-stream" }; } } /// <summary> /// CORS 配置 /// </summary> public class CorsConfig { /// <summary>允许的源</summary> public string AllowOrigins { get; set; } = "*"; /// <summary>允许的方法</summary> public string AllowMethods { get; set; } = "GET, POST, PUT, DELETE, PATCH, OPTIONS"; /// <summary>允许的请求头</summary> public string AllowHeaders { get; set; } = "Content-Type, Authorization, X-Requested-With"; /// <summary>是否允许携带凭据</summary> public bool AllowCredentials { get; set; } = false; /// <summary>预检请求缓存时间(秒)</summary> public int MaxAge { get; set; } = 86400; } 6.Route using System; using System.Collections.Generic; using System.Linq; namespace MiniGin; /// <summary> /// 路由定义 /// </summary> public sealed class Route { /// <summary> /// 创建路由定义 /// </summary> public Route(string method, string path, RoutePattern pattern, HandlerFunc[] handlers) { Method = method; Path = path; Pattern = pattern; Handlers = handlers; } /// <summary>HTTP 方法</summary> public string Method { get; } /// <summary>路由路径</summary> public string Path { get; } /// <summary>路由模式</summary> public RoutePattern Pattern { get; } /// <summary>处理器链</summary> public HandlerFunc[] Handlers { get; } /// <summary>OpenAPI 格式路径</summary> public string OpenApiPath => Path.Split('/') .Select(s => s.StartsWith(":") ? "{" + s[1..] + "}" : s) .Aggregate((a, b) => a + "/" + b); /// <summary>路径参数列表</summary> public string[] PathParameters => Path.Split('/') .Where(s => s.StartsWith(":")) .Select(s => s[1..]) .ToArray(); } /// <summary> /// 路由模式解析 /// </summary> public sealed class RoutePattern { private readonly Segment[] _segments; private RoutePattern(Segment[] segments) => _segments = segments; /// <summary> /// 解析路由模式 /// </summary> public static RoutePattern Parse(string path) { var cleaned = (path ?? "/").Trim().Trim('/'); if (string.IsNullOrEmpty(cleaned)) return new RoutePattern(Array.Empty<Segment>()); var parts = cleaned.Split('/', StringSplitOptions.RemoveEmptyEntries); var segments = parts.Select(ParseSegment).ToArray(); return new RoutePattern(segments); } private static Segment ParseSegment(string part) { if (part.StartsWith(":")) return new Segment(true, part[1..], false); if (part.StartsWith("*")) return new Segment(true, part[1..], true); return new Segment(false, part, false); } /// <summary> /// 尝试匹配请求路径 /// </summary> public bool TryMatch(string requestPath, out Dictionary<string, string> routeParams) { routeParams = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var cleaned = (requestPath ?? "/").Trim().Trim('/'); var parts = string.IsNullOrEmpty(cleaned) ? Array.Empty<string>() : cleaned.Split('/', StringSplitOptions.RemoveEmptyEntries); // 检查通配符 var hasWildcard = _segments.Any(s => s.IsWildcard); if (!hasWildcard && parts.Length != _segments.Length) return false; for (var i = 0; i < _segments.Length; i++) { var segment = _segments[i]; if (segment.IsWildcard) { // 通配符匹配剩余所有路径 var remaining = string.Join("/", parts.Skip(i)); routeParams[segment.Value] = Uri.UnescapeDataString(remaining); return true; } if (i >= parts.Length) return false; var value = parts[i]; if (segment.IsParam) { routeParams[segment.Value] = Uri.UnescapeDataString(value); } else if (!string.Equals(segment.Value, value, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } /// <summary>字面量段数量(用于排序)</summary> public int LiteralCount => _segments.Count(s => !s.IsParam); private readonly record struct Segment(bool IsParam, string Value, bool IsWildcard = false); } 7.RouteGroup using System; using System.Collections.Generic; using System.Linq; namespace MiniGin; /// <summary> /// 路由定义 /// </summary> public sealed class Route { /// <summary> /// 创建路由定义 /// </summary> public Route(string method, string path, RoutePattern pattern, HandlerFunc[] handlers) { Method = method; Path = path; Pattern = pattern; Handlers = handlers; } /// <summary>HTTP 方法</summary> public string Method { get; } /// <summary>路由路径</summary> public string Path { get; } /// <summary>路由模式</summary> public RoutePattern Pattern { get; } /// <summary>处理器链</summary> public HandlerFunc[] Handlers { get; } /// <summary>OpenAPI 格式路径</summary> public string OpenApiPath => Path.Split('/') .Select(s => s.StartsWith(":") ? "{" + s[1..] + "}" : s) .Aggregate((a, b) => a + "/" + b); /// <summary>路径参数列表</summary> public string[] PathParameters => Path.Split('/') .Where(s => s.StartsWith(":")) .Select(s => s[1..]) .ToArray(); } /// <summary> /// 路由模式解析 /// </summary> public sealed class RoutePattern { private readonly Segment[] _segments; private RoutePattern(Segment[] segments) => _segments = segments; /// <summary> /// 解析路由模式 /// </summary> public static RoutePattern Parse(string path) { var cleaned = (path ?? "/").Trim().Trim('/'); if (string.IsNullOrEmpty(cleaned)) return new RoutePattern(Array.Empty<Segment>()); var parts = cleaned.Split('/', StringSplitOptions.RemoveEmptyEntries); var segments = parts.Select(ParseSegment).ToArray(); return new RoutePattern(segments); } private static Segment ParseSegment(string part) { if (part.StartsWith(":")) return new Segment(true, part[1..], false); if (part.StartsWith("*")) return new Segment(true, part[1..], true); return new Segment(false, part, false); } /// <summary> /// 尝试匹配请求路径 /// </summary> public bool TryMatch(string requestPath, out Dictionary<string, string> routeParams) { routeParams = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var cleaned = (requestPath ?? "/").Trim().Trim('/'); var parts = string.IsNullOrEmpty(cleaned) ? Array.Empty<string>() : cleaned.Split('/', StringSplitOptions.RemoveEmptyEntries); // 检查通配符 var hasWildcard = _segments.Any(s => s.IsWildcard); if (!hasWildcard && parts.Length != _segments.Length) return false; for (var i = 0; i < _segments.Length; i++) { var segment = _segments[i]; if (segment.IsWildcard) { // 通配符匹配剩余所有路径 var remaining = string.Join("/", parts.Skip(i)); routeParams[segment.Value] = Uri.UnescapeDataString(remaining); return true; } if (i >= parts.Length) return false; var value = parts[i]; if (segment.IsParam) { routeParams[segment.Value] = Uri.UnescapeDataString(value); } else if (!string.Equals(segment.Value, value, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } /// <summary>字面量段数量(用于排序)</summary> public int LiteralCount => _segments.Count(s => !s.IsParam); private readonly record struct Segment(bool IsParam, string Value, bool IsWildcard = false); } 8.MiniHttpApi.csproj <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <!-- 排除 MiniGin 子项目的源文件 --> <ItemGroup> <Compile Remove="MiniGin\**\*.cs" /> <None Remove="MiniGin\**\*" /> </ItemGroup> <ItemGroup> <ProjectReference Include="MiniGin\MiniGin.csproj" /> </ItemGroup> </Project> 9.MiniGin.csproj <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <!-- 包信息 --> <PackageId>MiniGin</PackageId> <Version>1.0.0</Version> <Authors>Your Name</Authors> <Company>Your Company</Company> <Description>A lightweight Gin-style HTTP framework for .NET based on HttpListener</Description> <PackageTags>http;web;framework;gin;api;rest</PackageTags> <PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageReadmeFile>README.md</PackageReadmeFile> <!-- 生成文档 --> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);CS1591</NoWarn> <!-- 根命名空间 --> <RootNamespace>MiniGin</RootNamespace> <AssemblyName>MiniGin</AssemblyName> </PropertyGroup> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="\" /> </ItemGroup> </Project> 10.source liuzhixin405/netgin