发布:4.0.2

This commit is contained in:
若汝棋茗
2025-12-06 15:25:46 +08:00
parent 54febd14b4
commit d2ef2e941d
28 changed files with 1007 additions and 651 deletions

View File

@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<BaseVersion>4.0.1</BaseVersion> <BaseVersion>4.0.2</BaseVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'"> <PropertyGroup Condition="'$(Configuration)'=='Release'">

View File

@@ -137,7 +137,7 @@ public sealed class ConsoleAction
var separator = new string('─', maxOrderLength + 4); var separator = new string('─', maxOrderLength + 4);
var distinctActions = new List<string>(); var distinctActions = new List<string>();
foreach (var item in this.m_actions.OrderBy(a => a.Value.FullOrder)) foreach (var item in this.m_actions)
{ {
if (!distinctActions.Contains(item.Value.FullOrder.ToLower())) if (!distinctActions.Contains(item.Value.FullOrder.ToLower()))
{ {

View File

@@ -0,0 +1,128 @@
// ------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
// ------------------------------------------------------------------------------
namespace TouchSocket.Core;
/// <summary>
/// 表示一个字符串池,用于高效管理和复用字符串。
/// </summary>
public sealed class StringPool
{
private readonly Encoding m_encoding;
private readonly int m_maxCapacity;
private struct Entry
{
public byte[] Bytes;
public string Value;
public int Length;
public byte First;
}
private readonly Dictionary<int, Entry[]> m_buckets;
/// <summary>
/// 初始化 <see cref="StringPool"/> 类的新实例。
/// </summary>
/// <param name="encoding">用于字符串编码的 <see cref="Encoding"/>。</param>
/// <param name="maxCapacity">最大容量,默认为 -1 表示不限制。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="encoding"/> 为 <see langword="null"/> 时抛出。</exception>
public StringPool(Encoding encoding, int maxCapacity = -1)
{
ThrowHelper.ThrowIfNull(encoding, nameof(encoding));
this.m_encoding = encoding;
this.m_maxCapacity = maxCapacity;
this.m_buckets = new Dictionary<int, Entry[]>();
}
/// <summary>
/// 获取与指定字节序列对应的字符串。
/// </summary>
/// <param name="bytes">字节序列。</param>
/// <returns>与字节序列对应的字符串。</returns>
public string Get(ReadOnlySpan<byte> bytes)
{
if (bytes.Length == 0)
{
return string.Empty;
}
var hash = ComputeHash(bytes);
if (this.m_buckets.TryGetValue(hash, out var entries))
{
for (var i = 0; i < entries.Length; i++)
{
var e = entries[i];
if (e.Length == bytes.Length && e.First == bytes[0] && bytes.SequenceEqual(e.Bytes))
{
return e.Value;
}
}
}
var str = bytes.ToString(this.m_encoding);
var keyBytes = bytes.ToArray();
var newEntry = new Entry
{
Bytes = keyBytes,
Value = str,
Length = keyBytes.Length,
First = keyBytes[0]
};
if (this.m_buckets.TryGetValue(hash, out var existingEntries))
{
for (var i = 0; i < existingEntries.Length; i++)
{
var cur = existingEntries[i];
if (cur.Length == newEntry.Length && cur.First == newEntry.First && new ReadOnlySpan<byte>(newEntry.Bytes).SequenceEqual(cur.Bytes))
{
return cur.Value;
}
}
if (this.m_maxCapacity == -1 || this.m_buckets.Count < this.m_maxCapacity)
{
var arr = new Entry[existingEntries.Length + 1];
Array.Copy(existingEntries, arr, existingEntries.Length);
arr[existingEntries.Length] = newEntry;
this.m_buckets[hash] = arr;
}
}
else
{
if (this.m_maxCapacity == -1 || this.m_buckets.Count < this.m_maxCapacity)
{
this.m_buckets[hash] = new[] { newEntry };
}
}
return str;
}
private static int ComputeHash(ReadOnlySpan<byte> data)
{
unchecked
{
const uint offset = 2166136261u;
const uint prime = 16777619u;
var hash = offset;
for (var i = 0; i < data.Length; i++)
{
hash ^= data[i];
hash *= prime;
}
return (int)hash;
}
}
}

View File

@@ -34,7 +34,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public TextValues(string value) public TextValues(string value)
{ {
m_values = value; this.m_values = value;
} }
/// <summary> /// <summary>
@@ -42,7 +42,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public TextValues(string[] values) public TextValues(string[] values)
{ {
m_values = values; this.m_values = values;
} }
/// <summary> /// <summary>
@@ -53,7 +53,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
var value = m_values; var value = this.m_values;
if (value is null) if (value is null)
{ {
return 0; return 0;
@@ -74,9 +74,17 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
object value = m_values; var value = this.m_values;
if (value is null) return null; if (value is null)
if (value is string s) return s; {
return null;
}
if (value is string s)
{
return s;
}
var arr = Unsafe.As<string[]>(value); var arr = Unsafe.As<string[]>(value);
return arr.Length > 0 ? arr[0] : null; return arr.Length > 0 ? arr[0] : null;
} }
@@ -85,7 +93,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// <summary> /// <summary>
/// 指示是否为空集合。 /// 指示是否为空集合。
/// </summary> /// </summary>
public bool IsEmpty => Count == 0; public bool IsEmpty => this.m_values is null;
/// <summary> /// <summary>
/// 通过索引获取指定值。 /// 通过索引获取指定值。
@@ -95,15 +103,10 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
object value = m_values; var value = this.m_values ?? throw new IndexOutOfRangeException();
if (value is null)
{
throw new IndexOutOfRangeException();
}
if (value is string s) if (value is string s)
{ {
if (index == 0) return s; return index == 0 ? s : throw new IndexOutOfRangeException();
throw new IndexOutOfRangeException();
} }
return Unsafe.As<string[]>(value)[index]; return Unsafe.As<string[]>(value)[index];
} }
@@ -153,7 +156,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
{ {
return this; return this;
} }
var current = m_values; var current = this.m_values;
if (current is null) if (current is null)
{ {
return new TextValues(value); return new TextValues(value);
@@ -174,7 +177,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public bool Equals(string other, StringComparison comparison) public bool Equals(string other, StringComparison comparison)
{ {
return Equals(new TextValues(other)); return this.Equals(new TextValues(other));
} }
/// <summary> /// <summary>
@@ -182,17 +185,17 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public bool Equals(TextValues other) public bool Equals(TextValues other)
{ {
if (ReferenceEquals(m_values, other.m_values)) return true; if (ReferenceEquals(this.m_values, other.m_values)) return true;
int count = Count; var count = this.Count;
if (count != other.Count) return false; if (count != other.Count) return false;
if (count == 0) return true; if (count == 0) return true;
if (count == 1) if (count == 1)
{ {
return string.Equals(First, other.First, StringComparison.Ordinal); return string.Equals(this.First, other.First, StringComparison.Ordinal);
} }
var a = ToArray(); var a = this.ToArray();
var b = other.ToArray(); var b = other.ToArray();
for (int i = 0; i < a.Length; i++) for (var i = 0; i < a.Length; i++)
{ {
if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false; if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false;
} }
@@ -202,31 +205,31 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// <summary> /// <summary>
/// 判断相等。 /// 判断相等。
/// </summary> /// </summary>
public override bool Equals(object obj) => obj is TextValues v && Equals(v); public override bool Equals(object obj) => obj is TextValues v && this.Equals(v);
/// <summary> /// <summary>
/// 获取枚举器。 /// 获取枚举器。
/// </summary> /// </summary>
public Enumerator GetEnumerator() => new Enumerator(m_values); public Enumerator GetEnumerator() => new Enumerator(this.m_values);
IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator(); IEnumerator<string> IEnumerable<string>.GetEnumerator() => this.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <summary> /// <summary>
/// 获取哈希码。 /// 获取哈希码。
/// </summary> /// </summary>
public override int GetHashCode() public override int GetHashCode()
{ {
object value = m_values; var value = this.m_values;
if (value is null) return 0; if (value is null) return 0;
if (value is string s) if (value is string s)
{ {
return s?.GetHashCode() ?? 0; return s?.GetHashCode() ?? 0;
} }
var arr = Unsafe.As<string[]>(value); var arr = Unsafe.As<string[]>(value);
int hash = 17; var hash = 17;
for (int i = 0; i < arr.Length; i++) for (var i = 0; i < arr.Length; i++)
{ {
hash = hash * 31 + (arr[i]?.GetHashCode() ?? 0); hash = hash * 31 + (arr[i]?.GetHashCode() ?? 0);
} }
@@ -238,10 +241,10 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public string[] ToArray() public string[] ToArray()
{ {
object value = m_values; var value = this.m_values;
if (value is null) if (value is null)
{ {
return Array.Empty<string>(); return [];
} }
if (value is string s) if (value is string s)
{ {
@@ -255,7 +258,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public override string ToString() public override string ToString()
{ {
object value = m_values; var value = this.m_values;
if (value is null) if (value is null)
{ {
return string.Empty; return string.Empty;
@@ -265,8 +268,16 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
return s ?? string.Empty; return s ?? string.Empty;
} }
var arr = Unsafe.As<string[]>(value); var arr = Unsafe.As<string[]>(value);
if (arr.Length == 0) return string.Empty; if (arr.Length == 0)
if (arr.Length == 1) return arr[0] ?? string.Empty; {
return string.Empty;
}
if (arr.Length == 1)
{
return arr[0] ?? string.Empty;
}
return string.Join(",", arr); return string.Join(",", arr);
} }
@@ -281,22 +292,22 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
internal Enumerator(object values) internal Enumerator(object values)
{ {
m_values = values; this.m_values = values;
m_index = -1; this.m_index = -1;
m_current = null; this.m_current = null;
} }
/// <summary> /// <summary>
/// 当前值。 /// 当前值。
/// </summary> /// </summary>
public string Current => m_current; public readonly string Current => this.m_current;
object IEnumerator.Current => Current; readonly object IEnumerator.Current => this.Current;
/// <summary> /// <summary>
/// 释放资源。 /// 释放资源。
/// </summary> /// </summary>
public void Dispose() public readonly void Dispose()
{ {
} }
@@ -305,26 +316,26 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public bool MoveNext() public bool MoveNext()
{ {
if (m_values is null) if (this.m_values is null)
{ {
return false; return false;
} }
if (m_values is string s) if (this.m_values is string s)
{ {
if (m_index < 0) if (this.m_index < 0)
{ {
m_index = 0; this.m_index = 0;
m_current = s; this.m_current = s;
return true; return true;
} }
return false; return false;
} }
var arr = Unsafe.As<string[]>(m_values); var arr = Unsafe.As<string[]>(this.m_values);
int next = m_index + 1; var next = this.m_index + 1;
if ((uint)next < (uint)arr.Length) if ((uint)next < (uint)arr.Length)
{ {
m_index = next; this.m_index = next;
m_current = arr[next]; this.m_current = arr[next];
return true; return true;
} }
return false; return false;
@@ -335,8 +346,8 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
m_index = -1; this.m_index = -1;
m_current = null; this.m_current = null;
} }
} }

View File

@@ -10,6 +10,7 @@
// 感谢您的下载和使用 // 感谢您的下载和使用
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -37,6 +38,11 @@ public abstract class HttpBase : IRequestInfo
private readonly InternalHttpHeader m_headers = new InternalHttpHeader(); private readonly InternalHttpHeader m_headers = new InternalHttpHeader();
private readonly StringPool m_stringPool = new StringPool(Encoding.UTF8,32);
private bool m_isChunk;
private long m_contentLength;
/// <summary> /// <summary>
/// 可接受MIME类型 /// 可接受MIME类型
/// </summary> /// </summary>
@@ -70,13 +76,10 @@ public abstract class HttpBase : IRequestInfo
/// </summary> /// </summary>
public bool IsChunk public bool IsChunk
{ {
get get => this.m_isChunk;
{
var transferEncoding = this.Headers.Get(HttpHeaders.TransferEncoding);
return "chunked".Equals(transferEncoding, StringComparison.OrdinalIgnoreCase);
}
set set
{ {
this.m_isChunk = value;
if (value) if (value)
{ {
this.Headers.Add(HttpHeaders.TransferEncoding, "chunked"); this.Headers.Add(HttpHeaders.TransferEncoding, "chunked");
@@ -93,12 +96,12 @@ public abstract class HttpBase : IRequestInfo
/// </summary> /// </summary>
public long ContentLength public long ContentLength
{ {
get get => this.m_contentLength;
set
{ {
var contentLength = this.m_headers.Get(HttpHeaders.ContentLength); this.m_contentLength = value;
return contentLength.IsEmpty ? 0 : long.TryParse(contentLength, out var value) ? value : 0; this.m_headers.Add(HttpHeaders.ContentLength, value.ToString());
} }
set => this.m_headers.Add(HttpHeaders.ContentLength, value.ToString());
} }
/// <summary> /// <summary>
@@ -133,7 +136,8 @@ public abstract class HttpBase : IRequestInfo
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool ParsingHeader<TReader>(ref TReader reader) where TReader : IBytesReader internal bool ParsingHeader<TReader>(ref TReader reader) where TReader : IBytesReader
{ {
var index = ReaderExtension.IndexOf(ref reader, TouchSocketHttpUtility.CRLFCRLF); var sequence = reader.Sequence;
var index = sequence.IndexOf(TouchSocketHttpUtility.CRLFCRLF);
if (index < 0) if (index < 0)
{ {
@@ -150,10 +154,21 @@ public abstract class HttpBase : IRequestInfo
} }
// 一次性获取头部数据减少多次调用GetSpan的开销 // 一次性获取头部数据减少多次调用GetSpan的开销
var headerSpan = reader.GetSpan(headerLength); if (headerLength < 1024)
{
Span<byte> headerSpan = stackalloc byte[headerLength];
sequence.Slice(0, headerLength).CopyTo(headerSpan);
// 调用优化后的头部解析方法 // 调用优化后的头部解析方法
this.ReadHeadersOptimized(headerSpan); this.ReadHeadersOptimized(headerSpan);
}
else
{
var headerSpan = reader.GetSpan(headerLength);
// 调用优化后的头部解析方法
this.ReadHeadersOptimized(headerSpan);
}
// 跳过头部数据和 \r\n\r\n 分隔符 // 跳过头部数据和 \r\n\r\n 分隔符
reader.Advance(totalRequired); reader.Advance(totalRequired);
@@ -164,6 +179,8 @@ public abstract class HttpBase : IRequestInfo
{ {
this.m_headers.Clear(); this.m_headers.Clear();
this.ContentStatus = ContentCompletionStatus.Unknown; this.ContentStatus = ContentCompletionStatus.Unknown;
this.m_isChunk = false;
this.m_contentLength = 0;
} }
/// <summary> /// <summary>
@@ -178,10 +195,10 @@ public abstract class HttpBase : IRequestInfo
var colonIndex = line.IndexOf(TouchSocketHttpUtility.COLON); var colonIndex = line.IndexOf(TouchSocketHttpUtility.COLON);
if (colonIndex <= 0) if (colonIndex <= 0)
{ {
return; // 无效格式,冒号必须存在且不能在第一位 return;
} }
var keySpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(0, colonIndex)); var keySpan = line.Slice(0, colonIndex);
var valueSpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(colonIndex + 1)); var valueSpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(colonIndex + 1));
if (keySpan.IsEmpty || valueSpan.IsEmpty) if (keySpan.IsEmpty || valueSpan.IsEmpty)
@@ -189,47 +206,164 @@ public abstract class HttpBase : IRequestInfo
return; return;
} }
var key = keySpan.ToString(Encoding.UTF8).ToLowerInvariant(); if (keySpan.Length == 14 && EqualsIgnoreCaseAscii(keySpan, "Content-Length"u8))
// 检测是否包含英文逗号,按需分割
if (valueSpan.IndexOf((byte)',') < 0)
{ {
var value = valueSpan.ToString(Encoding.UTF8); if (TryParseLong(valueSpan, out var length))
this.m_headers.Add(key, value); {
this.m_contentLength = length;
}
var value = this.m_stringPool.Get(valueSpan);
this.m_headers.AddInternal(HttpHeaders.ContentLength, value);
return; return;
} }
// 多值处理 if (keySpan.Length == 17 && EqualsIgnoreCaseAscii(keySpan, "Transfer-Encoding"u8))
var values = new List<string>(); {
int start = 0; this.m_isChunk = EqualsIgnoreCaseAscii(valueSpan, "chunked"u8);
var value = this.m_stringPool.Get(valueSpan);
this.m_headers.AddInternal(HttpHeaders.TransferEncoding, value);
return;
}
var key = this.m_stringPool.Get(keySpan);
var commaCount = 0;
for (var i = 0; i < valueSpan.Length; i++)
{
if (valueSpan[i] == (byte)',')
{
commaCount++;
}
}
if (commaCount == 0)
{
var value = this.m_stringPool.Get(valueSpan);
this.m_headers.AddInternal(key, value);
return;
}
var values = new string[commaCount + 1];
var valueIndex = 0;
var start = 0;
while (start < valueSpan.Length) while (start < valueSpan.Length)
{ {
int commaIndex = valueSpan.Slice(start).IndexOf((byte)','); var nextComma = valueSpan.Slice(start).IndexOf((byte)',');
ReadOnlySpan<byte> segment; ReadOnlySpan<byte> segment;
if (commaIndex >= 0)
if (nextComma >= 0)
{ {
segment = valueSpan.Slice(start, commaIndex); segment = valueSpan.Slice(start, nextComma);
start += commaIndex + 1; start += nextComma + 1;
} }
else else
{ {
segment = valueSpan.Slice(start); segment = valueSpan.Slice(start);
start = valueSpan.Length; start = valueSpan.Length;
} }
segment = TouchSocketHttpUtility.TrimWhitespace(segment); segment = TouchSocketHttpUtility.TrimWhitespace(segment);
if (!segment.IsEmpty) if (!segment.IsEmpty)
{ {
values.Add(segment.ToString(Encoding.UTF8)); values[valueIndex++] = this.m_stringPool.Get(segment);
} }
} }
if (values.Count == 1)
if (valueIndex == 0)
{ {
this.m_headers.Add(key, values[0]); return;
} }
else if (values.Count > 1)
if (valueIndex == 1)
{ {
this.m_headers.Add(key, values.ToArray()); this.m_headers.AddInternal(key, values[0]);
} }
else if (valueIndex < values.Length)
{
var trimmedValues = new string[valueIndex];
Array.Copy(values, trimmedValues, valueIndex);
this.m_headers.AddInternal(key, trimmedValues);
}
else
{
this.m_headers.AddInternal(key, values);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool EqualsIgnoreCaseAscii(ReadOnlySpan<byte> span1, ReadOnlySpan<byte> span2)
{
if (span1.Length != span2.Length)
{
return false;
}
for (var i = 0; i < span1.Length; i++)
{
var c1 = span1[i];
var c2 = span2[i];
if (c1 == c2)
{
continue;
}
if ((uint)(c1 - 'A') <= 'Z' - 'A')
{
c1 = (byte)(c1 | 0x20);
}
if ((uint)(c2 - 'A') <= 'Z' - 'A')
{
c2 = (byte)(c2 | 0x20);
}
if (c1 != c2)
{
return false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryParseLong(ReadOnlySpan<byte> span, out long result)
{
result = 0;
if (span.IsEmpty)
{
return false;
}
var sign = 1;
var index = 0;
if (span[0] == (byte)'-' || span[0] == (byte)'+')
{
sign = span[0] == (byte)'-' ? -1 : 1;
index++;
if (index >= span.Length)
{
return false;
}
}
for (var i = index; i < span.Length; i++)
{
var c = span[i];
if (c < (byte)'0' || c > (byte)'9')
{
return false;
}
var digit = c - '0';
result = result * 10 + digit;
}
result *= sign;
return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -10,6 +10,8 @@
// 感谢您的下载和使用 // 感谢您的下载和使用
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
namespace TouchSocket.Http; namespace TouchSocket.Http;
/// <summary> /// <summary>
/// 请求头静态类 /// 请求头静态类
@@ -280,328 +282,167 @@ public static class HttpHeaders
/// Content-Disposition /// Content-Disposition
/// </summary> /// </summary>
public const string ContentDisposition = "Content-Disposition"; public const string ContentDisposition = "Content-Disposition";
internal static bool IsPredefinedHeader(string key)
{
var keyLength = key.Length;
if (keyLength == 0)
{
return false;
} }
///// <summary> var firstChar = char.ToLowerInvariant(key[0]);
///// 请求头枚举
///// </summary>
//public enum HttpHeaders : byte
//{
// /// <summary>
// /// Cache-Control 标头,指定请求/响应链上所有缓存控制机制必须服从的指令。
// /// </summary>
// [Description("Cache-Control")]
// CacheControl = 0,
// /// <summary> return firstChar switch
// /// Connection 标头,指定特定连接需要的选项。 {
// /// </summary> 'a' => IsAGroupHeader(key, keyLength),
// [Description("Connection")] 'c' => IsCGroupHeader(key, keyLength),
// Connection = 1, 'd' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.Date),
'e' => IsEGroupHeader(key, keyLength),
'f' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.From),
'h' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.Host),
'i' => IsIGroupHeader(key, keyLength),
'k' => keyLength == 9 && ReferenceEquals(key, HttpHeaders.KeepAlive),
'l' => keyLength == 8 && ReferenceEquals(key, HttpHeaders.Location),
'm' => keyLength == 11 && ReferenceEquals(key, HttpHeaders.MaxForwards),
'o' => keyLength == 6 && ReferenceEquals(key, HttpHeaders.Origin),
'p' => IsPGroupHeader(key, keyLength),
'r' => IsRGroupHeader(key, keyLength),
's' => IsSGroupHeader(key, keyLength),
't' => IsTGroupHeader(key, keyLength),
'u' => keyLength == 10 && ReferenceEquals(key, HttpHeaders.UserAgent),
'v' => IsVGroupHeader(key, keyLength),
'w' => keyLength == 13 && ReferenceEquals(key, HttpHeaders.WwwAuthenticate),
_ => false
};
}
// /// <summary> #region Header判断
// /// Date 标头,指定开始创建请求的日期和时间。 [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// </summary> private static bool IsAGroupHeader(string key, int keyLength)
// [Description("Date")] {
// Date = 2, // 预定义长度Age(3)、Allow(5)、Accept(6)、Accept-Ranges(12)、Accept-Charset/Authorization(13)、Accept-Encoding(14)、Accept-Language(15)
return keyLength switch
{
3 => ReferenceEquals(key, HttpHeaders.Age),
5 => ReferenceEquals(key, HttpHeaders.Allow),
6 => ReferenceEquals(key, HttpHeaders.Accept),
12 => ReferenceEquals(key, HttpHeaders.AcceptRanges),
13 => ReferenceEquals(key, HttpHeaders.AcceptCharset) || ReferenceEquals(key, HttpHeaders.Authorization),
14 => ReferenceEquals(key, HttpHeaders.AcceptEncoding),
15 => ReferenceEquals(key, HttpHeaders.AcceptLanguage),
_ => false // 长度不匹配直接返回false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Keep-Alive 标头,指定用以维护持久性连接的参数。 private static bool IsCGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Keep-Alive")] // 预定义长度Cookie(6)、Connection/Content-MD5(10)、Content-Type(12)、Cache-Control/Content-Range(13)、Content-Length(14)、Content-Encoding(15)、Content-Language/Content-Location(16)、Content-Disposition(19)
// KeepAlive = 3, return keyLength switch
{
6 => ReferenceEquals(key, HttpHeaders.Cookie),
10 => ReferenceEquals(key, HttpHeaders.Connection) || ReferenceEquals(key, HttpHeaders.ContentMd5),
12 => ReferenceEquals(key, HttpHeaders.ContentType),
13 => ReferenceEquals(key, HttpHeaders.CacheControl) || ReferenceEquals(key, HttpHeaders.ContentRange),
14 => ReferenceEquals(key, HttpHeaders.ContentLength),
15 => ReferenceEquals(key, HttpHeaders.ContentEncoding),
16 => ReferenceEquals(key, HttpHeaders.ContentLanguage) || ReferenceEquals(key, HttpHeaders.ContentLocation),
19 => ReferenceEquals(key, HttpHeaders.ContentDisposition),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Pragma 标头,指定可应用于请求/响应链上的任何代理的特定于实现的指令。 private static bool IsEGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Pragma")] // 预定义长度ETag(4)、Expect(6)、Expires(7)
// Pragma = 4, return keyLength switch
{
4 => ReferenceEquals(key, HttpHeaders.ETag),
6 => ReferenceEquals(key, HttpHeaders.Expect),
7 => ReferenceEquals(key, HttpHeaders.Expires),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Trailer 标头,指定标头字段显示在以 chunked 传输编码方式编码的消息的尾部。 private static bool IsIGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Trailer")] // 预定义长度If-Match/If-Range(7)、If-None-Match(11)、If-Modified-Since(15)、If-Unmodified-Since(18)
// Trailer = 5, return keyLength switch
{
7 => ReferenceEquals(key, HttpHeaders.IfMatch) || ReferenceEquals(key, HttpHeaders.IfRange),
11 => ReferenceEquals(key, HttpHeaders.IfNoneMatch),
15 => ReferenceEquals(key, HttpHeaders.IfModifiedSince),
18 => ReferenceEquals(key, HttpHeaders.IfUnmodifiedSince),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Transfer-Encoding 标头,指定对消息正文应用的转换的类型(如果有)。 private static bool IsPGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Transfer-Encoding")] // 预定义长度Pragma(6)、Proxy-Authorization/Proxy-Authenticate(16)
// TransferEncoding = 6, return keyLength switch
{
6 => ReferenceEquals(key, HttpHeaders.Pragma),
16 => ReferenceEquals(key, HttpHeaders.ProxyAuthorization) || ReferenceEquals(key, HttpHeaders.ProxyAuthenticate),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Upgrade 标头,指定客户端支持的附加通信协议。 private static bool IsRGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Upgrade")] // 预定义长度Range(5)、Referer(6)、Retry-After(10)
// Upgrade = 7, return keyLength switch
{
5 => ReferenceEquals(key, HttpHeaders.Range),
6 => ReferenceEquals(key, HttpHeaders.Referer),
10 => ReferenceEquals(key, HttpHeaders.RetryAfter),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Via 标头,指定网关和代理程序要使用的中间协议。 private static bool IsSGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Via")] // 预定义长度Server(6)、Set-Cookie(8)
// Via = 8, return keyLength switch
{
6 => ReferenceEquals(key, HttpHeaders.Server),
8 => ReferenceEquals(key, HttpHeaders.SetCookie),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Warning 标头,指定关于可能未在消息中反映的消息的状态或转换的附加信息。 private static bool IsTGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Warning")] // 预定义长度TE(2)、Trailer(7)、Translate(8)、Transfer-Encoding(16)
// Warning = 9, return keyLength switch
{
2 => ReferenceEquals(key, HttpHeaders.Te),
7 => ReferenceEquals(key, HttpHeaders.Trailer),
8 => ReferenceEquals(key, HttpHeaders.Translate),
16 => ReferenceEquals(key, HttpHeaders.TransferEncoding),
_ => false
};
}
// /// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
// /// Allow 标头,指定支持的 HTTP 方法集。 private static bool IsVGroupHeader(string key, int keyLength)
// /// </summary> {
// [Description("Allow")] // 预定义长度Via(3)、Vary(4)、Warning(7)
// Allow = 10, return keyLength switch
{
3 => ReferenceEquals(key, HttpHeaders.Via),
4 => ReferenceEquals(key, HttpHeaders.Vary),
7 => ReferenceEquals(key, HttpHeaders.Warning),
_ => false
};
}
#endregion
// /// <summary> }
// /// Content-Length 标头,指定伴随正文数据的长度(以字节为单位)。
// /// </summary>
// [Description("Content-Length")]
// ContentLength = 11,
// /// <summary>
// /// Content-Type 标头,指定伴随正文数据的 MIME 类型。
// /// </summary>
// [Description("Content-Type")]
// ContentType = 12,
// /// <summary>
// /// Content-Encoding 标头,指定已应用于伴随正文数据的编码。
// /// </summary>
// [Description("Content-Encoding")]
// ContentEncoding = 13,
// /// <summary>
// /// Content-Langauge 标头,指定伴随正文数据的自然语言。
// /// </summary>
// [Description("Content-Langauge")]
// ContentLanguage = 14,
// /// <summary>
// /// Content-Location 标头,指定可从其中获得伴随正文的 URI。
// /// </summary>
// [Description("Content-Location")]
// ContentLocation = 15,
// /// <summary>
// /// Content-MD5 标头,指定伴随正文数据的 MD5 摘要,用于提供端到端消息完整性检查。
// /// </summary>
// [Description("Content-MD5")]
// ContentMd5 = 16,
// /// <summary>
// /// Content-Range 标头,指定在完整正文中应用伴随部分正文数据的位置。
// /// </summary>
// [Description("Content-Range")]
// ContentRange = 17,
// /// <summary>
// /// Expires 标头,指定日期和时间,在此之后伴随的正文数据应视为陈旧的。
// /// </summary>
// [Description("Expires")]
// Expires = 18,
// /// <summary>
// /// Last-Modified 标头,指定上次修改伴随的正文数据的日期和时间。
// /// </summary>
// [Description("Last-Modified")]
// LastModified = 19,
// /// <summary>
// /// Accept 标头,指定响应可接受的 MIME 类型。
// /// </summary>
// [Description("Accept")]
// Accept = 20,
// /// <summary>
// /// Accept-Charset 标头,指定响应可接受的字符集。
// /// </summary>
// [Description("Accept-Charset")]
// AcceptCharset = 21,
// /// <summary>
// /// Accept-Encoding 标头,指定响应可接受的内容编码。
// /// </summary>
// [Description("Accept-Encoding")]
// AcceptEncoding = 22,
// /// <summary>
// /// Accept-Langauge 标头,指定响应首选的自然语言。
// /// </summary>
// [Description("Accept-Langauge")]
// AcceptLanguage = 23,
// /// <summary>
// /// Authorization 标头,指定客户端为向服务器验证自身身份而出示的凭据。
// /// </summary>
// [Description("Authorization")]
// Authorization = 24,
// /// <summary>
// /// Cookie 标头,指定向服务器提供的 Cookie 数据。
// /// </summary>
// [Description("Cookie")]
// Cookie = 25,
// /// <summary>
// /// Expect 标头,指定客户端要求的特定服务器行为。
// /// </summary>
// [Description("Expect")]
// Expect = 26,
// /// <summary>
// /// From 标头,指定控制请求用户代理的用户的 Internet 电子邮件地址。
// /// </summary>
// [Description("From")]
// From = 27,
// /// <summary>
// /// Host 标头,指定所请求资源的主机名和端口号。
// /// </summary>
// [Description("Host")]
// Host = 28,
// /// <summary>
// /// If-Match 标头,指定仅当客户端的指示资源的缓存副本是最新的时,才执行请求的操作。
// /// </summary>
// [Description("If-Match")]
// IfMatch = 29,
// /// <summary>
// /// If-Modified-Since 标头,指定仅当自指示的数据和时间之后修改了请求的资源时,才执行请求的操作。
// /// </summary>
// [Description("If-Modified-Since")]
// IfModifiedSince = 30,
// /// <summary>
// /// If-None-Match 标头,指定仅当客户端的指示资源的缓存副本都不是最新的时,才执行请求的操作。
// /// </summary>
// [Description("If-None-Match")]
// IfNoneMatch = 31,
// /// <summary>
// /// If-Range 标头,指定如果客户端的缓存副本是最新的,仅发送指定范围的请求资源。
// /// </summary>
// [Description("If-Range")]
// IfRange = 32,
// /// <summary>
// /// If-Unmodified-Since 标头,指定仅当自指示的日期和时间之后修改了请求的资源时,才执行请求的操作。
// /// </summary>
// [Description("If-Unmodified-Since")]
// IfUnmodifiedSince = 33,
// /// <summary>
// /// Max-Forwards 标头,指定一个整数,表示此请求还可转发的次数。
// /// </summary>
// [Description("Max-Forwards")]
// MaxForwards = 34,
// /// <summary>
// /// Proxy-Authorization 标头,指定客户端为向代理验证自身身份而出示的凭据。
// /// </summary>
// [Description("Proxy-Authorization")]
// ProxyAuthorization = 35,
// /// <summary>
// /// Referer 标头,指定从中获得请求 URI 的资源的 URI。
// /// </summary>
// [Description("Referer")]
// Referer = 36,
// /// <summary>
// /// Range 标头,指定代替整个响应返回的客户端请求的响应的子范围。
// /// </summary>
// [Description("Range")]
// Range = 37,
// /// <summary>
// /// TE 标头,指定响应可接受的传输编码方式。
// /// </summary>
// [Description("TE")]
// Te = 38,
// /// <summary>
// /// Translate 标头,与 WebDAV 功能一起使用的 HTTP 规范的 Microsoft 扩展。
// /// </summary>
// [Description("Translate")]
// Translate = 39,
// /// <summary>
// /// User-Agent 标头,指定有关客户端代理的信息。
// /// </summary>
// [Description("User-Agent")]
// UserAgent = 40,
// /// <summary>
// /// Accept-Ranges 标头,指定服务器接受的范围。
// /// </summary>
// [Description("Accept-Ranges")]
// AcceptRanges = 41,
// /// <summary>
// /// Age 标头,指定自起始服务器生成响应以来的时间长度(以秒为单位)。
// /// </summary>
// [Description("Age")]
// Age = 42,
// /// <summary>
// /// Etag 标头,指定请求的变量的当前值。
// /// </summary>
// [Description("Etag")]
// ETag = 43,
// /// <summary>
// /// Location 标头,指定为获取请求的资源而将客户端重定向到的 URI。
// /// </summary>
// [Description("Location")]
// Location = 44,
// /// <summary>
// /// Proxy-Authenticate 标头,指定客户端必须对代理验证其自身。
// /// </summary>
// [Description("Proxy-Authenticate")]
// ProxyAuthenticate = 45,
// /// <summary>
// /// Retry-After 标头,指定某个时间(以秒为单位)或日期和时间,在此时间之后客户端可以重试其请求。
// /// </summary>
// [Description("Retry-After")]
// RetryAfter = 46,
// /// <summary>
// /// Server 标头,指定关于起始服务器代理的信息。
// /// </summary>
// [Description("Server")]
// Server = 47,
// /// <summary>
// /// Set-Cookie 标头,指定提供给客户端的 Cookie 数据。
// /// </summary>
// [Description("Set-Cookie")]
// SetCookie = 48,
// /// <summary>
// /// Vary 标头,指定用于确定缓存的响应是否为新响应的请求标头。
// /// </summary>
// [Description("Vary")]
// Vary = 49,
// /// <summary>
// /// WWW-Authenticate 标头,指定客户端必须对服务器验证其自身。
// /// </summary>
// [Description("WWW-Authenticate")]
// WwwAuthenticate = 50,
// /// <summary>
// /// Origin。
// /// </summary>
// [Description("Origin")]
// Origin = 51,
// /// <summary>
// /// Content-Disposition
// /// </summary>
// [Description("Content-Disposition")]
// ContentDisposition = 52
//}

View File

@@ -193,28 +193,26 @@ public class HttpRequest : HttpBase
{ {
var start = 0; var start = 0;
// 解析 HTTP Method (GET/POST)
var methodEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start); var methodEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start);
if (methodEnd == -1) if (methodEnd == -1)
{ {
throw new Exception("Invalid HTTP request line: " + requestLineSpan.ToString(Encoding.UTF8)); throw new Exception("Invalid HTTP request line: " + requestLineSpan.ToString(Encoding.UTF8));
} }
this.Method = new HttpMethod(requestLineSpan.Slice(start, methodEnd - start).ToString(Encoding.UTF8)); var methodSpan = requestLineSpan.Slice(start, methodEnd - start);
this.Method = ParseHttpMethodFast(methodSpan);
start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, methodEnd + 1); start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, methodEnd + 1);
// 解析 URL
var urlEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start); var urlEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start);
if (urlEnd == -1) if (urlEnd == -1)
{ {
this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start)); this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start));
return; // No protocol version return;
} }
this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start, urlEnd - start)); this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start, urlEnd - start));
start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, urlEnd + 1); start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, urlEnd + 1);
// 解析 Protocol (HTTP/1.1)
var protocolSpan = requestLineSpan.Slice(start); var protocolSpan = requestLineSpan.Slice(start);
var slashIndex = protocolSpan.IndexOf((byte)'/'); var slashIndex = protocolSpan.IndexOf((byte)'/');
if (slashIndex > 0 && slashIndex < protocolSpan.Length - 1) if (slashIndex > 0 && slashIndex < protocolSpan.Length - 1)
@@ -224,6 +222,53 @@ public class HttpRequest : HttpBase
} }
} }
private static HttpMethod ParseHttpMethodFast(ReadOnlySpan<byte> span)
{
switch (span.Length)
{
case 3:
if (EqualsIgnoreCaseAscii(span, "GET")) return HttpMethod.Get;
if (EqualsIgnoreCaseAscii(span, "PUT")) return HttpMethod.Put;
break;
case 4:
if (EqualsIgnoreCaseAscii(span, "POST")) return HttpMethod.Post;
break;
case 6:
if (EqualsIgnoreCaseAscii(span, "DELETE")) return HttpMethod.Delete;
break;
case 7:
if (EqualsIgnoreCaseAscii(span, "CONNECT")) return HttpMethod.Connect;
break;
}
return new HttpMethod(span.ToString(Encoding.UTF8));
}
private static bool EqualsIgnoreCaseAscii(ReadOnlySpan<byte> span, string token)
{
if (span.Length != token.Length)
{
return false;
}
for (var i = 0; i < span.Length; i++)
{
var b = span[i];
if (b >= (byte)'a' && b <= (byte)'z')
{
b = (byte)(b - 32);
}
var c = (byte)token[i];
if (c >= (byte)'a' && c <= (byte)'z')
{
c = (byte)(c - 32);
}
if (b != c)
{
return false;
}
}
return true;
}
private static void GetParameters(ReadOnlySpan<char> querySpan, InternalHttpParams parameters) private static void GetParameters(ReadOnlySpan<char> querySpan, InternalHttpParams parameters)
{ {
while (!querySpan.IsEmpty) while (!querySpan.IsEmpty)

View File

@@ -101,9 +101,9 @@ public abstract class HttpResponse : HttpBase
} }
else else
{ {
content.InternalTryComputeLength(out var contentLength);
var writer = new PipeBytesWriter(transport.Writer);
content.InternalBuildingHeader(this.Headers); content.InternalBuildingHeader(this.Headers);
var writer = new PipeBytesWriter(transport.Writer);
this.BuildHeader(ref writer); this.BuildHeader(ref writer);
var result = content.InternalBuildingContent(ref writer); var result = content.InternalBuildingContent(ref writer);
@@ -213,7 +213,6 @@ public abstract class HttpResponse : HttpBase
this.m_sentHeader = false; this.m_sentHeader = false;
this.m_sentLength = 0; this.m_sentLength = 0;
this.Responsed = false; this.Responsed = false;
this.IsChunk = false;
this.StatusCode = 200; this.StatusCode = 200;
this.StatusMessage = "Success"; this.StatusMessage = "Success";
this.Content = default; this.Content = default;
@@ -305,12 +304,24 @@ public abstract class HttpResponse : HttpBase
} }
private void BuildHeader<TWriter>(ref TWriter writer) where TWriter : IBytesWriter private void BuildHeader<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{
var versionBytes = TouchSocketHttpUtility.GetHttpVersionBytes(this.ProtocolVersion);
if (!versionBytes.IsEmpty)
{
writer.Write(versionBytes);
}
else
{ {
TouchSocketHttpUtility.AppendHTTP(ref writer); TouchSocketHttpUtility.AppendHTTP(ref writer);
TouchSocketHttpUtility.AppendSlash(ref writer); TouchSocketHttpUtility.AppendSlash(ref writer);
TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion); TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion);
}
TouchSocketHttpUtility.AppendSpace(ref writer); TouchSocketHttpUtility.AppendSpace(ref writer);
TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusCode.ToString());
var statusCodeBytes = TouchSocketHttpUtility.GetStatusCodeBytes(this.StatusCode);
writer.Write(statusCodeBytes);
TouchSocketHttpUtility.AppendSpace(ref writer); TouchSocketHttpUtility.AppendSpace(ref writer);
TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusMessage); TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusMessage);
TouchSocketHttpUtility.AppendRn(ref writer); TouchSocketHttpUtility.AppendRn(ref writer);
@@ -322,11 +333,9 @@ public abstract class HttpResponse : HttpBase
TouchSocketHttpUtility.AppendSpace(ref writer); TouchSocketHttpUtility.AppendSpace(ref writer);
TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Value); TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Value);
TouchSocketHttpUtility.AppendRn(ref writer); TouchSocketHttpUtility.AppendRn(ref writer);
} }
TouchSocketHttpUtility.AppendRn(ref writer); TouchSocketHttpUtility.AppendRn(ref writer);
} }
private ITransport GetITransport() private ITransport GetITransport()

