[fix]Config创建默认配置文件的开关Runtime.CreateConfigOnMissing,仅需对自动创建生效,而不应该阻止用户主动Save
智能大石头 编写于 2024-08-09 00:30:41 石头 提交于 2024-08-10 14:22:24
X
using NewLife.Collections;
using NewLife.Data;

namespace NewLife.Http;

/// <summary>Http请求响应基类</summary>
public abstract class HttpBase
{
    #region 属性
    /// <summary>协议版本</summary>
    public String Version { get; set; } = "1.1";

    /// <summary>内容长度</summary>
    public Int32 ContentLength { get; set; } = -1;

    /// <summary>内容类型</summary>
    public String ContentType { get; set; }

    /// <summary>请求或响应的主体部分</summary>
    public Packet Body { get; set; }

    /// <summary>主体长度</summary>
    public Int32 BodyLength => Body == null ? 0 : Body.Total;

    /// <summary>是否已完整。头部未指定长度,或指定长度后内容已满足</summary>
    public Boolean IsCompleted => ContentLength < 0 || ContentLength <= BodyLength;

    /// <summary>头部集合</summary>
    public IDictionary<String, String> Headers { get; set; } = new NullableDictionary<String, String>(StringComparer.OrdinalIgnoreCase);

    /// <summary>获取/设置 头部</summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public String this[String key] { get => Headers[key] + ""; set => Headers[key] = value; }
    #endregion

    #region 解析
    /// <summary>快速验证协议头,剔除非HTTP协议。仅排除,验证通过不一定就是HTTP协议</summary>
    /// <param name="pk"></param>
    /// <returns></returns>
    public static Boolean FastValidHeader(Packet pk)
    {
        // 性能优化,Http头部第一行以请求谓语或响应版本开头,然后是一个空格。最长谓语Options/Connect,版本HTTP/1.1,不超过10个字符
        var p = pk.IndexOf([(Byte)' '], 0, 10);
        if (p < 0) return false;

        return true;
    }

    private static readonly Byte[] NewLine = [(Byte)'\r', (Byte)'\n', (Byte)'\r', (Byte)'\n'];
    /// <summary>分析请求头</summary>
    /// <param name="pk"></param>
    /// <returns></returns>
    public Boolean Parse(Packet pk)
    {
        if (!FastValidHeader(pk)) return false;

        // 识别整个请求头
        var p = pk.IndexOf(NewLine);
        if (p < 0) return false;

        var str = pk.ReadBytes(0, p).ToStr();

        // 截取
        var lines = str.Split("\r\n");
        Body = pk.Slice(p + 4);

        // 分析头部
        for (var i = 1; i < lines.Length; i++)
        {
            var line = lines[i];
            p = line.IndexOf(':');
            if (p > 0) Headers[line.Substring(0, p)] = line.Substring(p + 1).Trim();
        }

        ContentLength = Headers["Content-Length"].ToInt(-1);
        ContentType = Headers["Content-Type"];

        // 分析第一行
        if (!OnParse(lines[0])) return false;

        return true;
    }

    /// <summary>分析第一行</summary>
    /// <param name="firstLine"></param>
    protected abstract Boolean OnParse(String firstLine);
    #endregion

    #region 方法
    /// <summary>尝试添加名值到头部</summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public Boolean TryAdd(String name, String value)
    {
        if (Headers.ContainsKey(name)) return false;

        Headers[name] = value;

        return true;
    }

    /// <summary>尝试获取头部值</summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public Boolean TryGetValue(String name, out String value) => Headers.TryGetValue(name, out value);

    /// <summary>创建请求响应包</summary>
    /// <returns></returns>
    public virtual Packet Build()
    {
        var data = Body;
        var len = data != null ? data.Count : -1;

        var header = BuildHeader(len);
        var rs = new Packet(header.GetBytes())
        {
            Next = data
        };

        return rs;
    }

    /// <summary>创建头部</summary>
    /// <param name="length"></param>
    /// <returns></returns>
    protected abstract String BuildHeader(Int32 length);
    #endregion
}