mirror of
https://github.com/RRQM/TouchSocket.git
synced 2025-12-17 08:56:43 +08:00
发布:4.0.2
This commit is contained in:
@@ -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'">
|
||||||
|
|||||||
@@ -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()))
|
||||||
{
|
{
|
||||||
|
|||||||
128
src/TouchSocket.Core/Text/StringPool.cs
Normal file
128
src/TouchSocket.Core/Text/StringPool.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
|
||||||
|
|
||||||
///// <summary>
|
internal static bool IsPredefinedHeader(string key)
|
||||||
///// 请求头枚举
|
{
|
||||||
///// </summary>
|
var keyLength = key.Length;
|
||||||
//public enum HttpHeaders : byte
|
if (keyLength == 0)
|
||||||
//{
|
{
|
||||||
// /// <summary>
|
return false;
|
||||||
// /// Cache-Control 标头,指定请求/响应链上所有缓存控制机制必须服从的指令。
|
}
|
||||||
// /// </summary>
|
|
||||||
// [Description("Cache-Control")]
|
|
||||||
// CacheControl = 0,
|
|
||||||
|
|
||||||
// /// <summary>
|
var firstChar = char.ToLowerInvariant(key[0]);
|
||||||
// /// Connection 标头,指定特定连接需要的选项。
|
|
||||||
// /// </summary>
|
|
||||||
// [Description("Connection")]
|
|
||||||
// Connection = 1,
|
|
||||||
|
|
||||||
// /// <summary>
|
return firstChar switch
|
||||||
// /// Date 标头,指定开始创建请求的日期和时间。
|
{
|
||||||
// /// </summary>
|
'a' => IsAGroupHeader(key, keyLength),
|
||||||
// [Description("Date")]
|
'c' => IsCGroupHeader(key, keyLength),
|
||||||
// Date = 2,
|
'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判断(模块化,易维护)
|
||||||
// /// Keep-Alive 标头,指定用以维护持久性连接的参数。
|
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||||
// /// </summary>
|
private static bool IsAGroupHeader(string key, int keyLength)
|
||||||
// [Description("Keep-Alive")]
|
{
|
||||||
// KeepAlive = 3,
|
// 预定义长度: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)]
|
||||||
// /// Pragma 标头,指定可应用于请求/响应链上的任何代理的特定于实现的指令。
|
private static bool IsCGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Pragma")]
|
// 预定义长度: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)
|
||||||
// Pragma = 4,
|
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)]
|
||||||
// /// Trailer 标头,指定标头字段显示在以 chunked 传输编码方式编码的消息的尾部。
|
private static bool IsEGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Trailer")]
|
// 预定义长度:ETag(4)、Expect(6)、Expires(7)
|
||||||
// Trailer = 5,
|
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)]
|
||||||
// /// Transfer-Encoding 标头,指定对消息正文应用的转换的类型(如果有)。
|
private static bool IsIGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Transfer-Encoding")]
|
// 预定义长度:If-Match/If-Range(7)、If-None-Match(11)、If-Modified-Since(15)、If-Unmodified-Since(18)
|
||||||
// TransferEncoding = 6,
|
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)]
|
||||||
// /// Upgrade 标头,指定客户端支持的附加通信协议。
|
private static bool IsPGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Upgrade")]
|
// 预定义长度:Pragma(6)、Proxy-Authorization/Proxy-Authenticate(16)
|
||||||
// Upgrade = 7,
|
return keyLength switch
|
||||||
|
{
|
||||||
|
6 => ReferenceEquals(key, HttpHeaders.Pragma),
|
||||||
|
16 => ReferenceEquals(key, HttpHeaders.ProxyAuthorization) || ReferenceEquals(key, HttpHeaders.ProxyAuthenticate),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// /// <summary>
|
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||||
// /// Via 标头,指定网关和代理程序要使用的中间协议。
|
private static bool IsRGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Via")]
|
// 预定义长度:Range(5)、Referer(6)、Retry-After(10)
|
||||||
// Via = 8,
|
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)]
|
||||||
// /// Warning 标头,指定关于可能未在消息中反映的消息的状态或转换的附加信息。
|
private static bool IsSGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Warning")]
|
// 预定义长度:Server(6)、Set-Cookie(8)
|
||||||
// Warning = 9,
|
return keyLength switch
|
||||||
|
{
|
||||||
|
6 => ReferenceEquals(key, HttpHeaders.Server),
|
||||||
|
8 => ReferenceEquals(key, HttpHeaders.SetCookie),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// /// <summary>
|
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||||
// /// Allow 标头,指定支持的 HTTP 方法集。
|
private static bool IsTGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Allow")]
|
// 预定义长度:TE(2)、Trailer(7)、Translate(8)、Transfer-Encoding(16)
|
||||||
// Allow = 10,
|
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)]
|
||||||
// /// Content-Length 标头,指定伴随正文数据的长度(以字节为单位)。
|
private static bool IsVGroupHeader(string key, int keyLength)
|
||||||
// /// </summary>
|
{
|
||||||
// [Description("Content-Length")]
|
// 预定义长度:Via(3)、Vary(4)、Warning(7)
|
||||||
// ContentLength = 11,
|
return keyLength switch
|
||||||
|
{
|
||||||
|
3 => ReferenceEquals(key, HttpHeaders.Via),
|
||||||
|
4 => ReferenceEquals(key, HttpHeaders.Vary),
|
||||||
|
7 => ReferenceEquals(key, HttpHeaders.Warning),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
// /// <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
|
|
||||||
//}
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -306,11 +305,23 @@ 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
|
||||||
{
|
{
|
||||||
TouchSocketHttpUtility.AppendHTTP(ref writer);
|
var versionBytes = TouchSocketHttpUtility.GetHttpVersionBytes(this.ProtocolVersion);
|
||||||
TouchSocketHttpUtility.AppendSlash(ref writer);
|
if (!versionBytes.IsEmpty)
|
||||||
TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion);
|
{
|
||||||
|
writer.Write(versionBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TouchSocketHttpUtility.AppendHTTP(ref writer);
|
||||||
|
TouchSocketHttpUtility.AppendSlash(ref writer);
|
||||||
|
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()
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set => this.Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICollection<string> Keys => m_dictionary.Keys;
|
|
||||||
|
|
||||||
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
|
||||||
|
{
|
||||||
|
this.m_dictionary.Add(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_dictionary.Add(key, value);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"/> 中追加 "&" 符号。
|
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,35 +134,35 @@ 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)
|
||||||
{
|
{
|
||||||
#if NET9_0_OR_GREATER
|
#if NET9_0_OR_GREATER
|
||||||
return Uri.UnescapeDataString(urlSpan);
|
return Uri.UnescapeDataString(urlSpan);
|
||||||
#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 = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
101
src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs
Normal file
101
src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier);
|
if (this.SubscriptionIdentifier > 0)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}"/>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user