View File

@@ -14,11 +14,46 @@ namespace TouchSocket.Http;
internal abstract class InternalHttpCollection : IDictionary<string, TextValues> internal abstract class InternalHttpCollection : IDictionary<string, TextValues>
{ {
private readonly IEqualityComparer<string> m_comparer;
private readonly Dictionary<string, TextValues> m_dictionary; private readonly Dictionary<string, TextValues> m_dictionary;
private readonly List<KeyValuePair<string, TextValues>> m_pendingItems = new List<KeyValuePair<string, TextValues>>();
private bool m_hasDuplicateKeys;
private bool m_hasNonPredefinedKeys;
private bool m_isDictionaryBuilt;
protected InternalHttpCollection(IEqualityComparer<string> comparer = null) protected InternalHttpCollection(IEqualityComparer<string> comparer = null)
{ {
m_dictionary = new Dictionary<string, TextValues>(comparer ?? StringComparer.OrdinalIgnoreCase); this.m_comparer = comparer ?? StringComparer.OrdinalIgnoreCase;
this.m_dictionary = new Dictionary<string, TextValues>(this.m_comparer);
}
public int Count
{
get
{
this.EnsureDictionaryBuilt();
return this.m_dictionary.Count;
}
}
public bool IsReadOnly => false;
public ICollection<string> Keys
{
get
{
this.EnsureDictionaryBuilt();
return this.m_dictionary.Keys;
}
}
public ICollection<TextValues> Values
{
get
{
this.EnsureDictionaryBuilt();
return this.m_dictionary.Values;
}
} }
public TextValues this[string key] public TextValues this[string key]
@@ -26,85 +61,186 @@ internal abstract class InternalHttpCollection : IDictionary<string, TextValues>
get get
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; this.EnsureDictionaryBuilt();
} return this.m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty;
set
{
ThrowHelper.ThrowIfNull(key, nameof(key));
m_dictionary[key] = value;
}
} }
public ICollection<string> Keys => m_dictionary.Keys; set => this.Add(key, value);
}
public ICollection<TextValues> Values => m_dictionary.Values;
public int Count => m_dictionary.Count;
public bool IsReadOnly => false;
public void Add(string key, TextValues value) public void Add(string key, TextValues value)
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
if (m_dictionary.TryGetValue(key, out var old))
if (this.m_isDictionaryBuilt)
{ {
m_dictionary[key] = Merge(old, value); if (this.m_dictionary.TryGetValue(key, out var old))
{
this.m_dictionary[key] = Merge(old, value);
} }
else else
{ {
m_dictionary.Add(key, value); this.m_dictionary.Add(key, value);
} }
} }
else
{
if (!this.m_hasNonPredefinedKeys && !HttpHeaders.IsPredefinedHeader(key))
{
this.m_hasNonPredefinedKeys = true;
}
if (!this.m_hasDuplicateKeys && this.m_pendingItems.Count > 0)
{
for (var i = 0; i < this.m_pendingItems.Count; i++)
{
if (this.m_comparer.Equals(this.m_pendingItems[i].Key, key))
{
this.m_hasDuplicateKeys = true;
break;
}
}
}
this.m_pendingItems.Add(new KeyValuePair<string, TextValues>(key, value));
}
}
public void Add(KeyValuePair<string, TextValues> item) => this.Add(item.Key, item.Value);
public void Clear()
{
this.m_dictionary.Clear();
this.m_pendingItems.Clear();
this.m_hasDuplicateKeys = false;
this.m_hasNonPredefinedKeys = false;
this.m_isDictionaryBuilt = false;
}
public bool Contains(KeyValuePair<string, TextValues> item)
{
this.EnsureDictionaryBuilt();
return ((IDictionary<string, TextValues>)this.m_dictionary).Contains(item);
}
public bool ContainsKey(string key) public bool ContainsKey(string key)
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
return m_dictionary.ContainsKey(key); this.EnsureDictionaryBuilt();
return this.m_dictionary.ContainsKey(key);
} }
public void CopyTo(KeyValuePair<string, TextValues>[] array, int arrayIndex)
{
this.EnsureDictionaryBuilt();
((IDictionary<string, TextValues>)this.m_dictionary).CopyTo(array, arrayIndex);
}
public TextValues Get(string key)
{
ThrowHelper.ThrowIfNull(key, nameof(key));
this.EnsureDictionaryBuilt();
return this.m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty;
}
public IEnumerator<KeyValuePair<string, TextValues>> GetEnumerator()
{
if (this.m_isDictionaryBuilt)
{
return this.m_dictionary.GetEnumerator();
}
else if (this.m_hasDuplicateKeys || this.m_hasNonPredefinedKeys)
{
this.EnsureDictionaryBuilt();
return this.m_dictionary.GetEnumerator();
}
else
{
return this.m_pendingItems.GetEnumerator();
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => this.GetEnumerator();
public bool Remove(string key) public bool Remove(string key)
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
return m_dictionary.Remove(key); this.EnsureDictionaryBuilt();
return this.m_dictionary.Remove(key);
}
public bool Remove(KeyValuePair<string, TextValues> item)
{
this.EnsureDictionaryBuilt();
return ((IDictionary<string, TextValues>)this.m_dictionary).Remove(item);
} }
public bool TryGetValue(string key, out TextValues value) public bool TryGetValue(string key, out TextValues value)
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
return m_dictionary.TryGetValue(key, out value); this.EnsureDictionaryBuilt();
return this.m_dictionary.TryGetValue(key, out value);
} }
public void Add(KeyValuePair<string, TextValues> item) => Add(item.Key, item.Value); internal void AddInternal(string key, TextValues value)
{
public void Clear() => m_dictionary.Clear(); this.m_pendingItems.Add(new KeyValuePair<string, TextValues>(key, value));
}
public bool Contains(KeyValuePair<string, TextValues> item) => ((IDictionary<string, TextValues>)m_dictionary).Contains(item);
public void CopyTo(KeyValuePair<string, TextValues>[] array, int arrayIndex) => ((IDictionary<string, TextValues>)m_dictionary).CopyTo(array, arrayIndex);
public bool Remove(KeyValuePair<string, TextValues> item) => ((IDictionary<string, TextValues>)m_dictionary).Remove(item);
public IEnumerator<KeyValuePair<string, TextValues>> GetEnumerator() => m_dictionary.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
protected static TextValues Merge(TextValues a, TextValues b) protected static TextValues Merge(TextValues a, TextValues b)
{ {
if (a.IsEmpty) return b; if (a.IsEmpty)
if (b.IsEmpty) return a; {
var ac = a.Count; return b;
var bc = b.Count; }
if (b.IsEmpty)
{
return a;
}
var arrA = a.ToArray(); var arrA = a.ToArray();
var arrB = b.ToArray(); var arrB = b.ToArray();
var ac = arrA.Length;
var bc = arrB.Length;
var newArr = new string[ac + bc]; var newArr = new string[ac + bc];
Array.Copy(arrA, 0, newArr, 0, ac); Array.Copy(arrA, 0, newArr, 0, ac);
Array.Copy(arrB, 0, newArr, ac, bc); Array.Copy(arrB, 0, newArr, ac, bc);
return new TextValues(newArr); return new TextValues(newArr);
} }
public TextValues Get(string key) private void EnsureDictionaryBuilt()
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); if (this.m_isDictionaryBuilt)
return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; {
return;
}
if (!this.m_hasDuplicateKeys && !this.m_hasNonPredefinedKeys && this.m_dictionary.Count == 0)
{
foreach (var item in this.m_pendingItems)
{
this.m_dictionary.Add(item.Key, item.Value);
}
}
else
{
foreach (var item in this.m_pendingItems)
{
if (this.m_dictionary.TryGetValue(item.Key, out var old))
{
this.m_dictionary[item.Key] = Merge(old, item.Value);
}
else
{
this.m_dictionary.Add(item.Key, item.Value);
}
}
}
this.m_pendingItems.Clear();
this.m_hasDuplicateKeys = false;
this.m_hasNonPredefinedKeys = false;
this.m_isDictionaryBuilt = true;
} }
} }

