优化 HttpServer 路由逻辑并增强安全性
智能大石头 authored at 2025-09-27 21:54:01
6.79 KiB
X
using System.ComponentModel;
using NewLife.Net;

namespace NewLife.Http;

/// <summary>Http服务器</summary>
/// <remarks>
/// 主要职责:
/// 1. 保存路由映射 <see cref="Routes"/> 并在收到请求时根据路径匹配处理器;
/// 2. 为每个网络会话创建对应的 <see cref="HttpSession"/> 协议处理器;
/// 3. 提供多种 Map 重载(委托/控制器/静态文件)。
/// 
/// 线程安全说明:
/// - 典型使用场景下,路由在启动阶段集中注册,运行期只读访问;
/// - 若需要在运行期动态增删路由,应在外部自行序列化(加锁)调用 Map 方法,或者在未来引入并发字典方案;
/// - 当前实现为了保持兼容性,不直接改为 ConcurrentDictionary,仅在匹配时使用快照数组降低并发修改风险(仍不保证完全线程安全)。
/// </remarks>
[DisplayName("Http服务器")]
public class HttpServer : NetServer, IHttpHost
{
    #region 属性
    /// <summary>Http响应头Server名称</summary>
    public String ServerName { get; set; }

    /// <summary>路由映射。Key 为路径(可含 * 通配),Value 为处理器</summary>
    public IDictionary<String, IHttpHandler> Routes { get; set; } = new Dictionary<String, IHttpHandler>(StringComparer.OrdinalIgnoreCase);
    #endregion

    /// <summary>实例化</summary>
    public HttpServer()
    {
        Name = "Http";
        Port = 80;
        ProtocolType = NetType.Http;

        var ver = GetType().Assembly.GetName().Version ?? new Version();
        ServerName = $"NewLife-HttpServer/{ver.Major}.{ver.Minor}";
    }

    ///// <summary>创建会话</summary>
    ///// <param name="session"></param>
    ///// <returns></returns>
    //protected override INetSession CreateSession(ISocketSession session) => new HttpSession();

    /// <summary>为会话创建网络数据处理器。可作为业务处理实现,也可以作为前置协议解析</summary>
    /// <param name="session"></param> 
    /// <returns></returns>
    public override INetHandler? CreateHandler(INetSession session) => new HttpSession();

    #region 路由注册
    /// <summary>映射路由处理器</summary>
    /// <param name="path">路径,如 /api/test 或 /api/*</param>
    /// <param name="handler">处理器</param>
    public void Map(String path, IHttpHandler handler) => SetRoute(path, handler);

    /// <summary>映射路由处理器(委托)</summary>
    public void Map(String path, HttpProcessDelegate handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射路由处理器(委托返回值)</summary>
    public void Map<TResult>(String path, Func<TResult> handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射路由处理器(带模型)</summary>
    public void Map<TModel, TResult>(String path, Func<TModel, TResult> handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射路由处理器(2 参数)</summary>
    public void Map<T1, T2, TResult>(String path, Func<T1, T2, TResult> handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射路由处理器(3 参数)</summary>
    public void Map<T1, T2, T3, TResult>(String path, Func<T1, T2, T3, TResult> handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射路由处理器(4 参数)</summary>
    public void Map<T1, T2, T3, T4, TResult>(String path, Func<T1, T2, T3, T4, TResult> handler) => SetRoute(path, new DelegateHandler { Callback = handler });

    /// <summary>映射控制器</summary>
    /// <typeparam name="TController">控制器类型</typeparam>
    /// <param name="path">可选起始路径,默认 /{ControllerName}</param>
    public void MapController<TController>(String? path = null) => MapController(typeof(TController), path);

    /// <summary>映射控制器</summary>
    /// <param name="controllerType">控制器类型</param>
    /// <param name="path">可选起始路径,默认 /{ControllerName}</param>
    public void MapController(Type controllerType, String? path = null)
    {
        if (controllerType == null) throw new ArgumentNullException(nameof(controllerType));

        if (path.IsNullOrEmpty()) path = "/" + controllerType.Name.TrimEnd("Controller");

        var path2 = path.EnsureStart("/").EnsureEnd("/*");
        SetRoute(path2, new ControllerHandler { ControllerType = controllerType });
    }

    /// <summary>映射静态文件目录</summary>
    /// <param name="path">映射路径,如 /js</param>
    /// <param name="contentPath">内容目录,如 /wwwroot/js</param>
    public void MapStaticFiles(String path, String contentPath)
    {
        if (contentPath.IsNullOrEmpty()) throw new ArgumentNullException(nameof(contentPath));

        path = path.EnsureStart("/");
        var path2 = path.EnsureEnd("/").EnsureEnd("*");
        SetRoute(path2, new StaticFilesHandler { Path = path.EnsureEnd("/"), ContentPath = contentPath });
    }

    /// <summary>统一设置路由(内部)。自动处理前导 /。</summary>
    private void SetRoute(String path, IHttpHandler handler)
    {
        if (path.IsNullOrEmpty()) throw new ArgumentNullException(nameof(path));
        if (handler == null) throw new ArgumentNullException(nameof(handler));

        // 统一路径格式:必须以 /
        path = path.EnsureStart("/");
        Routes[path] = handler; // 保持原语义:后注册覆盖
    }
    #endregion

    private readonly IDictionary<String, String> _maps = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
    /// <summary>匹配处理器</summary>
    /// <param name="path">已规范化后的请求路径(不含查询)</param>
    /// <param name="request">Http请求对象(可用于深度匹配)</param>
    /// <returns>匹配到的处理器;找不到时返回 null</returns>
    public IHttpHandler? MatchHandler(String path, HttpRequest? request)
    {
        if (path.IsNullOrEmpty()) return null;

        // 直接精确匹配
        if (Routes.TryGetValue(path, out var handler)) return handler;

        // 缓存匹配
        if (_maps.TryGetValue(path, out var p) && Routes.TryGetValue(p, out handler)) return handler;

        // 模糊匹配(使用快照避免运行期新增导致枚举异常)
        foreach (var item in Routes)
        {
            var key = item.Key;
            if (!key.Contains('*')) continue;
            if (!key.IsMatch(path)) continue;

            if (Routes.TryGetValue(key, out handler))
            {
                // 大于3段的路径不做缓存,避免动态Url引起缓存膨胀(保持原逻辑)
                if (handler is StaticFilesHandler || path.Split('/').Length <= 3) _maps[path] = key;
                return handler;
            }
        }

        return null;
    }
}