mirror of
https://github.com/RRQM/TouchSocket.git
synced 2025-12-17 17:06:45 +08:00
发布:4.0.2
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<BaseVersion>4.0.1</BaseVersion>
|
||||
<BaseVersion>4.0.2</BaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed class ConsoleAction
|
||||
var separator = new string('─', maxOrderLength + 4);
|
||||
|
||||
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()))
|
||||
{
|
||||
|
||||
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>
|
||||
public TextValues(string value)
|
||||
{
|
||||
m_values = value;
|
||||
this.m_values = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +42,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public TextValues(string[] values)
|
||||
{
|
||||
m_values = values;
|
||||
this.m_values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +53,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var value = m_values;
|
||||
var value = this.m_values;
|
||||
if (value is null)
|
||||
{
|
||||
return 0;
|
||||
@@ -74,9 +74,17 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
object value = m_values;
|
||||
if (value is null) return null;
|
||||
if (value is string s) return s;
|
||||
var value = this.m_values;
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is string s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
var arr = Unsafe.As<string[]>(value);
|
||||
return arr.Length > 0 ? arr[0] : null;
|
||||
}
|
||||
@@ -85,7 +93,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// <summary>
|
||||
/// 指示是否为空集合。
|
||||
/// </summary>
|
||||
public bool IsEmpty => Count == 0;
|
||||
public bool IsEmpty => this.m_values is null;
|
||||
|
||||
/// <summary>
|
||||
/// 通过索引获取指定值。
|
||||
@@ -95,15 +103,10 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
object value = m_values;
|
||||
if (value is null)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
var value = this.m_values ?? throw new IndexOutOfRangeException();
|
||||
if (value is string s)
|
||||
{
|
||||
if (index == 0) return s;
|
||||
throw new IndexOutOfRangeException();
|
||||
return index == 0 ? s : throw new IndexOutOfRangeException();
|
||||
}
|
||||
return Unsafe.As<string[]>(value)[index];
|
||||
}
|
||||
@@ -153,7 +156,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
{
|
||||
return this;
|
||||
}
|
||||
var current = m_values;
|
||||
var current = this.m_values;
|
||||
if (current is null)
|
||||
{
|
||||
return new TextValues(value);
|
||||
@@ -174,7 +177,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public bool Equals(string other, StringComparison comparison)
|
||||
{
|
||||
return Equals(new TextValues(other));
|
||||
return this.Equals(new TextValues(other));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,17 +185,17 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public bool Equals(TextValues other)
|
||||
{
|
||||
if (ReferenceEquals(m_values, other.m_values)) return true;
|
||||
int count = Count;
|
||||
if (ReferenceEquals(this.m_values, other.m_values)) return true;
|
||||
var count = this.Count;
|
||||
if (count != other.Count) return false;
|
||||
if (count == 0) return true;
|
||||
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();
|
||||
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;
|
||||
}
|
||||
@@ -202,31 +205,31 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// <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>
|
||||
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>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
object value = m_values;
|
||||
var value = this.m_values;
|
||||
if (value is null) return 0;
|
||||
if (value is string s)
|
||||
{
|
||||
return s?.GetHashCode() ?? 0;
|
||||
}
|
||||
var arr = Unsafe.As<string[]>(value);
|
||||
int hash = 17;
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
var hash = 17;
|
||||
for (var i = 0; i < arr.Length; i++)
|
||||
{
|
||||
hash = hash * 31 + (arr[i]?.GetHashCode() ?? 0);
|
||||
}
|
||||
@@ -238,10 +241,10 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public string[] ToArray()
|
||||
{
|
||||
object value = m_values;
|
||||
var value = this.m_values;
|
||||
if (value is null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
return [];
|
||||
}
|
||||
if (value is string s)
|
||||
{
|
||||
@@ -255,7 +258,7 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
object value = m_values;
|
||||
var value = this.m_values;
|
||||
if (value is null)
|
||||
{
|
||||
return string.Empty;
|
||||
@@ -265,8 +268,16 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
return s ?? string.Empty;
|
||||
}
|
||||
var arr = Unsafe.As<string[]>(value);
|
||||
if (arr.Length == 0) return string.Empty;
|
||||
if (arr.Length == 1) return arr[0] ?? string.Empty;
|
||||
if (arr.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (arr.Length == 1)
|
||||
{
|
||||
return arr[0] ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Join(",", arr);
|
||||
}
|
||||
|
||||
@@ -281,22 +292,22 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
|
||||
internal Enumerator(object values)
|
||||
{
|
||||
m_values = values;
|
||||
m_index = -1;
|
||||
m_current = null;
|
||||
this.m_values = values;
|
||||
this.m_index = -1;
|
||||
this.m_current = null;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void Dispose()
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -305,26 +316,26 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (m_values is null)
|
||||
if (this.m_values is null)
|
||||
{
|
||||
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;
|
||||
m_current = s;
|
||||
this.m_index = 0;
|
||||
this.m_current = s;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var arr = Unsafe.As<string[]>(m_values);
|
||||
int next = m_index + 1;
|
||||
var arr = Unsafe.As<string[]>(this.m_values);
|
||||
var next = this.m_index + 1;
|
||||
if ((uint)next < (uint)arr.Length)
|
||||
{
|
||||
m_index = next;
|
||||
m_current = arr[next];
|
||||
this.m_index = next;
|
||||
this.m_current = arr[next];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -335,8 +346,8 @@ public readonly struct TextValues : IEnumerable<string>, IEquatable<TextValues>
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_index = -1;
|
||||
m_current = null;
|
||||
this.m_index = -1;
|
||||
this.m_current = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -37,6 +38,11 @@ public abstract class HttpBase : IRequestInfo
|
||||
|
||||
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>
|
||||
/// 可接受MIME类型
|
||||
/// </summary>
|
||||
@@ -70,13 +76,10 @@ public abstract class HttpBase : IRequestInfo
|
||||
/// </summary>
|
||||
public bool IsChunk
|
||||
{
|
||||
get
|
||||
{
|
||||
var transferEncoding = this.Headers.Get(HttpHeaders.TransferEncoding);
|
||||
return "chunked".Equals(transferEncoding, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
get => this.m_isChunk;
|
||||
set
|
||||
{
|
||||
this.m_isChunk = value;
|
||||
if (value)
|
||||
{
|
||||
this.Headers.Add(HttpHeaders.TransferEncoding, "chunked");
|
||||
@@ -93,12 +96,12 @@ public abstract class HttpBase : IRequestInfo
|
||||
/// </summary>
|
||||
public long ContentLength
|
||||
{
|
||||
get
|
||||
get => this.m_contentLength;
|
||||
set
|
||||
{
|
||||
var contentLength = this.m_headers.Get(HttpHeaders.ContentLength);
|
||||
return contentLength.IsEmpty ? 0 : long.TryParse(contentLength, out var value) ? value : 0;
|
||||
this.m_contentLength = value;
|
||||
this.m_headers.Add(HttpHeaders.ContentLength, value.ToString());
|
||||
}
|
||||
set => this.m_headers.Add(HttpHeaders.ContentLength, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,7 +136,8 @@ public abstract class HttpBase : IRequestInfo
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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)
|
||||
{
|
||||
@@ -150,10 +154,21 @@ public abstract class HttpBase : IRequestInfo
|
||||
}
|
||||
|
||||
// 一次性获取头部数据,减少多次调用GetSpan的开销
|
||||
var headerSpan = reader.GetSpan(headerLength);
|
||||
if (headerLength < 1024)
|
||||
{
|
||||
Span<byte> headerSpan = stackalloc byte[headerLength];
|
||||
|
||||
sequence.Slice(0, headerLength).CopyTo(headerSpan);
|
||||
|
||||
// 调用优化后的头部解析方法
|
||||
this.ReadHeadersOptimized(headerSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
var headerSpan = reader.GetSpan(headerLength);
|
||||
// 调用优化后的头部解析方法
|
||||
this.ReadHeadersOptimized(headerSpan);
|
||||
}
|
||||
|
||||
// 跳过头部数据和 \r\n\r\n 分隔符
|
||||
reader.Advance(totalRequired);
|
||||
@@ -164,6 +179,8 @@ public abstract class HttpBase : IRequestInfo
|
||||
{
|
||||
this.m_headers.Clear();
|
||||
this.ContentStatus = ContentCompletionStatus.Unknown;
|
||||
this.m_isChunk = false;
|
||||
this.m_contentLength = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,10 +195,10 @@ public abstract class HttpBase : IRequestInfo
|
||||
var colonIndex = line.IndexOf(TouchSocketHttpUtility.COLON);
|
||||
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));
|
||||
|
||||
if (keySpan.IsEmpty || valueSpan.IsEmpty)
|
||||
@@ -189,47 +206,164 @@ public abstract class HttpBase : IRequestInfo
|
||||
return;
|
||||
}
|
||||
|
||||
var key = keySpan.ToString(Encoding.UTF8).ToLowerInvariant();
|
||||
|
||||
// 检测是否包含英文逗号,按需分割
|
||||
if (valueSpan.IndexOf((byte)',') < 0)
|
||||
if (keySpan.Length == 14 && EqualsIgnoreCaseAscii(keySpan, "Content-Length"u8))
|
||||
{
|
||||
var value = valueSpan.ToString(Encoding.UTF8);
|
||||
this.m_headers.Add(key, value);
|
||||
if (TryParseLong(valueSpan, out var length))
|
||||
{
|
||||
this.m_contentLength = length;
|
||||
}
|
||||
var value = this.m_stringPool.Get(valueSpan);
|
||||
this.m_headers.AddInternal(HttpHeaders.ContentLength, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// 多值处理
|
||||
var values = new List<string>();
|
||||
int start = 0;
|
||||
if (keySpan.Length == 17 && EqualsIgnoreCaseAscii(keySpan, "Transfer-Encoding"u8))
|
||||
{
|
||||
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)
|
||||
{
|
||||
int commaIndex = valueSpan.Slice(start).IndexOf((byte)',');
|
||||
var nextComma = valueSpan.Slice(start).IndexOf((byte)',');
|
||||
ReadOnlySpan<byte> segment;
|
||||
if (commaIndex >= 0)
|
||||
|
||||
if (nextComma >= 0)
|
||||
{
|
||||
segment = valueSpan.Slice(start, commaIndex);
|
||||
start += commaIndex + 1;
|
||||
segment = valueSpan.Slice(start, nextComma);
|
||||
start += nextComma + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = valueSpan.Slice(start);
|
||||
start = valueSpan.Length;
|
||||
}
|
||||
|
||||
segment = TouchSocketHttpUtility.TrimWhitespace(segment);
|
||||
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)]
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace TouchSocket.Http;
|
||||
/// <summary>
|
||||
/// 请求头静态类
|
||||
@@ -280,328 +282,167 @@ public static class HttpHeaders
|
||||
/// Content-Disposition
|
||||
/// </summary>
|
||||
public const string ContentDisposition = "Content-Disposition";
|
||||
|
||||
internal static bool IsPredefinedHeader(string key)
|
||||
{
|
||||
var keyLength = key.Length;
|
||||
if (keyLength == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstChar = char.ToLowerInvariant(key[0]);
|
||||
|
||||
return firstChar switch
|
||||
{
|
||||
'a' => IsAGroupHeader(key, keyLength),
|
||||
'c' => IsCGroupHeader(key, keyLength),
|
||||
'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
|
||||
};
|
||||
}
|
||||
|
||||
#region 各分组Header判断(模块化,易维护)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsAGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度: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
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsCGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度: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)
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsEGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:ETag(4)、Expect(6)、Expires(7)
|
||||
return keyLength switch
|
||||
{
|
||||
4 => ReferenceEquals(key, HttpHeaders.ETag),
|
||||
6 => ReferenceEquals(key, HttpHeaders.Expect),
|
||||
7 => ReferenceEquals(key, HttpHeaders.Expires),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsIGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:If-Match/If-Range(7)、If-None-Match(11)、If-Modified-Since(15)、If-Unmodified-Since(18)
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsPGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:Pragma(6)、Proxy-Authorization/Proxy-Authenticate(16)
|
||||
return keyLength switch
|
||||
{
|
||||
6 => ReferenceEquals(key, HttpHeaders.Pragma),
|
||||
16 => ReferenceEquals(key, HttpHeaders.ProxyAuthorization) || ReferenceEquals(key, HttpHeaders.ProxyAuthenticate),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsRGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:Range(5)、Referer(6)、Retry-After(10)
|
||||
return keyLength switch
|
||||
{
|
||||
5 => ReferenceEquals(key, HttpHeaders.Range),
|
||||
6 => ReferenceEquals(key, HttpHeaders.Referer),
|
||||
10 => ReferenceEquals(key, HttpHeaders.RetryAfter),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsSGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:Server(6)、Set-Cookie(8)
|
||||
return keyLength switch
|
||||
{
|
||||
6 => ReferenceEquals(key, HttpHeaders.Server),
|
||||
8 => ReferenceEquals(key, HttpHeaders.SetCookie),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsTGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:TE(2)、Trailer(7)、Translate(8)、Transfer-Encoding(16)
|
||||
return keyLength switch
|
||||
{
|
||||
2 => ReferenceEquals(key, HttpHeaders.Te),
|
||||
7 => ReferenceEquals(key, HttpHeaders.Trailer),
|
||||
8 => ReferenceEquals(key, HttpHeaders.Translate),
|
||||
16 => ReferenceEquals(key, HttpHeaders.TransferEncoding),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
|
||||
private static bool IsVGroupHeader(string key, int keyLength)
|
||||
{
|
||||
// 预定义长度:Via(3)、Vary(4)、Warning(7)
|
||||
return keyLength switch
|
||||
{
|
||||
3 => ReferenceEquals(key, HttpHeaders.Via),
|
||||
4 => ReferenceEquals(key, HttpHeaders.Vary),
|
||||
7 => ReferenceEquals(key, HttpHeaders.Warning),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 请求头枚举
|
||||
///// </summary>
|
||||
//public enum HttpHeaders : byte
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// Cache-Control 标头,指定请求/响应链上所有缓存控制机制必须服从的指令。
|
||||
// /// </summary>
|
||||
// [Description("Cache-Control")]
|
||||
// CacheControl = 0,
|
||||
|
||||
// /// <summary>
|
||||
// /// Connection 标头,指定特定连接需要的选项。
|
||||
// /// </summary>
|
||||
// [Description("Connection")]
|
||||
// Connection = 1,
|
||||
|
||||
// /// <summary>
|
||||
// /// Date 标头,指定开始创建请求的日期和时间。
|
||||
// /// </summary>
|
||||
// [Description("Date")]
|
||||
// Date = 2,
|
||||
|
||||
// /// <summary>
|
||||
// /// Keep-Alive 标头,指定用以维护持久性连接的参数。
|
||||
// /// </summary>
|
||||
// [Description("Keep-Alive")]
|
||||
// KeepAlive = 3,
|
||||
|
||||
// /// <summary>
|
||||
// /// Pragma 标头,指定可应用于请求/响应链上的任何代理的特定于实现的指令。
|
||||
// /// </summary>
|
||||
// [Description("Pragma")]
|
||||
// Pragma = 4,
|
||||
|
||||
// /// <summary>
|
||||
// /// Trailer 标头,指定标头字段显示在以 chunked 传输编码方式编码的消息的尾部。
|
||||
// /// </summary>
|
||||
// [Description("Trailer")]
|
||||
// Trailer = 5,
|
||||
|
||||
// /// <summary>
|
||||
// /// Transfer-Encoding 标头,指定对消息正文应用的转换的类型(如果有)。
|
||||
// /// </summary>
|
||||
// [Description("Transfer-Encoding")]
|
||||
// TransferEncoding = 6,
|
||||
|
||||
// /// <summary>
|
||||
// /// Upgrade 标头,指定客户端支持的附加通信协议。
|
||||
// /// </summary>
|
||||
// [Description("Upgrade")]
|
||||
// Upgrade = 7,
|
||||
|
||||
// /// <summary>
|
||||
// /// Via 标头,指定网关和代理程序要使用的中间协议。
|
||||
// /// </summary>
|
||||
// [Description("Via")]
|
||||
// Via = 8,
|
||||
|
||||
// /// <summary>
|
||||
// /// Warning 标头,指定关于可能未在消息中反映的消息的状态或转换的附加信息。
|
||||
// /// </summary>
|
||||
// [Description("Warning")]
|
||||
// Warning = 9,
|
||||
|
||||
// /// <summary>
|
||||
// /// Allow 标头,指定支持的 HTTP 方法集。
|
||||
// /// </summary>
|
||||
// [Description("Allow")]
|
||||
// Allow = 10,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Length 标头,指定伴随正文数据的长度(以字节为单位)。
|
||||
// /// </summary>
|
||||
// [Description("Content-Length")]
|
||||
// ContentLength = 11,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Type 标头,指定伴随正文数据的 MIME 类型。
|
||||
// /// </summary>
|
||||
// [Description("Content-Type")]
|
||||
// ContentType = 12,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Encoding 标头,指定已应用于伴随正文数据的编码。
|
||||
// /// </summary>
|
||||
// [Description("Content-Encoding")]
|
||||
// ContentEncoding = 13,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Langauge 标头,指定伴随正文数据的自然语言。
|
||||
// /// </summary>
|
||||
// [Description("Content-Langauge")]
|
||||
// ContentLanguage = 14,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Location 标头,指定可从其中获得伴随正文的 URI。
|
||||
// /// </summary>
|
||||
// [Description("Content-Location")]
|
||||
// ContentLocation = 15,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-MD5 标头,指定伴随正文数据的 MD5 摘要,用于提供端到端消息完整性检查。
|
||||
// /// </summary>
|
||||
// [Description("Content-MD5")]
|
||||
// ContentMd5 = 16,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Range 标头,指定在完整正文中应用伴随部分正文数据的位置。
|
||||
// /// </summary>
|
||||
// [Description("Content-Range")]
|
||||
// ContentRange = 17,
|
||||
|
||||
// /// <summary>
|
||||
// /// Expires 标头,指定日期和时间,在此之后伴随的正文数据应视为陈旧的。
|
||||
// /// </summary>
|
||||
// [Description("Expires")]
|
||||
// Expires = 18,
|
||||
|
||||
// /// <summary>
|
||||
// /// Last-Modified 标头,指定上次修改伴随的正文数据的日期和时间。
|
||||
// /// </summary>
|
||||
// [Description("Last-Modified")]
|
||||
// LastModified = 19,
|
||||
|
||||
// /// <summary>
|
||||
// /// Accept 标头,指定响应可接受的 MIME 类型。
|
||||
// /// </summary>
|
||||
// [Description("Accept")]
|
||||
// Accept = 20,
|
||||
|
||||
// /// <summary>
|
||||
// /// Accept-Charset 标头,指定响应可接受的字符集。
|
||||
// /// </summary>
|
||||
// [Description("Accept-Charset")]
|
||||
// AcceptCharset = 21,
|
||||
|
||||
// /// <summary>
|
||||
// /// Accept-Encoding 标头,指定响应可接受的内容编码。
|
||||
// /// </summary>
|
||||
// [Description("Accept-Encoding")]
|
||||
// AcceptEncoding = 22,
|
||||
|
||||
// /// <summary>
|
||||
// /// Accept-Langauge 标头,指定响应首选的自然语言。
|
||||
// /// </summary>
|
||||
// [Description("Accept-Langauge")]
|
||||
// AcceptLanguage = 23,
|
||||
|
||||
// /// <summary>
|
||||
// /// Authorization 标头,指定客户端为向服务器验证自身身份而出示的凭据。
|
||||
// /// </summary>
|
||||
// [Description("Authorization")]
|
||||
// Authorization = 24,
|
||||
|
||||
// /// <summary>
|
||||
// /// Cookie 标头,指定向服务器提供的 Cookie 数据。
|
||||
// /// </summary>
|
||||
// [Description("Cookie")]
|
||||
// Cookie = 25,
|
||||
|
||||
// /// <summary>
|
||||
// /// Expect 标头,指定客户端要求的特定服务器行为。
|
||||
// /// </summary>
|
||||
// [Description("Expect")]
|
||||
// Expect = 26,
|
||||
|
||||
// /// <summary>
|
||||
// /// From 标头,指定控制请求用户代理的用户的 Internet 电子邮件地址。
|
||||
// /// </summary>
|
||||
// [Description("From")]
|
||||
// From = 27,
|
||||
|
||||
// /// <summary>
|
||||
// /// Host 标头,指定所请求资源的主机名和端口号。
|
||||
// /// </summary>
|
||||
// [Description("Host")]
|
||||
// Host = 28,
|
||||
|
||||
// /// <summary>
|
||||
// /// If-Match 标头,指定仅当客户端的指示资源的缓存副本是最新的时,才执行请求的操作。
|
||||
// /// </summary>
|
||||
// [Description("If-Match")]
|
||||
// IfMatch = 29,
|
||||
|
||||
// /// <summary>
|
||||
// /// If-Modified-Since 标头,指定仅当自指示的数据和时间之后修改了请求的资源时,才执行请求的操作。
|
||||
// /// </summary>
|
||||
// [Description("If-Modified-Since")]
|
||||
// IfModifiedSince = 30,
|
||||
|
||||
// /// <summary>
|
||||
// /// If-None-Match 标头,指定仅当客户端的指示资源的缓存副本都不是最新的时,才执行请求的操作。
|
||||
// /// </summary>
|
||||
// [Description("If-None-Match")]
|
||||
// IfNoneMatch = 31,
|
||||
|
||||
// /// <summary>
|
||||
// /// If-Range 标头,指定如果客户端的缓存副本是最新的,仅发送指定范围的请求资源。
|
||||
// /// </summary>
|
||||
// [Description("If-Range")]
|
||||
// IfRange = 32,
|
||||
|
||||
// /// <summary>
|
||||
// /// If-Unmodified-Since 标头,指定仅当自指示的日期和时间之后修改了请求的资源时,才执行请求的操作。
|
||||
// /// </summary>
|
||||
// [Description("If-Unmodified-Since")]
|
||||
// IfUnmodifiedSince = 33,
|
||||
|
||||
// /// <summary>
|
||||
// /// Max-Forwards 标头,指定一个整数,表示此请求还可转发的次数。
|
||||
// /// </summary>
|
||||
// [Description("Max-Forwards")]
|
||||
// MaxForwards = 34,
|
||||
|
||||
// /// <summary>
|
||||
// /// Proxy-Authorization 标头,指定客户端为向代理验证自身身份而出示的凭据。
|
||||
// /// </summary>
|
||||
// [Description("Proxy-Authorization")]
|
||||
// ProxyAuthorization = 35,
|
||||
|
||||
// /// <summary>
|
||||
// /// Referer 标头,指定从中获得请求 URI 的资源的 URI。
|
||||
// /// </summary>
|
||||
// [Description("Referer")]
|
||||
// Referer = 36,
|
||||
|
||||
// /// <summary>
|
||||
// /// Range 标头,指定代替整个响应返回的客户端请求的响应的子范围。
|
||||
// /// </summary>
|
||||
// [Description("Range")]
|
||||
// Range = 37,
|
||||
|
||||
// /// <summary>
|
||||
// /// TE 标头,指定响应可接受的传输编码方式。
|
||||
// /// </summary>
|
||||
// [Description("TE")]
|
||||
// Te = 38,
|
||||
|
||||
// /// <summary>
|
||||
// /// Translate 标头,与 WebDAV 功能一起使用的 HTTP 规范的 Microsoft 扩展。
|
||||
// /// </summary>
|
||||
// [Description("Translate")]
|
||||
// Translate = 39,
|
||||
|
||||
// /// <summary>
|
||||
// /// User-Agent 标头,指定有关客户端代理的信息。
|
||||
// /// </summary>
|
||||
// [Description("User-Agent")]
|
||||
// UserAgent = 40,
|
||||
|
||||
// /// <summary>
|
||||
// /// Accept-Ranges 标头,指定服务器接受的范围。
|
||||
// /// </summary>
|
||||
// [Description("Accept-Ranges")]
|
||||
// AcceptRanges = 41,
|
||||
|
||||
// /// <summary>
|
||||
// /// Age 标头,指定自起始服务器生成响应以来的时间长度(以秒为单位)。
|
||||
// /// </summary>
|
||||
// [Description("Age")]
|
||||
// Age = 42,
|
||||
|
||||
// /// <summary>
|
||||
// /// Etag 标头,指定请求的变量的当前值。
|
||||
// /// </summary>
|
||||
// [Description("Etag")]
|
||||
// ETag = 43,
|
||||
|
||||
// /// <summary>
|
||||
// /// Location 标头,指定为获取请求的资源而将客户端重定向到的 URI。
|
||||
// /// </summary>
|
||||
// [Description("Location")]
|
||||
// Location = 44,
|
||||
|
||||
// /// <summary>
|
||||
// /// Proxy-Authenticate 标头,指定客户端必须对代理验证其自身。
|
||||
// /// </summary>
|
||||
// [Description("Proxy-Authenticate")]
|
||||
// ProxyAuthenticate = 45,
|
||||
|
||||
// /// <summary>
|
||||
// /// Retry-After 标头,指定某个时间(以秒为单位)或日期和时间,在此时间之后客户端可以重试其请求。
|
||||
// /// </summary>
|
||||
// [Description("Retry-After")]
|
||||
// RetryAfter = 46,
|
||||
|
||||
// /// <summary>
|
||||
// /// Server 标头,指定关于起始服务器代理的信息。
|
||||
// /// </summary>
|
||||
// [Description("Server")]
|
||||
// Server = 47,
|
||||
|
||||
// /// <summary>
|
||||
// /// Set-Cookie 标头,指定提供给客户端的 Cookie 数据。
|
||||
// /// </summary>
|
||||
// [Description("Set-Cookie")]
|
||||
// SetCookie = 48,
|
||||
|
||||
// /// <summary>
|
||||
// /// Vary 标头,指定用于确定缓存的响应是否为新响应的请求标头。
|
||||
// /// </summary>
|
||||
// [Description("Vary")]
|
||||
// Vary = 49,
|
||||
|
||||
// /// <summary>
|
||||
// /// WWW-Authenticate 标头,指定客户端必须对服务器验证其自身。
|
||||
// /// </summary>
|
||||
// [Description("WWW-Authenticate")]
|
||||
// WwwAuthenticate = 50,
|
||||
|
||||
// /// <summary>
|
||||
// /// Origin。
|
||||
// /// </summary>
|
||||
// [Description("Origin")]
|
||||
// Origin = 51,
|
||||
|
||||
// /// <summary>
|
||||
// /// Content-Disposition
|
||||
// /// </summary>
|
||||
// [Description("Content-Disposition")]
|
||||
// ContentDisposition = 52
|
||||
//}
|
||||
@@ -193,28 +193,26 @@ public class HttpRequest : HttpBase
|
||||
{
|
||||
var start = 0;
|
||||
|
||||
// 解析 HTTP Method (GET/POST)
|
||||
var methodEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start);
|
||||
if (methodEnd == -1)
|
||||
{
|
||||
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);
|
||||
|
||||
// 解析 URL
|
||||
var urlEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start);
|
||||
if (urlEnd == -1)
|
||||
{
|
||||
this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start));
|
||||
return; // No protocol version
|
||||
return;
|
||||
}
|
||||
|
||||
this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start, urlEnd - start));
|
||||
start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, urlEnd + 1);
|
||||
|
||||
// 解析 Protocol (HTTP/1.1)
|
||||
var protocolSpan = requestLineSpan.Slice(start);
|
||||
var slashIndex = protocolSpan.IndexOf((byte)'/');
|
||||
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)
|
||||
{
|
||||
while (!querySpan.IsEmpty)
|
||||
|
||||
@@ -101,9 +101,9 @@ public abstract class HttpResponse : HttpBase
|
||||
}
|
||||
else
|
||||
{
|
||||
content.InternalTryComputeLength(out var contentLength);
|
||||
var writer = new PipeBytesWriter(transport.Writer);
|
||||
content.InternalBuildingHeader(this.Headers);
|
||||
|
||||
var writer = new PipeBytesWriter(transport.Writer);
|
||||
this.BuildHeader(ref writer);
|
||||
|
||||
var result = content.InternalBuildingContent(ref writer);
|
||||
@@ -213,7 +213,6 @@ public abstract class HttpResponse : HttpBase
|
||||
this.m_sentHeader = false;
|
||||
this.m_sentLength = 0;
|
||||
this.Responsed = false;
|
||||
this.IsChunk = false;
|
||||
this.StatusCode = 200;
|
||||
this.StatusMessage = "Success";
|
||||
this.Content = default;
|
||||
@@ -305,12 +304,24 @@ public abstract class HttpResponse : HttpBase
|
||||
}
|
||||
|
||||
private void BuildHeader<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
var versionBytes = TouchSocketHttpUtility.GetHttpVersionBytes(this.ProtocolVersion);
|
||||
if (!versionBytes.IsEmpty)
|
||||
{
|
||||
writer.Write(versionBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
TouchSocketHttpUtility.AppendHTTP(ref writer);
|
||||
TouchSocketHttpUtility.AppendSlash(ref writer);
|
||||
TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion);
|
||||
}
|
||||
|
||||
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.AppendUtf8String(ref writer, this.StatusMessage);
|
||||
TouchSocketHttpUtility.AppendRn(ref writer);
|
||||
@@ -322,11 +333,9 @@ public abstract class HttpResponse : HttpBase
|
||||
TouchSocketHttpUtility.AppendSpace(ref writer);
|
||||
TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Value);
|
||||
TouchSocketHttpUtility.AppendRn(ref writer);
|
||||
|
||||
}
|
||||
|
||||
TouchSocketHttpUtility.AppendRn(ref writer);
|
||||
|
||||
}
|
||||
|
||||
private ITransport GetITransport()
|
||||
|
||||
@@ -14,11 +14,46 @@ namespace TouchSocket.Http;
|
||||
|
||||
internal abstract class InternalHttpCollection : IDictionary<string, TextValues>
|
||||
{
|
||||
private readonly IEqualityComparer<string> m_comparer;
|
||||
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)
|
||||
{
|
||||
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]
|
||||
@@ -26,85 +61,186 @@ internal abstract class InternalHttpCollection : IDictionary<string, TextValues>
|
||||
get
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(key, nameof(key));
|
||||
return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(key, nameof(key));
|
||||
m_dictionary[key] = value;
|
||||
}
|
||||
this.EnsureDictionaryBuilt();
|
||||
return this.m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty;
|
||||
}
|
||||
|
||||
public ICollection<string> Keys => m_dictionary.Keys;
|
||||
|
||||
public ICollection<TextValues> Values => m_dictionary.Values;
|
||||
|
||||
public int Count => m_dictionary.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
set => this.Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(string key, TextValues value)
|
||||
{
|
||||
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
|
||||
{
|
||||
m_dictionary.Add(key, value);
|
||||
this.m_dictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.m_hasNonPredefinedKeys && !HttpHeaders.IsPredefinedHeader(key))
|
||||
{
|
||||
this.m_hasNonPredefinedKeys = true;
|
||||
}
|
||||
|
||||
if (!this.m_hasDuplicateKeys && this.m_pendingItems.Count > 0)
|
||||
{
|
||||
for (var i = 0; i < this.m_pendingItems.Count; i++)
|
||||
{
|
||||
if (this.m_comparer.Equals(this.m_pendingItems[i].Key, key))
|
||||
{
|
||||
this.m_hasDuplicateKeys = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.m_pendingItems.Add(new KeyValuePair<string, TextValues>(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, TextValues> item) => this.Add(item.Key, item.Value);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.m_dictionary.Clear();
|
||||
this.m_pendingItems.Clear();
|
||||
this.m_hasDuplicateKeys = false;
|
||||
this.m_hasNonPredefinedKeys = false;
|
||||
this.m_isDictionaryBuilt = false;
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, TextValues> item)
|
||||
{
|
||||
this.EnsureDictionaryBuilt();
|
||||
return ((IDictionary<string, TextValues>)this.m_dictionary).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
public void Clear() => m_dictionary.Clear();
|
||||
|
||||
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();
|
||||
internal void AddInternal(string key, TextValues value)
|
||||
{
|
||||
this.m_pendingItems.Add(new KeyValuePair<string, TextValues>(key, value));
|
||||
}
|
||||
|
||||
protected static TextValues Merge(TextValues a, TextValues b)
|
||||
{
|
||||
if (a.IsEmpty) return b;
|
||||
if (b.IsEmpty) return a;
|
||||
var ac = a.Count;
|
||||
var bc = b.Count;
|
||||
if (a.IsEmpty)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b.IsEmpty)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
var arrA = a.ToArray();
|
||||
var arrB = b.ToArray();
|
||||
var ac = arrA.Length;
|
||||
var bc = arrB.Length;
|
||||
|
||||
var newArr = new string[ac + bc];
|
||||
Array.Copy(arrA, 0, newArr, 0, ac);
|
||||
Array.Copy(arrB, 0, newArr, ac, bc);
|
||||
return new TextValues(newArr);
|
||||
}
|
||||
|
||||
public TextValues Get(string key)
|
||||
private void EnsureDictionaryBuilt()
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(key, nameof(key));
|
||||
return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty;
|
||||
if (this.m_isDictionaryBuilt)
|
||||
{
|
||||
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));
|
||||
|
||||
if (!TryGetValue(key, out var headerValue))
|
||||
if (!this.TryGetValue(key, out var headerValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,160 +14,119 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace TouchSocket.Http;
|
||||
|
||||
/// <summary>
|
||||
/// TouchSocketHttp辅助工具类
|
||||
/// </summary>
|
||||
public static class TouchSocketHttpUtility
|
||||
static class TouchSocketHttpUtility
|
||||
{
|
||||
public const int MaxReadSize = 1024 * 1024;
|
||||
|
||||
// HTTP头部解析相关常量
|
||||
/// <summary>
|
||||
/// 冒号字节值
|
||||
/// </summary>
|
||||
public const byte COLON = (byte)':';
|
||||
|
||||
/// <summary>
|
||||
/// 空格字节值
|
||||
/// </summary>
|
||||
public const byte SPACE = (byte)' ';
|
||||
|
||||
/// <summary>
|
||||
/// 制表符字节值
|
||||
/// </summary>
|
||||
public const byte TAB = (byte)'\t';
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个只读的字节序列,表示回车换行(CRLF)。
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 一个包含回车和换行字节的只读字节序列。
|
||||
/// </value>
|
||||
public static ReadOnlySpan<byte> CRLF => "\r\n"u8;
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个只读的字节序列,表示双回车换行(CRLFCRLF)。
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 一个包含双回车和换行字节的只读字节序列。
|
||||
/// </value>
|
||||
public static ReadOnlySpan<byte> CRLFCRLF => "\r\n\r\n"u8;
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 "&" 符号。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
private static readonly byte[] s_http11Response = "HTTP/1.1 "u8.ToArray();
|
||||
private static readonly byte[] s_http10Response = "HTTP/1.0 "u8.ToArray();
|
||||
|
||||
private static readonly string[] s_statusCodeCache = new string[600];
|
||||
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
|
||||
{
|
||||
writer.Write("&"u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 ":" 符号。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendColon<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write(":"u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 "=" 符号。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendEqual<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write("="u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 "HTTP" 字符串。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendHTTP<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write("HTTP"u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 "?" 符号。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendQuestionMark<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write("?"u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加回车换行符 "\r\n"。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendRn<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write(CRLF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加 "/" 符号。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendSlash<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write("/"u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加空格符。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendSpace<TWriter>(ref TWriter writer) where TWriter : IBytesWriter
|
||||
{
|
||||
writer.Write(StringExtension.DefaultSpaceUtf8Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加指定的 UTF-8 编码字符串。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
/// <param name="value">要追加的字符串。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendUtf8String<TWriter>(ref TWriter writer, string value) where TWriter : IBytesWriter
|
||||
{
|
||||
WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="IByteBlock"/> 中追加指定整数的十六进制表示。
|
||||
/// </summary>
|
||||
/// <typeparam name="TWriter">实现了 <see cref="IByteBlock"/> 的类型。</typeparam>
|
||||
/// <param name="writer">字节块实例。</param>
|
||||
/// <param name="value">要追加的整数值。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AppendHex<TWriter>(ref TWriter writer, int value) where TWriter : IBytesWriter
|
||||
{
|
||||
AppendUtf8String(ref writer, $"{value:X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字节是否为HTTP规范允许的空白字符(空格或制表符)
|
||||
/// </summary>
|
||||
/// <param name="b">要检查的字节</param>
|
||||
/// <returns>如果是空白字符返回true,否则返回false</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsWhitespace(byte b) => b == SPACE || b == TAB;
|
||||
|
||||
/// <summary>
|
||||
/// 高效的空白字符去除,避免使用通用的Trim方法
|
||||
/// </summary>
|
||||
/// <param name="span">要处理的字节跨度</param>
|
||||
/// <returns>去除前后空白字符后的字节跨度</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ReadOnlySpan<byte> TrimWhitespace(ReadOnlySpan<byte> span)
|
||||
{
|
||||
@@ -175,25 +134,26 @@ public static class TouchSocketHttpUtility
|
||||
var end = span.Length - 1;
|
||||
|
||||
while (start <= end && IsWhitespace(span[start]))
|
||||
{
|
||||
start++;
|
||||
}
|
||||
|
||||
while (end >= start && IsWhitespace(span[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)
|
||||
{
|
||||
#if NET9_0_OR_GREATER
|
||||
// 直接处理字节的URL解码
|
||||
Span<char> charBuffer = stackalloc char[urlSpan.Length];
|
||||
var charCount = Encoding.UTF8.GetChars(urlSpan, charBuffer);
|
||||
return Uri.UnescapeDataString(charBuffer.Slice(0, charCount));
|
||||
#else
|
||||
return Uri.UnescapeDataString(urlSpan.ToString(Encoding.UTF8));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
internal static string UnescapeDataString(ReadOnlySpan<char> urlSpan)
|
||||
@@ -203,7 +163,6 @@ public static class TouchSocketHttpUtility
|
||||
#else
|
||||
return Uri.UnescapeDataString(urlSpan.ToString());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
internal static int FindNextWhitespace(ReadOnlySpan<byte> span, int start)
|
||||
@@ -224,7 +183,6 @@ public static class TouchSocketHttpUtility
|
||||
{
|
||||
start++;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
@@ -240,7 +198,6 @@ public static class TouchSocketHttpUtility
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理没有值的键
|
||||
keySpan = kvSpan;
|
||||
valueSpan = [];
|
||||
}
|
||||
|
||||
@@ -210,7 +210,6 @@ public abstract class HttpClientBase : TcpClientBase, IHttpSession
|
||||
return;
|
||||
}
|
||||
|
||||
content.InternalTryComputeLength(out var contentLength);
|
||||
content.InternalBuildingHeader(request.Headers);
|
||||
request.BuildHeader(ref writer);
|
||||
|
||||
|
||||
@@ -20,22 +20,24 @@ namespace TouchSocket.Http;
|
||||
/// </summary>
|
||||
public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClient
|
||||
{
|
||||
private HttpContext m_httpContext;
|
||||
|
||||
private ServerHttpResponse m_serverHttpResponse;
|
||||
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>
|
||||
protected HttpSessionClient()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
internal ITransport InternalTransport => this.Transport;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,18 +67,6 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes
|
||||
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/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
@@ -103,4 +93,10 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes
|
||||
|
||||
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
|
||||
{
|
||||
public HttpServerDataHandlingAdapter(Func<ServerHttpRequest, Task> func)
|
||||
public HttpServerDataHandlingAdapter(ServerHttpRequest request, Func<ServerHttpRequest, Task> func)
|
||||
{
|
||||
this.m_func = func;
|
||||
this.m_requestRoot = request;
|
||||
}
|
||||
private ServerHttpRequest m_currentRequest;
|
||||
private ServerHttpRequest m_requestRoot;
|
||||
private readonly ServerHttpRequest m_requestRoot;
|
||||
private long m_surLen;
|
||||
private Task m_task;
|
||||
|
||||
@@ -61,8 +62,6 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd
|
||||
{
|
||||
throw new Exception($"此适配器必须适用于{nameof(IHttpService)}");
|
||||
}
|
||||
|
||||
this.m_requestRoot = new ServerHttpRequest(httpSessionClient);
|
||||
base.OnLoaded(owner);
|
||||
}
|
||||
|
||||
@@ -119,7 +118,7 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,18 +36,9 @@ public abstract class HttpContent
|
||||
/// <param name="header">HTTP头的接口实现</param>
|
||||
internal void InternalBuildingHeader(IHttpHeader header)
|
||||
{
|
||||
if (this.TryComputeLength(out var length))
|
||||
{
|
||||
header.TryAdd(HttpHeaders.ContentLength, length.ToString());
|
||||
}
|
||||
this.OnBuildingHeader(header);
|
||||
}
|
||||
|
||||
internal bool InternalTryComputeLength(out long length)
|
||||
{
|
||||
return this.TryComputeLength(out length);
|
||||
}
|
||||
|
||||
internal Task InternalWriteContent(PipeWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
return this.WriteContent(writer, cancellationToken);
|
||||
@@ -67,13 +58,6 @@ public abstract class HttpContent
|
||||
/// <param name="header">HTTP头的接口实现</param>
|
||||
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);
|
||||
|
||||
#region implicit
|
||||
|
||||
@@ -30,9 +30,11 @@ public class ReadonlyMemoryHttpContent : HttpContent
|
||||
{
|
||||
this.m_memory = memory;
|
||||
this.m_contentType = contentType;
|
||||
this.m_lengthString= this.m_memory.Length.ToString();
|
||||
}
|
||||
|
||||
private readonly string m_contentType;
|
||||
private readonly string m_lengthString;
|
||||
|
||||
/// <summary>
|
||||
/// 获取封装的只读内存。
|
||||
@@ -60,7 +62,7 @@ public class ReadonlyMemoryHttpContent : HttpContent
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
else if (TryComputeLength(out var length))
|
||||
{
|
||||
header.Add(HttpHeaders.ContentLength,length.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool TryComputeLength(out long length)
|
||||
private bool TryComputeLength(out long length)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -36,8 +36,9 @@ public abstract class MqttActor : DisposableObject, IOnlineClient
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.m_tokenSource.Cancel();
|
||||
this.m_tokenSource.Dispose();
|
||||
this.m_tokenSource.SafeCancel();
|
||||
this.m_tokenSource.SafeDispose();
|
||||
this.m_waitHandlePool.CancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,10 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient
|
||||
await base.TcpConnectAsync(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);
|
||||
}
|
||||
|
||||
@@ -149,6 +152,8 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient
|
||||
/// <inheritdoc/>
|
||||
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 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
|
||||
|
||||
MqttExtension.WriteSessionExpiryInterval(ref writer, this.SessionExpiryInterval);
|
||||
MqttExtension.WriteAuthenticationMethod(ref writer, this.AuthenticationMethod);
|
||||
MqttExtension.WriteAuthenticationData(ref writer, this.AuthenticationData.Span);
|
||||
MqttExtension.WriteReceiveMaximum(ref writer, this.ReceiveMaximum);
|
||||
MqttExtension.WriteTopicAliasMaximum(ref writer, this.TopicAliasMaximum);
|
||||
MqttExtension.WriteMaximumPacketSize(ref writer, this.MaximumPacketSize);
|
||||
MqttExtension.WriteRequestResponseInformation(ref writer, this.RequestResponseInformation);
|
||||
MqttExtension.WriteRequestProblemInformation(ref writer, this.RequestProblemInformation);
|
||||
MqttExtension.WriteUserProperties(ref writer, this.UserProperties);
|
||||
// 写入属性长度字段与属性内容
|
||||
var variableByteIntegerRecorder = new VariableByteIntegerRecorder();
|
||||
var propertiesWriter = this.CreateVariableWriter(ref writer);
|
||||
variableByteIntegerRecorder.CheckOut(ref propertiesWriter);
|
||||
MqttExtension.WriteSessionExpiryInterval(ref propertiesWriter, this.SessionExpiryInterval);
|
||||
MqttExtension.WriteAuthenticationMethod(ref propertiesWriter, this.AuthenticationMethod);
|
||||
MqttExtension.WriteAuthenticationData(ref propertiesWriter, this.AuthenticationData.Span);
|
||||
MqttExtension.WriteReceiveMaximum(ref propertiesWriter, this.ReceiveMaximum);
|
||||
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
|
||||
|
||||
MqttExtension.WriteMqttInt16String(ref writer, this.ClientId);
|
||||
if (this.WillFlag)
|
||||
{
|
||||
MqttExtension.WritePayloadFormatIndicator(ref writer, this.WillPayloadFormatIndicator);
|
||||
MqttExtension.WriteMessageExpiryInterval(ref writer, this.WillMessageExpiryInterval);
|
||||
MqttExtension.WriteResponseTopic(ref writer, this.WillResponseTopic);
|
||||
MqttExtension.WriteCorrelationData(ref writer, this.WillCorrelationData.Span);
|
||||
MqttExtension.WriteContentType(ref writer, this.WillContentType);
|
||||
MqttExtension.WriteWillDelayInterval(ref writer, this.WillDelayInterval);
|
||||
// 写入遗嘱属性长度字段与属性内容
|
||||
var willVariableByteIntegerRecorder = new VariableByteIntegerRecorder();
|
||||
var willPropertiesWriter = this.CreateVariableWriter(ref writer);
|
||||
willVariableByteIntegerRecorder.CheckOut(ref willPropertiesWriter);
|
||||
MqttExtension.WritePayloadFormatIndicator(ref willPropertiesWriter, this.WillPayloadFormatIndicator);
|
||||
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.WriteMqttInt16Memory(ref writer, this.WillPayload);
|
||||
MqttExtension.WriteUserProperties(ref writer, this.WillUserProperties);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
this.ClientId = MqttExtension.ReadMqttInt16String(ref reader);
|
||||
|
||||
@@ -24,7 +24,10 @@ public partial class MqttSubscribeMessage
|
||||
var variableByteIntegerRecorder = new VariableByteIntegerRecorder();
|
||||
var byteBlockWriter = this.CreateVariableWriter(ref writer);
|
||||
variableByteIntegerRecorder.CheckOut(ref byteBlockWriter);
|
||||
if (this.SubscriptionIdentifier > 0)
|
||||
{
|
||||
MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier);
|
||||
}
|
||||
MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties);
|
||||
variableByteIntegerRecorder.CheckIn(ref byteBlockWriter);
|
||||
writer.Advance(byteBlockWriter.Position);
|
||||
@@ -32,7 +35,13 @@ public partial class MqttSubscribeMessage
|
||||
foreach (var item in this.m_subscribeRequests)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
while (propertiesReader.TryRead(ref reader, out var mqttPropertyId))
|
||||
@@ -58,8 +67,7 @@ public partial class MqttSubscribeMessage
|
||||
break;
|
||||
}
|
||||
}
|
||||
//this.SubscriptionIdentifier = propertiesReader.SubscriptionIdentifier;
|
||||
//this.UserProperties = propertiesReader.UserProperties;
|
||||
|
||||
while (!this.EndOfByteBlock(reader))
|
||||
{
|
||||
var topic = MqttExtension.ReadMqttInt16String(ref reader);
|
||||
|
||||
@@ -429,6 +429,8 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession
|
||||
TargetHost = iPHost.Host
|
||||
};
|
||||
}
|
||||
|
||||
sslOption.TargetHost ??= iPHost.Host;
|
||||
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),
|
||||
NoDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty),
|
||||
Adapter = this.Config.GetValue(TouchSocketConfigExtension.TcpDataHandlingAdapterProperty),
|
||||
Backlog= this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty)
|
||||
};
|
||||
option.Backlog = this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty) ?? option.Backlog;
|
||||
optionList.Add(option);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public static class TouchSocketConfigExtension
|
||||
/// 挂起连接队列的最大长度,所需类型<see cref="int"/>
|
||||
/// </summary>
|
||||
[GeneratorProperty(TargetType = typeof(TouchSocketConfig))]
|
||||
public static readonly DependencyProperty<int?> BacklogProperty = new("Backlog", null);
|
||||
public static readonly DependencyProperty<int> BacklogProperty = new("Backlog", 100);
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认Id的获取方式,所需类型<see cref="Func{T, TResult}"/>
|
||||
|
||||
@@ -117,9 +117,10 @@ public class ReconnectionOption<TClient>
|
||||
|
||||
while (this.MaxRetryCount < 0 || attempts < this.MaxRetryCount)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
if (client.DisposedValue||cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (client.PauseReconnection)
|
||||
{
|
||||
if (this.LogReconnection)
|
||||
@@ -150,9 +151,6 @@ public class ReconnectionOption<TClient>
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
this.OnFailed?.Invoke(client, attempts, ex);
|
||||
|
||||
if (this.LogReconnection)
|
||||
|
||||
@@ -20,27 +20,27 @@ public class TcpListenOption
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 监听地址
|
||||
/// </summary>
|
||||
public IPHost IpHost { get; set; }
|
||||
public IPHost IpHost { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用地址复用
|
||||
/// </summary>
|
||||
public bool ReuseAddress { get; set; }
|
||||
public bool ReuseAddress { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Tcp处理并发连接时最大半连接队列
|
||||
/// </summary>
|
||||
public int Backlog { get; set; } = 100;
|
||||
public int Backlog { get; init; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 禁用延迟发送
|
||||
/// </summary>
|
||||
public bool NoDelay { get; set; } = true;
|
||||
public bool NoDelay { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用ssl加密
|
||||
@@ -50,10 +50,10 @@ public class TcpListenOption
|
||||
/// <summary>
|
||||
/// 用于Ssl加密的证书
|
||||
/// </summary>
|
||||
public ServiceSslOption ServiceSslOption { get; set; }
|
||||
public ServiceSslOption ServiceSslOption { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置Tcp适配器
|
||||
/// </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;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Result> CloseAsync(string msg, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user