View File

@@ -22,7 +22,7 @@ internal sealed class InternalHttpHeader : InternalHttpCollection, IHttpHeader
{ {
ThrowHelper.ThrowIfNull(key, nameof(key)); ThrowHelper.ThrowIfNull(key, nameof(key));
if (!TryGetValue(key, out var headerValue)) if (!this.TryGetValue(key, out var headerValue))
{ {
return false; return false;
} }

View File

@@ -14,160 +14,119 @@ using System.Runtime.CompilerServices;
namespace TouchSocket.Http; namespace TouchSocket.Http;
/// <summary> static class TouchSocketHttpUtility
/// TouchSocketHttp辅助工具类
/// </summary>
public static class TouchSocketHttpUtility
{ {
public const int MaxReadSize = 1024 * 1024; public const int MaxReadSize = 1024 * 1024;
// HTTP头部解析相关常量
/// <summary>
/// 冒号字节值
/// </summary>
public const byte COLON = (byte)':'; public const byte COLON = (byte)':';
/// <summary>
/// 空格字节值
/// </summary>
public const byte SPACE = (byte)' '; public const byte SPACE = (byte)' ';
/// <summary>
/// 制表符字节值
/// </summary>
public const byte TAB = (byte)'\t'; public const byte TAB = (byte)'\t';
/// <summary>
/// 获取一个只读的字节序列,表示回车换行(CRLF)。
/// </summary>
/// <value>
/// 一个包含回车和换行字节的只读字节序列。
/// </value>
public static ReadOnlySpan<byte> CRLF => "\r\n"u8; public static ReadOnlySpan<byte> CRLF => "\r\n"u8;
/// <summary>
/// 获取一个只读的字节序列,表示双回车换行(CRLFCRLF)。
/// </summary>
/// <value>
/// 一个包含双回车和换行字节的只读字节序列。
/// </value>
public static ReadOnlySpan<byte> CRLFCRLF => "\r\n\r\n"u8; public static ReadOnlySpan<byte> CRLFCRLF => "\r\n\r\n"u8;
/// <summary> private static readonly byte[] s_http11Response = "HTTP/1.1 "u8.ToArray();
/// 在 <see cref="IByteBlock"/> 中追加 "&amp;" 符号。 private static readonly byte[] s_http10Response = "HTTP/1.0 "u8.ToArray();
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam> private static readonly string[] s_statusCodeCache = new string[600];
/// <param name="writer">字节块实例。</param> private static readonly byte[][] s_statusCodeBytesCache = new byte[600][];
static TouchSocketHttpUtility()
{
for (var i = 0; i < 600; i++)
{
s_statusCodeCache[i] = i.ToString();
s_statusCodeBytesCache[i] = Encoding.UTF8.GetBytes(s_statusCodeCache[i]);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ReadOnlySpan<byte> GetStatusCodeBytes(int statusCode)
{
if (statusCode >= 0 && statusCode < 600)
{
return s_statusCodeBytesCache[statusCode];
}
return Encoding.UTF8.GetBytes(statusCode.ToString());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ReadOnlySpan<byte> GetHttpVersionBytes(string version)
{
if (version == "1.1")
{
return s_http11Response.AsSpan(0, 8);
}
if (version == "1.0")
{
return s_http10Response.AsSpan(0, 8);
}
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AppendAnd<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendAnd<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write("&"u8); writer.Write("&"u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加 ":" 符号。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendColon<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendColon<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write(":"u8); writer.Write(":"u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加 "=" 符号。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendEqual<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendEqual<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write("="u8); writer.Write("="u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加 "HTTP" 字符串。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendHTTP<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendHTTP<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write("HTTP"u8); writer.Write("HTTP"u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加 "?" 符号。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendQuestionMark<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendQuestionMark<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write("?"u8); writer.Write("?"u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加回车换行符 "\r\n"。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendRn<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendRn<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write(CRLF); writer.Write(CRLF);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加 "/" 符号。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendSlash<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendSlash<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write("/"u8); writer.Write("/"u8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加空格符。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
public static void AppendSpace<TWriter>(ref TWriter writer) where TWriter : IBytesWriter public static void AppendSpace<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
{ {
writer.Write(StringExtension.DefaultSpaceUtf8Span); writer.Write(StringExtension.DefaultSpaceUtf8Span);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加指定的 UTF-8 编码字符串。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
/// <param name="value">要追加的字符串。</param>
public static void AppendUtf8String<TWriter>(ref TWriter writer, string value) where TWriter : IBytesWriter public static void AppendUtf8String<TWriter>(ref TWriter writer, string value) where TWriter : IBytesWriter
{ {
WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8); WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8);
} }
/// <summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// 在 <see cref="IByteBlock"/> 中追加指定整数的十六进制表示。
/// </summary>
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
/// <param name="writer">字节块实例。</param>
/// <param name="value">要追加的整数值。</param>
public static void AppendHex<TWriter>(ref TWriter writer, int value) where TWriter : IBytesWriter public static void AppendHex<TWriter>(ref TWriter writer, int value) where TWriter : IBytesWriter
{ {
AppendUtf8String(ref writer, $"{value:X}"); AppendUtf8String(ref writer, $"{value:X}");
} }
/// <summary>
/// 检查字节是否为HTTP规范允许的空白字符空格或制表符
/// </summary>
/// <param name="b">要检查的字节</param>
/// <returns>如果是空白字符返回true否则返回false</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsWhitespace(byte b) => b == SPACE || b == TAB; internal static bool IsWhitespace(byte b) => b == SPACE || b == TAB;
/// <summary>
/// 高效的空白字符去除避免使用通用的Trim方法
/// </summary>
/// <param name="span">要处理的字节跨度</param>
/// <returns>去除前后空白字符后的字节跨度</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ReadOnlySpan<byte> TrimWhitespace(ReadOnlySpan<byte> span) internal static ReadOnlySpan<byte> TrimWhitespace(ReadOnlySpan<byte> span)
{ {
@@ -175,25 +134,26 @@ public static class TouchSocketHttpUtility
var end = span.Length - 1; var end = span.Length - 1;
while (start <= end && IsWhitespace(span[start])) while (start <= end && IsWhitespace(span[start]))
{
start++; start++;
}
while (end >= start && IsWhitespace(span[end])) while (end >= start && IsWhitespace(span[end]))
{
end--; end--;
}
return start > end ? ReadOnlySpan<byte>.Empty : span.Slice(start, end - start + 1); return start > end ? [] : span[start..(end + 1)];
} }
internal static string UnescapeDataString(ReadOnlySpan<byte> urlSpan) internal static string UnescapeDataString(ReadOnlySpan<byte> urlSpan)
{ {
#if NET9_0_OR_GREATER #if NET9_0_OR_GREATER
// 直接处理字节的URL解码
Span<char> charBuffer = stackalloc char[urlSpan.Length]; Span<char> charBuffer = stackalloc char[urlSpan.Length];
var charCount = Encoding.UTF8.GetChars(urlSpan, charBuffer); var charCount = Encoding.UTF8.GetChars(urlSpan, charBuffer);
return Uri.UnescapeDataString(charBuffer.Slice(0, charCount)); return Uri.UnescapeDataString(charBuffer.Slice(0, charCount));
#else #else
return Uri.UnescapeDataString(urlSpan.ToString(Encoding.UTF8)); return Uri.UnescapeDataString(urlSpan.ToString(Encoding.UTF8));
#endif #endif
} }
internal static string UnescapeDataString(ReadOnlySpan<char> urlSpan) internal static string UnescapeDataString(ReadOnlySpan<char> urlSpan)
@@ -203,7 +163,6 @@ public static class TouchSocketHttpUtility
#else #else
return Uri.UnescapeDataString(urlSpan.ToString()); return Uri.UnescapeDataString(urlSpan.ToString());
#endif #endif
} }
internal static int FindNextWhitespace(ReadOnlySpan<byte> span, int start) internal static int FindNextWhitespace(ReadOnlySpan<byte> span, int start)
@@ -224,7 +183,6 @@ public static class TouchSocketHttpUtility
{ {
start++; start++;
} }
return start; return start;
} }
@@ -240,7 +198,6 @@ public static class TouchSocketHttpUtility
} }
else else
{ {
// 处理没有值的键
keySpan = kvSpan; keySpan = kvSpan;
valueSpan = []; valueSpan = [];
} }

View File

@@ -210,7 +210,6 @@ public abstract class HttpClientBase : TcpClientBase, IHttpSession
return; return;
} }
content.InternalTryComputeLength(out var contentLength);
content.InternalBuildingHeader(request.Headers); content.InternalBuildingHeader(request.Headers);
request.BuildHeader(ref writer); request.BuildHeader(ref writer);

View File

@@ -20,22 +20,24 @@ namespace TouchSocket.Http;
/// </summary> /// </summary>
public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClient public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClient
{ {
private HttpContext m_httpContext;
private ServerHttpResponse m_serverHttpResponse;
private readonly HttpServerDataHandlingAdapter m_httpAdapter; private readonly HttpServerDataHandlingAdapter m_httpAdapter;
private WebSocketDataHandlingAdapter m_webSocketAdapter; private readonly WebSocketDataHandlingAdapter m_webSocketAdapter;
private readonly HttpContext m_httpContext;
private readonly ServerHttpResponse m_serverHttpResponse;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数
/// </summary> /// </summary>
protected HttpSessionClient() protected HttpSessionClient()
{ {
this.Protocol = Protocol.Http; this.Protocol = Protocol.Http;
this.m_httpAdapter = new HttpServerDataHandlingAdapter(this.OnReceivingHttpRequest); var serverHttpRequest = new ServerHttpRequest(this);
this.m_serverHttpResponse = new ServerHttpResponse(serverHttpRequest, this);
this.m_httpContext = new HttpContext(serverHttpRequest, this.m_serverHttpResponse);
this.m_httpAdapter = new HttpServerDataHandlingAdapter(serverHttpRequest,this.OnReceivingHttpRequest);
this.m_webSocketAdapter = new WebSocketDataHandlingAdapter(); this.m_webSocketAdapter = new WebSocketDataHandlingAdapter();
} }
internal ITransport InternalTransport => this.Transport; internal ITransport InternalTransport => this.Transport;
/// <summary> /// <summary>
@@ -65,18 +67,6 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes
return base.OnTcpConnecting(e); return base.OnTcpConnecting(e);
} }
private async Task OnReceivingHttpRequest(ServerHttpRequest request)
{
if (this.m_httpContext == null)
{
this.m_serverHttpResponse = new ServerHttpResponse(request, this);
this.m_httpContext = new HttpContext(request, this.m_serverHttpResponse);
}
await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.m_serverHttpResponse.Reset();
}
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e) protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{ {
@@ -103,4 +93,10 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes
base.SafetyDispose(disposing); base.SafetyDispose(disposing);
} }
private async Task OnReceivingHttpRequest(ServerHttpRequest request)
{
await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.m_serverHttpResponse.Reset();
}
} }

View File

@@ -14,12 +14,13 @@ namespace TouchSocket.Http;
internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdapter internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdapter
{ {
public HttpServerDataHandlingAdapter(Func<ServerHttpRequest, Task> func) public HttpServerDataHandlingAdapter(ServerHttpRequest request, Func<ServerHttpRequest, Task> func)
{ {
this.m_func = func; this.m_func = func;
this.m_requestRoot = request;
} }
private ServerHttpRequest m_currentRequest; private ServerHttpRequest m_currentRequest;
private ServerHttpRequest m_requestRoot; private readonly ServerHttpRequest m_requestRoot;
private long m_surLen; private long m_surLen;
private Task m_task; private Task m_task;
@@ -61,8 +62,6 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd
{ {
throw new Exception($"此适配器必须适用于{nameof(IHttpService)}"); throw new Exception($"此适配器必须适用于{nameof(IHttpService)}");
} }
this.m_requestRoot = new ServerHttpRequest(httpSessionClient);
base.OnLoaded(owner); base.OnLoaded(owner);
} }
@@ -119,7 +118,7 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd
{ {
this.m_currentRequest.InternalSetContent(ReadOnlyMemory<byte>.Empty); this.m_currentRequest.InternalSetContent(ReadOnlyMemory<byte>.Empty);
} }
await this.GoReceivedAsync(this.m_currentRequest); await this.GoReceivedAsync(this.m_currentRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.m_currentRequest = null; this.m_currentRequest = null;
} }
} }

