[fix]修正UdpServer在接收广播时连续启动接收的错误,在StarAgent中,此时可能收到广播包,SocketFlags是Broadcast,需要清空,否则报错“参考的对象类型不支持尝试的操作”; 无需设置SocketOptionName.PacketInformation,在ReceiveMessageFromAsync时会自动设置,并且支持ipv6;
石头 编写于 2024-10-10 00:36:00 石头 提交于 2024-10-10 00:45:43
X
using System;
using System.Collections.Generic;
using NewLife.Data;
using NewLife.Messaging;
using NewLife.Model;
using NewLife.Net;

namespace NewLife.Http
{
    /// <summary>Http编解码器</summary>
    public class HttpCodec : Handler
    {
        #region 属性
        /// <summary>允许分析头部。默认false</summary>
        /// <remarks>
        /// 分析头部对性能有一定损耗
        /// </remarks>
        public Boolean AllowParseHeader { get; set; }
        #endregion

        /// <summary>写入数据</summary>
        /// <param name="context"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public override Object Write(IHandlerContext context, Object message)
        {
            // Http编码器仅支持Tcp
            if (context?.Owner is ISocket sock && sock.Local != null && sock.Local.Type != NetType.Tcp) return base.Write(context, message);

            if (message is HttpMessage http)
            {
                message = http.ToPacket();
            }

            return base.Write(context, message);
        }

        /// <summary>读取数据</summary>
        /// <param name="context"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public override Object Read(IHandlerContext context, Object message)
        {
            // Http编码器仅支持Tcp
            if (context?.Owner is ISocket sock && sock.Local != null && sock.Local.Type != NetType.Tcp) return base.Read(context, message);

            if (message is not Packet pk) return base.Read(context, message);

            // 是否Http请求
            var isGet = pk.Count >= 4 && pk[0] == 'G' && pk[1] == 'E' && pk[2] == 'T' && pk[3] == ' ';
            var isPost = pk.Count >= 5 && pk[0] == 'P' && pk[1] == 'O' && pk[2] == 'S' && pk[3] == 'T' && pk[4] == ' ';

            // 该连接第一包检查是否Http
            var ext = context.Owner as IExtend;
            if (ext["Encoder"] is not HttpEncoder)
            {
                // 第一个请求必须是GET/POST,才执行后续操作
                if (!isGet && !isPost) return base.Read(context, message);

                ext["Encoder"] = new HttpEncoder();
            }

            // 检查是否有未完成消息
            if (ext["Message"] is HttpMessage msg)
            {
                // 数据包拼接到上一个未完整消息中
                if (msg.Payload == null)
                    msg.Payload = pk.Clone(); //拷贝一份,避免缓冲区重用
                else
                    msg.Payload.Append(pk.Clone());//拷贝一份,避免缓冲区重用

                // 消息完整才允许上报
                if (msg.ContentLength == 0 || msg.ContentLength > 0 && msg.Payload != null && msg.Payload.Total >= msg.ContentLength)
                {
                    // 移除消息
                    ext["Message"] = null;

                    // 匹配输入回调,让上层事件收到分包信息
                    //context.FireRead(msg);
                    return base.Read(context, msg);
                }
            }
            else
            {
                // 解码得到消息
                msg = new HttpMessage();
                if (!msg.Read(pk)) throw new XException("Http请求头不完整");

                if (AllowParseHeader && !msg.ParseHeaders()) throw new XException("Http头部解码失败");

                // GET请求一次性过来,暂时不支持头部被拆为多包的场景
                if (isGet)
                {
                    // 匹配输入回调,让上层事件收到分包信息
                    //context.FireRead(msg);
                    return base.Read(context, msg);
                }
                // POST可能多次,最典型的是头部和主体分离
                else
                {
                    // 消息完整才允许上报
                    if (msg.ContentLength == 0 || msg.ContentLength > 0 && msg.Payload != null && msg.Payload.Total >= msg.ContentLength)
                    {
                        // 匹配输入回调,让上层事件收到分包信息
                        //context.FireRead(msg);
                        return base.Read(context, msg);
                    }
                    else
                    {
                        // 请求不完整,拷贝一份,避免缓冲区重用
                        if (msg.Header != null) msg.Header = msg.Header.Clone();
                        if (msg.Payload != null)
                        {
                            // payload有长度时才能复制,否则会造成数据错误
                            if (msg.Payload.Count > 0)
                            {
                                msg.Payload = msg.Payload.Clone();
                            } 
                            else
                            {
                                msg.Payload = null;
                            }
                        }

                        ext["Message"] = msg;
                    }
                }
            }

            return null;
        }
    }

    /// <summary>Http消息</summary>
    public class HttpMessage : IMessage
    {
        #region 属性
        /// <summary>是否响应</summary>
        public Boolean Reply { get; set; }

        /// <summary>是否有错</summary>
        public Boolean Error { get; set; }

        /// <summary>单向请求</summary>
        public Boolean OneWay => false;

        /// <summary>头部数据</summary>
        public Packet Header { get; set; }

        /// <summary>负载数据</summary>
        public Packet Payload { get; set; }

        /// <summary>请求方法</summary>
        public String Method { get; set; }

        /// <summary>请求资源</summary>
        public String Uri { get; set; }

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

        /// <summary>头部集合</summary>
        public IDictionary<String, String> Headers { get; set; }
        #endregion

        #region 方法
        /// <summary>根据请求创建配对的响应消息</summary>
        /// <returns></returns>
        public IMessage CreateReply()
        {
            if (Reply) throw new Exception("不能根据响应消息创建响应消息");

            var msg = new HttpMessage
            {
                Reply = true
            };

            return msg;
        }

        private static readonly Byte[] NewLine = new[] { (Byte)'\r', (Byte)'\n', (Byte)'\r', (Byte)'\n' };
        /// <summary>从数据包中读取消息</summary>
        /// <param name="pk"></param>
        /// <returns>是否成功</returns>
        public virtual Boolean Read(Packet pk)
        {
            var p = pk.IndexOf(NewLine);
            if (p < 0) return false;

            Header = pk.Slice(0, p);
            Payload = pk.Slice(p + 4);

            return true;
        }

        /// <summary>解码头部</summary>
        public virtual Boolean ParseHeaders()
        {
            var pk = Header;
            if (pk == null || pk.Total == 0) return false;

            // 请求方法 GET / HTTP/1.1
            var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
            var ss = pk.ToStr().Split("\r\n");
            {
                var kv = ss[0].Split(' ');
                if (kv != null && kv.Length >= 3)
                {
                    Method = kv[0].Trim();
                    Uri = kv[1].Trim();
                }
            }
            for (var i = 1; i < ss.Length; i++)
            {
                var kv = ss[i].Split(':');
                if (kv != null && kv.Length >= 2)
                {
                    dic[kv[0].Trim()] = kv[1].Trim();
                }
            }
            Headers = dic;

            // 内容长度
            if (dic.TryGetValue("Content-Length", out var str))
                ContentLength = str.ToInt();

            return true;
        }

        /// <summary>把消息转为封包</summary>
        /// <returns></returns>
        public virtual Packet ToPacket()
        {
            // 使用子数据区,不改变原来的头部对象
            var pk = Header.Slice(0, -1);
            pk.Next = NewLine;
            //pk.Next = new[] { (Byte)'\r', (Byte)'\n' };

            var pay = Payload;
            if (pay != null && pay.Total > 0) pk.Append(pay);

            return pk;
        }
        #endregion
    }
}