View File

@@ -36,18 +36,9 @@ public abstract class HttpContent
/// <param name="header">HTTP头的接口实现</param> /// <param name="header">HTTP头的接口实现</param>
internal void InternalBuildingHeader(IHttpHeader header) internal void InternalBuildingHeader(IHttpHeader header)
{ {
if (this.TryComputeLength(out var length))
{
header.TryAdd(HttpHeaders.ContentLength, length.ToString());
}
this.OnBuildingHeader(header); this.OnBuildingHeader(header);
} }
internal bool InternalTryComputeLength(out long length)
{
return this.TryComputeLength(out length);
}
internal Task InternalWriteContent(PipeWriter writer, CancellationToken cancellationToken) internal Task InternalWriteContent(PipeWriter writer, CancellationToken cancellationToken)
{ {
return this.WriteContent(writer, cancellationToken); return this.WriteContent(writer, cancellationToken);
@@ -67,13 +58,6 @@ public abstract class HttpContent
/// <param name="header">HTTP头的接口实现</param> /// <param name="header">HTTP头的接口实现</param>
protected abstract void OnBuildingHeader(IHttpHeader header); protected abstract void OnBuildingHeader(IHttpHeader header);
/// <summary>
/// 尝试计算内容的长度。
/// </summary>
/// <param name="length">输出参数,表示内容的长度。</param>
/// <returns>如果成功计算长度,则返回 true否则返回 false。</returns>
protected abstract bool TryComputeLength(out long length);
protected abstract Task WriteContent(PipeWriter writer, CancellationToken cancellationToken); protected abstract Task WriteContent(PipeWriter writer, CancellationToken cancellationToken);
#region implicit #region implicit

View File

@@ -30,9 +30,11 @@ public class ReadonlyMemoryHttpContent : HttpContent
{ {
this.m_memory = memory; this.m_memory = memory;
this.m_contentType = contentType; this.m_contentType = contentType;
this.m_lengthString= this.m_memory.Length.ToString();
} }
private readonly string m_contentType; private readonly string m_contentType;
private readonly string m_lengthString;
/// <summary> /// <summary>
/// 获取封装的只读内存。 /// 获取封装的只读内存。
@@ -60,7 +62,7 @@ public class ReadonlyMemoryHttpContent : HttpContent
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnBuildingHeader(IHttpHeader header) protected override void OnBuildingHeader(IHttpHeader header)
{ {
header.Add(HttpHeaders.ContentLength, this.m_memory.Length.ToString()); header.Add(HttpHeaders.ContentLength, this.m_lengthString);
if (this.m_contentType != null) if (this.m_contentType != null)
{ {
header.Add(HttpHeaders.ContentType, this.m_contentType); header.Add(HttpHeaders.ContentType, this.m_contentType);
@@ -76,11 +78,4 @@ public class ReadonlyMemoryHttpContent : HttpContent
{ {
await writer.WriteAsync(this.m_memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await writer.WriteAsync(this.m_memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
/// <inheritdoc/>
protected override bool TryComputeLength(out long length)
{
length = this.m_memory.Length;
return true;
}
} }

View File

@@ -75,10 +75,13 @@ public class StreamHttpContent : HttpContent
{ {
header.Add(HttpHeaders.TransferEncoding, "chunked"); header.Add(HttpHeaders.TransferEncoding, "chunked");
} }
else if (TryComputeLength(out var length))
{
header.Add(HttpHeaders.ContentLength,length.ToString());
}
} }
/// <inheritdoc/> private bool TryComputeLength(out long length)
protected override bool TryComputeLength(out long length)
{ {
try try
{ {

View File

@@ -36,8 +36,9 @@ public abstract class MqttActor : DisposableObject, IOnlineClient
if (disposing) if (disposing)
{ {
this.m_tokenSource.Cancel(); this.m_tokenSource.SafeCancel();
this.m_tokenSource.Dispose(); this.m_tokenSource.SafeDispose();
this.m_waitHandlePool.CancelAll();
} }
} }

View File

@@ -117,7 +117,10 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient
await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
var connAckMessage = await this.m_mqttActor.ConnectAsync(connectMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); var connAckMessage = await this.m_mqttActor.ConnectAsync(connectMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
if (connAckMessage.ReturnCode!= MqttReasonCode.ConnectionAccepted)
{
ThrowHelper.ThrowException($"Connection failed with reason: {connAckMessage.ReturnCode}reasonString: {connAckMessage.ReasonString}");
}
await this.PluginManager.RaiseAsync(typeof(IMqttConnectedPlugin), this.Resolver, this, new MqttConnectedEventArgs(connectMessage, connAckMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.PluginManager.RaiseAsync(typeof(IMqttConnectedPlugin), this.Resolver, this, new MqttConnectedEventArgs(connectMessage, connAckMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
@@ -149,6 +152,8 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OnTcpClosed(ClosedEventArgs e) protected override async Task OnTcpClosed(ClosedEventArgs e)
{ {
this.m_mqttActor.WaitHandlePool.CancelAll();
await this.PluginManager.RaiseAsync(typeof(IMqttClosedPlugin), this.Resolver, this, new MqttClosedEventArgs(e.Manual, e.Message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.PluginManager.RaiseAsync(typeof(IMqttClosedPlugin), this.Resolver, this, new MqttClosedEventArgs(e.Manual, e.Message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }

View File

@@ -0,0 +1,101 @@
// ------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
// ------------------------------------------------------------------------------
using TouchSocket.Sockets;
namespace TouchSocket.Mqtt;
/// <summary>
/// 提供扩展方法以支持MQTT检查操作。
/// </summary>
public static class ReconnectionOptionsExtension
{
/// <summary>
/// 配置MQTT检查操作。
/// </summary>
/// <typeparam name="TClient">客户端类型,必须实现<see cref="IConnectableClient"/>、<see cref="IOnlineClient"/>、<see cref="IDependencyClient"/>和<see cref="IMqttClient"/>。</typeparam>
/// <param name="reconnectionOption">重连选项。</param>
/// <param name="activeTimeSpan">活动时间间隔默认为3秒。</param>
/// <param name="pingTimeout">Ping超时时间默认为5秒。</param>
/// <exception cref="ArgumentOutOfRangeException">当时间参数小于或等于零时抛出。</exception>
/// <exception cref="ArgumentNullException">当<paramref name="reconnectionOption"/>为<see langword="null"/>时抛出。</exception>
public static void UseMqttCheckAction<TClient>(
this ReconnectionOption<TClient> reconnectionOption,
TimeSpan? activeTimeSpan = null,
TimeSpan? pingTimeout = null)
where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IMqttClient
{
ThrowHelper.ThrowIfNull(reconnectionOption, nameof(reconnectionOption));
var span = activeTimeSpan ?? TimeSpan.FromSeconds(3);
var timeout = pingTimeout ?? TimeSpan.FromSeconds(5);
// 验证时间参数的有效性
if (span <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(activeTimeSpan), "活动时间间隔必须大于零");
}
if (timeout <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(pingTimeout), "Ping超时时间必须大于零");
}
reconnectionOption.CheckAction = async (client) =>
{
// 第1步快速在线状态检查
// 如果客户端已经离线无需进一步检查直接返回Dead状态
if (!client.Online)
{
return ConnectionCheckResult.Dead;
}
// 第2步活动时间检查
// 如果客户端在指定时间内有活动,说明连接正常,跳过本次心跳检查
var lastActiveTime = client.GetLastActiveTime();
var timeSinceLastActivity = DateTimeOffset.UtcNow - lastActiveTime;
if (timeSinceLastActivity < span)
{
return ConnectionCheckResult.Skip;
}
// 第3步主动心跳检查
// 通过Ping操作验证连接的实际可用性
try
{
using var pingCts = new CancellationTokenSource(timeout);
var pingResult = await client.PingAsync(pingCts.Token).ConfigureAwait(false);
if (pingResult.IsSuccess)
{
return ConnectionCheckResult.Alive;
}
using var closeCts = new CancellationTokenSource(timeout);
var closeResult = await client.CloseAsync("心跳插件ping失败主动断开连接", closeCts.Token).ConfigureAwait(false);
return ConnectionCheckResult.Dead;
}
catch (OperationCanceledException)
{
// Ping超时认为连接已死
return ConnectionCheckResult.Dead;
}
catch
{
// 其他异常也认为连接不可用
return ConnectionCheckResult.Dead;
}
};
}
}

View File

@@ -162,30 +162,43 @@ public partial class MqttConnectMessage
#region Properties #region Properties
MqttExtension.WriteSessionExpiryInterval(ref writer, this.SessionExpiryInterval); // 写入属性长度字段与属性内容
MqttExtension.WriteAuthenticationMethod(ref writer, this.AuthenticationMethod); var variableByteIntegerRecorder = new VariableByteIntegerRecorder();
MqttExtension.WriteAuthenticationData(ref writer, this.AuthenticationData.Span); var propertiesWriter = this.CreateVariableWriter(ref writer);
MqttExtension.WriteReceiveMaximum(ref writer, this.ReceiveMaximum); variableByteIntegerRecorder.CheckOut(ref propertiesWriter);
MqttExtension.WriteTopicAliasMaximum(ref writer, this.TopicAliasMaximum); MqttExtension.WriteSessionExpiryInterval(ref propertiesWriter, this.SessionExpiryInterval);
MqttExtension.WriteMaximumPacketSize(ref writer, this.MaximumPacketSize); MqttExtension.WriteAuthenticationMethod(ref propertiesWriter, this.AuthenticationMethod);
MqttExtension.WriteRequestResponseInformation(ref writer, this.RequestResponseInformation); MqttExtension.WriteAuthenticationData(ref propertiesWriter, this.AuthenticationData.Span);
MqttExtension.WriteRequestProblemInformation(ref writer, this.RequestProblemInformation); MqttExtension.WriteReceiveMaximum(ref propertiesWriter, this.ReceiveMaximum);
MqttExtension.WriteUserProperties(ref writer, this.UserProperties); MqttExtension.WriteTopicAliasMaximum(ref propertiesWriter, this.TopicAliasMaximum);
MqttExtension.WriteMaximumPacketSize(ref propertiesWriter, this.MaximumPacketSize);
MqttExtension.WriteRequestResponseInformation(ref propertiesWriter, this.RequestResponseInformation);
MqttExtension.WriteRequestProblemInformation(ref propertiesWriter, this.RequestProblemInformation);
MqttExtension.WriteUserProperties(ref propertiesWriter, this.UserProperties);
variableByteIntegerRecorder.CheckIn(ref propertiesWriter);
writer.Advance(propertiesWriter.Position);
#endregion Properties #endregion Properties
MqttExtension.WriteMqttInt16String(ref writer, this.ClientId); MqttExtension.WriteMqttInt16String(ref writer, this.ClientId);
if (this.WillFlag) if (this.WillFlag)
{ {
MqttExtension.WritePayloadFormatIndicator(ref writer, this.WillPayloadFormatIndicator); // 写入遗嘱属性长度字段与属性内容
MqttExtension.WriteMessageExpiryInterval(ref writer, this.WillMessageExpiryInterval); var willVariableByteIntegerRecorder = new VariableByteIntegerRecorder();
MqttExtension.WriteResponseTopic(ref writer, this.WillResponseTopic); var willPropertiesWriter = this.CreateVariableWriter(ref writer);
MqttExtension.WriteCorrelationData(ref writer, this.WillCorrelationData.Span); willVariableByteIntegerRecorder.CheckOut(ref willPropertiesWriter);
MqttExtension.WriteContentType(ref writer, this.WillContentType); MqttExtension.WritePayloadFormatIndicator(ref willPropertiesWriter, this.WillPayloadFormatIndicator);
MqttExtension.WriteWillDelayInterval(ref writer, this.WillDelayInterval); MqttExtension.WriteMessageExpiryInterval(ref willPropertiesWriter, this.WillMessageExpiryInterval);
MqttExtension.WriteResponseTopic(ref willPropertiesWriter, this.WillResponseTopic);
MqttExtension.WriteCorrelationData(ref willPropertiesWriter, this.WillCorrelationData.Span);
MqttExtension.WriteContentType(ref willPropertiesWriter, this.WillContentType);
MqttExtension.WriteWillDelayInterval(ref willPropertiesWriter, this.WillDelayInterval);
MqttExtension.WriteUserProperties(ref willPropertiesWriter, this.WillUserProperties);
willVariableByteIntegerRecorder.CheckIn(ref willPropertiesWriter);
writer.Advance(willPropertiesWriter.Position);
MqttExtension.WriteMqttInt16String(ref writer, this.WillTopic); MqttExtension.WriteMqttInt16String(ref writer, this.WillTopic);
MqttExtension.WriteMqttInt16Memory(ref writer, this.WillPayload); MqttExtension.WriteMqttInt16Memory(ref writer, this.WillPayload);
MqttExtension.WriteUserProperties(ref writer, this.WillUserProperties);
} }
if (this.UserNameFlag) if (this.UserNameFlag)
@@ -260,16 +273,6 @@ public partial class MqttConnectMessage
} }
} }
//this.SessionExpiryInterval = propertiesReader.SessionExpiryInterval;
//this.AuthenticationMethod = propertiesReader.AuthenticationMethod;
//this.AuthenticationData = propertiesReader.AuthenticationData;
//this.ReceiveMaximum = propertiesReader.ReceiveMaximum;
//this.TopicAliasMaximum = propertiesReader.TopicAliasMaximum;
//this.MaximumPacketSize = propertiesReader.MaximumPacketSize;
//this.RequestResponseInformation = propertiesReader.RequestResponseInformation;
//this.RequestProblemInformation = propertiesReader.RequestProblemInformation;
//this.UserProperties = propertiesReader.UserProperties;
#endregion Properties #endregion Properties
this.ClientId = MqttExtension.ReadMqttInt16String(ref reader); this.ClientId = MqttExtension.ReadMqttInt16String(ref reader);

View File

@@ -24,7 +24,10 @@ public partial class MqttSubscribeMessage
var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); var variableByteIntegerRecorder = new VariableByteIntegerRecorder();
var byteBlockWriter = this.CreateVariableWriter(ref writer); var byteBlockWriter = this.CreateVariableWriter(ref writer);
variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); variableByteIntegerRecorder.CheckOut(ref byteBlockWriter);
if (this.SubscriptionIdentifier > 0)
{
MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier); MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier);
}
MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties);
variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); variableByteIntegerRecorder.CheckIn(ref byteBlockWriter);
writer.Advance(byteBlockWriter.Position); writer.Advance(byteBlockWriter.Position);
@@ -32,7 +35,13 @@ public partial class MqttSubscribeMessage
foreach (var item in this.m_subscribeRequests) foreach (var item in this.m_subscribeRequests)
{ {
MqttExtension.WriteMqttInt16String(ref writer, item.Topic); MqttExtension.WriteMqttInt16String(ref writer, item.Topic);
WriterExtension.WriteValue<TWriter, byte>(ref writer, (byte)item.QosLevel); // MQTT v5 订阅选项包含: QoS(bit 0-1), NL(bit 2), RAP(bit 3), Retain Handling(bit 4-5)
byte options = 0;
options = options.SetQosLevel(0, item.QosLevel);
options = options.SetBit(2, item.NoLocal);
options = options.SetBit(3, item.RetainAsPublished);
options = options.SetRetainHandling(4, item.RetainHandling);
WriterExtension.WriteValue<TWriter, byte>(ref writer, options);
} }
} }
@@ -40,7 +49,7 @@ public partial class MqttSubscribeMessage
protected override void UnpackWithMqtt5<TReader>(ref TReader reader) protected override void UnpackWithMqtt5<TReader>(ref TReader reader)
{ {
this.MessageId = ReaderExtension.ReadValue<TReader, ushort>(ref reader, EndianType.Big); this.MessageId = ReaderExtension.ReadValue<TReader, ushort>(ref reader, EndianType.Big);
this.MessageId = ReaderExtension.ReadValue<TReader, ushort>(ref reader, EndianType.Big);
var propertiesReader = new MqttV5PropertiesReader<TReader>(ref reader); var propertiesReader = new MqttV5PropertiesReader<TReader>(ref reader);
while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) while (propertiesReader.TryRead(ref reader, out var mqttPropertyId))
@@ -58,8 +67,7 @@ public partial class MqttSubscribeMessage
break; break;
} }
} }
//this.SubscriptionIdentifier = propertiesReader.SubscriptionIdentifier;
//this.UserProperties = propertiesReader.UserProperties;
while (!this.EndOfByteBlock(reader)) while (!this.EndOfByteBlock(reader))
{ {
var topic = MqttExtension.ReadMqttInt16String(ref reader); var topic = MqttExtension.ReadMqttInt16String(ref reader);

View File

@@ -429,6 +429,8 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession
TargetHost = iPHost.Host TargetHost = iPHost.Host
}; };
} }
sslOption.TargetHost ??= iPHost.Host;
await this.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }

View File

@@ -163,8 +163,8 @@ public abstract class TcpServiceBase<TClient> : ConnectableService<TClient>, ITc
ReuseAddress = this.Config.GetValue(TouchSocketConfigExtension.ReuseAddressProperty), ReuseAddress = this.Config.GetValue(TouchSocketConfigExtension.ReuseAddressProperty),
NoDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty), NoDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty),
Adapter = this.Config.GetValue(TouchSocketConfigExtension.TcpDataHandlingAdapterProperty), Adapter = this.Config.GetValue(TouchSocketConfigExtension.TcpDataHandlingAdapterProperty),
Backlog= this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty)
}; };
option.Backlog = this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty) ?? option.Backlog;
optionList.Add(option); optionList.Add(option);
} }
} }

View File

@@ -113,7 +113,7 @@ public static class TouchSocketConfigExtension
/// 挂起连接队列的最大长度,所需类型<see cref="int"/> /// 挂起连接队列的最大长度,所需类型<see cref="int"/>
/// </summary> /// </summary>
[GeneratorProperty(TargetType = typeof(TouchSocketConfig))] [GeneratorProperty(TargetType = typeof(TouchSocketConfig))]
public static readonly DependencyProperty<int?> BacklogProperty = new("Backlog", null); public static readonly DependencyProperty<int> BacklogProperty = new("Backlog", 100);
/// <summary> /// <summary>
/// 设置默认Id的获取方式所需类型<see cref="Func{T, TResult}"/> /// 设置默认Id的获取方式所需类型<see cref="Func{T, TResult}"/>

View File

@@ -117,9 +117,10 @@ public class ReconnectionOption<TClient>
while (this.MaxRetryCount < 0 || attempts < this.MaxRetryCount) while (this.MaxRetryCount < 0 || attempts < this.MaxRetryCount)
{ {
if (cancellationToken.IsCancellationRequested) if (client.DisposedValue||cancellationToken.IsCancellationRequested)
{
return; return;
}
if (client.PauseReconnection) if (client.PauseReconnection)
{ {
if (this.LogReconnection) if (this.LogReconnection)
@@ -150,9 +151,6 @@ public class ReconnectionOption<TClient>
} }
catch (Exception ex) catch (Exception ex)
{ {
if (cancellationToken.IsCancellationRequested)
return;
this.OnFailed?.Invoke(client, attempts, ex); this.OnFailed?.Invoke(client, attempts, ex);
if (this.LogReconnection) if (this.LogReconnection)

View File

@@ -20,27 +20,27 @@ public class TcpListenOption
/// <summary> /// <summary>
/// 名称 /// 名称
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; init; }
/// <summary> /// <summary>
/// 监听地址 /// 监听地址
/// </summary> /// </summary>
public IPHost IpHost { get; set; } public IPHost IpHost { get; init; }
/// <summary> /// <summary>
/// 是否使用地址复用 /// 是否使用地址复用
/// </summary> /// </summary>
public bool ReuseAddress { get; set; } public bool ReuseAddress { get; init; }
/// <summary> /// <summary>
/// Tcp处理并发连接时最大半连接队列 /// Tcp处理并发连接时最大半连接队列
/// </summary> /// </summary>
public int Backlog { get; set; } = 100; public int Backlog { get; init; } = 100;
/// <summary> /// <summary>
/// 禁用延迟发送 /// 禁用延迟发送
/// </summary> /// </summary>
public bool NoDelay { get; set; } = true; public bool NoDelay { get; init; } = true;
/// <summary> /// <summary>
/// 是否使用ssl加密 /// 是否使用ssl加密
@@ -50,10 +50,10 @@ public class TcpListenOption
/// <summary> /// <summary>
/// 用于Ssl加密的证书 /// 用于Ssl加密的证书
/// </summary> /// </summary>
public ServiceSslOption ServiceSslOption { get; set; } public ServiceSslOption ServiceSslOption { get; init; }
/// <summary> /// <summary>
/// 配置Tcp适配器 /// 配置Tcp适配器
/// </summary> /// </summary>
public Func<SingleStreamDataHandlingAdapter> Adapter { get; set; } public Func<SingleStreamDataHandlingAdapter> Adapter { get; init; }
} }

View File

@@ -107,6 +107,7 @@ internal sealed class TcpTransport : BaseTransport
this.UseSsl = true; this.UseSsl = true;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<Result> CloseAsync(string msg, CancellationToken cancellationToken = default) public override async Task<Result> CloseAsync(string msg, CancellationToken cancellationToken = default)
{ {