- Initial commit

This commit is contained in:
Timur Kozanov
2026-07-03 05:02:26 +03:00
commit 374ff1e689
4080 changed files with 388870 additions and 0 deletions

View File

@ -0,0 +1,182 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
{
public sealed class FrameworkTLSByteForwarder : Stream, ITCPStreamerContentConsumer
{
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length { get { return this._length; } }
private long _length;
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
private long _maxBufferSize;
private TCPStreamer _streamer;
private LoggingContext _context;
private ITCPStreamerContentConsumer _contentConsumer;
private Queue<BufferSegment> _segmentsToReadFrom = new Queue<BufferSegment>(8);
private AutoResetEvent _are = new AutoResetEvent(false);
private ReaderWriterLockSlim _rws = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
private BufferSegment _currentReadSegment = BufferSegment.Empty;
public FrameworkTLSByteForwarder(TCPStreamer streamer, ITCPStreamerContentConsumer contentConsumer, long maxBufferSize, LoggingContext context)
{
this._streamer = streamer;
this._streamer.ContentConsumer = this;
this._contentConsumer = contentConsumer;
this._context = context;
this._maxBufferSize = maxBufferSize;
}
public void /*ITCPStreamerContentConsumer.*/ Write(BufferSegment buffer)
{
using var _ = new AutoReleaseBuffer(buffer);
this.Write(buffer.Data, buffer.Offset, buffer.Count);
}
int _pullContentInProgress;
void PullContentFromStreamer()
{
//using var _ = new WriteLock(this._rws);
if (Interlocked.CompareExchange(ref _pullContentInProgress, 1, 0) != 0)
return;
try
{
while (this._streamer.Length > 0 && this._length < this.MaxBufferSize)
{
var tmp = this._streamer.DequeueReceived();
if (tmp.Count <= 0)
{
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"DequeueReceived({tmp}) !", this._context);
BufferPool.Release(tmp);
return;
}
this._segmentsToReadFrom.Enqueue(tmp);
this._length += tmp.Count;
}
}
finally
{
Interlocked.Exchange(ref _pullContentInProgress, 0);
}
}
public void /*ITCPStreamerContentConsumer.*/ OnContent(TCPStreamer streamer)
{
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"OnContent({streamer?.Length})", this._context);
PullContentFromStreamer();
this._are?.Set();
this._contentConsumer?.OnContent(streamer);
}
public void /*ITCPStreamerContentConsumer.*/ OnConnectionClosed(TCPStreamer streamer) => this._contentConsumer?.OnConnectionClosed(streamer);
public void /*ITCPStreamerContentConsumer.*/ OnError(TCPStreamer streamer, Exception ex) => this._contentConsumer?.OnError(streamer, ex);
// Called by SslStream.Read expecting encrypted content
public override int Read(byte[] buffer, int offset, int count)
{
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Read({offset}, {count})", this._context);
PullContentFromStreamer();
int sumReadCount = 0;
while (this._currentReadSegment == BufferSegment.Empty && this._segmentsToReadFrom.Count == 0)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"WaitOne() for new data!", this._context);
if (this.Length == 0)
this._are.WaitOne();
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"WaitOne() returned!", this._context);
}
while ((this._currentReadSegment != BufferSegment.Empty || this._segmentsToReadFrom.Count > 0) && count > 0)
{
if (this._currentReadSegment != BufferSegment.Empty)
{
int readCount = Math.Min(count, this._currentReadSegment.Count);
Array.Copy(this._currentReadSegment.Data, this._currentReadSegment.Offset, buffer, offset, readCount);
offset += readCount;
count -= readCount;
sumReadCount += readCount;
if (this._currentReadSegment.Count <= readCount)
this._currentReadSegment = BufferSegment.Empty;
else
this._currentReadSegment = this._currentReadSegment.Slice(this._currentReadSegment.Offset + readCount);
}
else
{
this._currentReadSegment = this._segmentsToReadFrom.Dequeue();
}
}
this._length -= sumReadCount;
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Read() returns with readCount: {sumReadCount}, remaining: {this._length}", this._context);
return sumReadCount;
}
// Called by SslStream.Write with encrypted payload
public override void Write(byte[] buffer, int offset, int count)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Write({buffer.AsBuffer(offset, count)})", this._context);
var queued = BufferPool.Get(count, true, this._context);
Array.Copy(buffer, offset, queued, 0, count);
this._streamer.EnqueueToSend(queued.AsBuffer(count));
}
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this._are?.Dispose();
this._are = null;
this._rws?.Dispose();
this._rws = null;
this._streamer?.Dispose();
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c78a502070c09ea489f93f800efb2e28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/FrameworkTLSByteForwarder.cs
uploadId: 783279

View File

@ -0,0 +1,297 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Best.HTTP.Hosts.Connections;
using Best.HTTP.Hosts.Settings;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.Streams;
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
{
/*
* --> FrameworkTLSStream.Write => SslStream.Write => TLSByteForwarder.Write => TCPStream.EnqueueToSend
*
* --> TLSByteForwarder.OnContent => SslStream.Read => FrameworkTLSStream.Read
* */
public sealed class FrameworkTLSStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
{
public Action<FrameworkTLSStream, TCPStreamer, string /*negotiated appplication protocol*/, Exception> OnNegotiated;
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
private long _maxBufferSize;
private string _targetHost;
private TCPStreamer _streamer;
private FrameworkTLSByteForwarder _forwarder;
private SslStream _sslStream;
private LoggingContext _context;
private HostSettings _hostSettings;
private int peek_listIdx;
private int peek_pos;
#if UNITY_2021_2_OR_NEWER
private static bool loggedWarning = false;
#endif
private object locker = new object();
public FrameworkTLSStream(TCPStreamer streamer, string targetHost, HostSettings hostSettings)
{
this._streamer = streamer;
this._targetHost = targetHost;
this._context = new LoggingContext(this);
this._context.Add("streamer", this._streamer.Context);
this._hostSettings = hostSettings;
this._maxBufferSize = hostSettings.LowLevelConnectionSettings.ReadBufferSize;
this._forwarder = new FrameworkTLSByteForwarder(this._streamer, this, this.MaxBufferSize, this._context);
this._sslStream = new SslStream(this._forwarder,
leaveInnerStreamOpen: false,
OnUserCertificationValidation,
OnUserCertificationSelection,
EncryptionPolicy.RequireEncryption);
this._sslStream.BeginAuthenticateAsClient(targetHost,
null,
this._hostSettings.TLSSettings.FrameworkTLSSettings.TlsVersions,
true,
OnAuthenticatedAsClient,
null);
}
private bool OnUserCertificationValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnUserCertificationValidation)}({sender}, {certificate}, {chain}, {sslPolicyErrors})", this._context);
var validator = this._hostSettings.TLSSettings.FrameworkTLSSettings.CertificationValidator;
if (validator == null)
return FrameworkTLSSettings.DefaultCertificationValidator(_targetHost, certificate, chain, sslPolicyErrors);
return validator(this._targetHost, certificate, chain, sslPolicyErrors);
}
private X509Certificate OnUserCertificationSelection(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnUserCertificationSelection)}({sender}, {targetHost}, {localCertificates}, {remoteCertificate}, {acceptableIssuers?.Length})", this._context);
return this._hostSettings.TLSSettings.FrameworkTLSSettings.ClientCertificationProvider?.Invoke(targetHost, localCertificates, remoteCertificate, acceptableIssuers);
}
private void OnAuthenticatedAsClient(IAsyncResult ar)
{
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}()", this._context);
try
{
this._sslStream.EndAuthenticateAsClient(ar);
string alpn = string.Empty;
#if UNITY_2021_2_OR_NEWER
try
{
alpn = this._sslStream.NegotiatedApplicationProtocol.ToString();
}
catch (Exception ex)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}() - NegotiatedApplicationProtocol", ex, this._context);
if (!loggedWarning)
{
loggedWarning = true;
HTTPManager.Logger.Warning(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}(): SslStream's NegotiatedApplicationProtocol inaccessible! Using http/1.", this._context);
}
}
#endif
if (string.IsNullOrEmpty(alpn))
alpn = HTTPProtocolFactory.W3C_HTTP1;
CallOnNegotiated(alpn, null);
BeginRead();
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}()", ex, this._context);
CallOnNegotiated(null, ex);
}
}
bool CallOnNegotiated(string alpn, Exception error)
{
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"CallOnNegotiated(\"{alpn}\", {error})", this._context);
var callback = Interlocked.CompareExchange(ref this.OnNegotiated, null, this.OnNegotiated);
if (callback != null)
{
try
{
callback(this, this._streamer, alpn, error);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), "OnContent - OnNegotiated", ex, this._streamer.Context);
}
}
return callback != null;
}
public override void BeginPeek()
{
peek_listIdx = 0;
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
}
public override int PeekByte()
{
if (base.bufferList.Count == 0)
return -1;
var segment = base.bufferList[this.peek_listIdx];
if (peek_pos >= segment.Offset + segment.Count)
{
if (base.bufferList.Count <= this.peek_listIdx + 1)
return -1;
segment = base.bufferList[++this.peek_listIdx];
this.peek_pos = segment.Offset;
}
return segment.Data[this.peek_pos++];
}
public void OnContent(TCPStreamer streamer)
{
if (this._sslStream.IsAuthenticated)
BeginRead();
}
int _reading;
private void BeginRead()
{
if (Interlocked.CompareExchange(ref _reading, 1, 0) != 0)
{
//HTTPManager.Logger.Warning(nameof(FrameworkTLSStream), $"{nameof(BeginRead)}() - already reading!", this._context);
return;
}
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(BeginRead)}()", this._context);
var buffer = BufferPool.Get(Math.Min(this.MaxBufferSize, 1 * 1024 * 1024), true, this._context);
this._sslStream.ReadAsync(buffer, 0, buffer.Length)
//.AsTask()
.ContinueWith((ti) =>
{
int readCount = 0;
try
{
readCount = ti.Result;
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(OnRead)}({readCount}, {this.Length})", this._context);
if (readCount > 0)
{
lock (locker)
base.Write(buffer.AsBuffer(readCount));
}
this.Consumer?.OnContent();
}
finally
{
Interlocked.Exchange(ref _reading, 0);
if (readCount > 0)
BeginRead();
}
})
.ConfigureAwait(false);
/*IAsyncResult ar = null;
try
{
do
{
var buffer = BufferPool.Get(Math.Min(this.MaxBufferSize, 1 * 1024 * 1024), true, this._context);
ar = this._sslStream.BeginRead(buffer, 0, buffer.Length, OnRead, buffer);
} while (ar != null && ar.CompletedSynchronously);
}
finally
{
Interlocked.Exchange(ref _reading, 0);
//if (ar is not null && ar.CompletedSynchronously)
// BeginRead();
}*/
}
private void OnRead(IAsyncResult ar)
{
try
{
var readCount = this._sslStream.EndRead(ar);
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(OnRead)}({readCount}, {ar.CompletedSynchronously})", this._context);
if (readCount > 0)
{
var buffer = ar.AsyncState as byte[];
lock (locker)
base.Write(buffer.AsBuffer(readCount));
this.Consumer?.OnContent();
}
// This call might fail if the read completed synchronously.
BeginRead();
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"EndRead", ex, this._context);
}
finally
{
}
}
public void OnConnectionClosed(TCPStreamer streamer) => this.Consumer?.OnConnectionClosed();
public void OnError(TCPStreamer streamer, Exception ex) => this.Consumer?.OnError(ex);
public override int Read(byte[] buffer, int offset, int count) { lock (locker) return base.Read(buffer, offset, count); }
public override void Write(byte[] buffer, int offset, int count) => this._sslStream.Write(buffer, offset, count);
public override void Write(BufferSegment bufferSegment)
{
using var _ = new AutoReleaseBuffer(bufferSegment);
this.Write(bufferSegment.Data, bufferSegment.Offset, bufferSegment.Count);
}
public override void Flush() => this._sslStream.Flush();
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this._sslStream?.Dispose();
this._sslStream = null;
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1a8f61b94735e634da61bc8b1324b56f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/FrameworkTLSStream.cs
uploadId: 783279

View File

@ -0,0 +1,300 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.Streams;
using Best.HTTP.Shared.TLS;
using System;
using System.Threading;
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
{
public sealed class NonblockingBCTLSStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
{
public Action<NonblockingBCTLSStream, TCPStreamer, AbstractTls13Client, Exception> OnNegotiated;
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
private long _maxBufferSize;
private TlsClientProtocol _tlsClientProtocol;
private AbstractTls13Client _tlsClient;
//private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
private object locker = new object();
private TCPStreamer _streamer;
private uint _sendBufferSize;
private bool _disposeStreamer;
private int peek_listIdx;
private int peek_pos;
private bool _disposed;
public NonblockingBCTLSStream(TCPStreamer streamer, TlsClientProtocol tlsClientProtocol, AbstractTls13Client tlsClient, bool disposeStreamer, uint maxBufferSize)
{
this._streamer = streamer;
this._streamer.ContentConsumer = this;
this._disposeStreamer = disposeStreamer;
// Maximize buffer use
this._sendBufferSize = this._streamer.MaxBufferedWriteAmount;
this._tlsClientProtocol = tlsClientProtocol;
this._tlsClient = tlsClient;
this.Write(null, 0, 0);
if (streamer.IsConnectionClosed)
CallOnNegotiated(new Exception("Connection closed before TLS negotiation started!"));
this._maxBufferSize = maxBufferSize;
}
public override void BeginPeek()
{
lock (this.locker)
{
peek_listIdx = 0;
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
}
}
public override int PeekByte()
{
lock (this.locker)
{
if (base.bufferList.Count == 0)
return -1;
var segment = base.bufferList[this.peek_listIdx];
if (peek_pos >= segment.Offset + segment.Count)
{
if (base.bufferList.Count <= this.peek_listIdx + 1)
return -1;
segment = base.bufferList[++this.peek_listIdx];
this.peek_pos = segment.Offset;
}
return segment.Data[this.peek_pos++];
}
}
// Called when content from the server is available
public void OnContent(TCPStreamer streamer)
{
lock (this.locker)
{
var socket = this._streamer?.Socket;
// Ignore content after a TLS client protocol closure (it can happen because of an error, but the server still pumping data to the client).
if (this._disposed || socket == null || this._tlsClientProtocol.IsClosed)
{
if (this._tlsClientProtocol.IsHandshaking)
CallOnNegotiated(new Exception("Connection closed while TLS negotiation is in progress!"));
return;
}
try
{
PullContentFromStreamer();
}
catch (Exception ex)
{
if (!CallOnNegotiated(ex))
this.Consumer?.OnError(ex);
}
// There's no read/write when it's still hanshaking, so we have to simulate one.
if (this._tlsClientProtocol.IsHandshaking)
{
this.Write(null, 0, 0);
return;
}
else
CallOnNegotiated(null);
// Call OnContent only if we have something to offer.
if (this.Length > 0)
this.Consumer?.OnContent();
}
}
public void OnConnectionClosed(TCPStreamer streamer)
{
var consumer = this.Consumer;
if (consumer != null)
consumer.OnConnectionClosed();
else
CallOnNegotiated(new Exception("TCP Connection closed during TLS negotiation!"));
}
public void OnError(TCPStreamer streamer, Exception ex)
{
var consumer = this.Consumer;
if (consumer != null)
consumer.OnError(ex);
else
CallOnNegotiated(ex);
}
// TODO: It can throw an exception (for example in case of a bad record mac :/), we have to
// 1.) handle it
// 2.) report to the consumer (through an OnError call)
// 3.) prevent other read/write attempts.
private void PullContentFromStreamer()
{
while (!this._disposed && this._streamer.Length > 0 && this._length < this.MaxBufferSize)
{
var tmp = this._streamer.DequeueReceived();
if (tmp.Count <= 0)
{
BufferPool.Release(tmp);
return;
}
try
{
this._tlsClientProtocol.OfferInput(tmp.Data, tmp.Offset, tmp.Count);
// each call of OfferInput might generate data (for example alerts) to send to the remote peer!
this.Write(null, 0, 0);
}
catch (Exception ex)
{
BufferPool.Release(tmp);
// each call of OfferInput might generate data (for example alerts) to send to the remote peer!
this.Write(null, 0, 0);
CallOnNegotiated(ex);
throw;
}
int available = this._tlsClientProtocol.GetAvailableInputBytes();
byte[] readBuffer = tmp.Data;
while (available > 0)
{
if (readBuffer == null)
readBuffer = BufferPool.Get(available, true, this._streamer.Context);
var readCount = this._tlsClientProtocol.ReadInput(readBuffer, 0, readBuffer.Length);
base.Write(readBuffer.AsBuffer(readCount));
readBuffer = null;
available = this._tlsClientProtocol.GetAvailableInputBytes();
}
BufferPool.Release(readBuffer);
}
}
public override int Read(byte[] buffer, int offset, int count)
{
lock (this.locker)
{
var readCount = base.Read(buffer, offset, count);
// pull content from the streamer, if buffered amount is less then the desired.
if (base.Length <= this.MaxBufferSize)
{
try
{
PullContentFromStreamer();
}
catch (Exception ex)
{
this.Consumer.OnError(ex);
}
}
return readCount;
}
}
// write -> tls encoding -> TCP streamer
public override void Write(byte[] buffer, int offset, int count)
{
lock (this.locker)
{
var streamer = this._streamer;
if (streamer == null)
return;
if (buffer != null && count > 0)
this._tlsClientProtocol.WriteApplicationData(buffer, offset, count);
int available = 0;
available = this._tlsClientProtocol.GetAvailableOutputBytes();
while (available > 0)
{
var tmp = BufferPool.Get(Math.Min(available, this._sendBufferSize), true, streamer.Context);
int readCount = 0;
try
{
readCount = this._tlsClientProtocol.ReadOutput(tmp, 0, tmp.Length);
}
catch
{
BufferPool.Release(tmp);
throw;
}
streamer.EnqueueToSend(tmp.AsBuffer(readCount));
available = this._tlsClientProtocol.GetAvailableOutputBytes();
}
}
}
public override void Write(BufferSegment bufferSegment)
{
lock (this.locker)
{
using var _ = new AutoReleaseBuffer(bufferSegment);
Write(bufferSegment.Data, bufferSegment.Offset, bufferSegment.Count);
}
}
bool CallOnNegotiated(Exception error)
{
var callback = Interlocked.CompareExchange(ref this.OnNegotiated, null, this.OnNegotiated);
if (callback != null)
{
try
{
callback(this, this._streamer, this._tlsClient, error);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(NonblockingBCTLSStream), "CallOnNegotiated", ex, this._streamer.Context);
}
}
return callback != null;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (this._disposed)
return;
HTTPManager.Logger.Verbose(nameof(NonblockingBCTLSStream), "Dispose", this._streamer.Context);
this._disposed = true;
this._tlsClientProtocol?.Close();
if (this._disposeStreamer)
this._streamer?.Dispose();
this._streamer = null;
//this._rwLock?.Dispose();
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: db49d73dab50cad4a92350516ec20d8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingBCTLSStream.cs
uploadId: 783279

View File

@ -0,0 +1,153 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.Streams;
using System;
using System.Threading;
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
{
/// <summary>
/// A non-blocking-read stream over a TCPStreamer that buffers the received bytes from the network in a Peekable stream.
/// </summary>
public sealed class NonblockingTCPStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
{
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
private long _maxBufferSize;
private TCPStreamer _streamer;
private bool _disposeStreamer;
private int peek_listIdx;
private int peek_pos;
private object _locker = new object();
public NonblockingTCPStream(TCPStreamer streamer, bool disposeStreamer, uint maxBufferSize)
{
this._streamer = streamer;
this._streamer.ContentConsumer = this;
this._disposeStreamer = disposeStreamer;
this._maxBufferSize = maxBufferSize;
}
public override void BeginPeek()
{
lock (this._locker)
{
peek_listIdx = 0;
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
}
}
public override int PeekByte()
{
lock (this._locker)
{
if (base.bufferList.Count == 0)
return -1;
var segment = base.bufferList[this.peek_listIdx];
if (peek_pos >= segment.Offset + segment.Count)
{
if (base.bufferList.Count <= this.peek_listIdx + 1)
return -1;
segment = base.bufferList[++this.peek_listIdx];
this.peek_pos = segment.Offset;
}
return segment.Data[this.peek_pos++];
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
lock (this._locker)
{
if (this._streamer != null)
this._streamer.ContentConsumer = null;
if (this._disposeStreamer)
this._streamer?.Dispose();
this._streamer = null;
}
}
// PeekableStream's default implementation of write would place the buffer into its inner segment list,
// but here we want to send it to the server instead.
public override void Write(byte[] buffer, int offset, int count) => this._streamer.EnqueueToSend(buffer.CopyAsBuffer(offset, count));
public override void Write(BufferSegment buffer) => this._streamer.EnqueueToSend(buffer);
public override int Read(byte[] buffer, int offset, int count)
{
lock (this._locker)
{
int readCount = base.Read(buffer, offset, count);
// pull content from the streamer, if buffered amount is less then the desired.
if (base.Length <= this.MaxBufferSize)
{
DequeueFromStreamer();
this._streamer.BeginReceive();
}
return readCount;
}
}
public void OnContent(TCPStreamer streamer)
{
lock (this._locker)
{
DequeueFromStreamer();
var consumer = this.Consumer;
if (consumer != null)
consumer?.OnContent();
else
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnContent)}({streamer}) - No consumer to call OnContent on!", streamer.Context);
}
}
public void OnConnectionClosed(TCPStreamer streamer)
{
var consumer = this.Consumer;
if (consumer != null)
consumer?.OnConnectionClosed();
else
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnConnectionClosed)}({streamer}) - No consumer to call OnConnectionClosed on!", streamer.Context);
}
public void OnError(TCPStreamer streamer, Exception ex)
{
var consumer = this.Consumer;
if (consumer != null)
consumer?.OnError(ex);
else
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnError)}({streamer}, {ex}) - No consumer to call OnError on!", streamer.Context);
}
void DequeueFromStreamer()
{
if (this._streamer == null)
return;
while (this._streamer.Length > 0 && this._length < this.MaxBufferSize)
{
var segment = this._streamer.DequeueReceived();
if (segment.Count <= 0)
return;
base.Write(segment);
}
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e4c36b588df49d147981ba1e73401b7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingTCPStream.cs
uploadId: 783279

View File

@ -0,0 +1,163 @@
using System;
using System.Threading;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Streams;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
{
public sealed class NonblockingUnderlyingStream : PeekableContentProviderStream
{
private System.IO.Stream _stream;
private int _receiving;
private uint _maxBufferSize;
private LoggingContext _context;
private object _locker = new object();
private int peek_listIdx;
private int peek_pos;
public NonblockingUnderlyingStream(System.IO.Stream stream, uint maxBufferSize, LoggingContext context)
{
this._stream = stream;
this._context = context;
this._maxBufferSize = maxBufferSize;
if (!stream.CanRead)
throw new NotSupportedException("Stream.Read");
}
public override int Read(byte[] buffer, int offset, int count)
{
lock (this._locker)
{
int readCount = base.Read(buffer, offset, count);
if (base.Length <= this._maxBufferSize)
BeginReceive();
return readCount;
}
}
public void BeginReceive()
{
if (base._length < this._maxBufferSize && Interlocked.CompareExchange(ref this._receiving, 1, 0) == 0 && this._stream.CanRead)
{
long readCount = this._maxBufferSize - base._length;
var readBuffer = BufferPool.Get(readCount, true, this._context);
try
{
var ar = this._stream.BeginRead(readBuffer, 0, (int)readCount, OnReceived, readBuffer);
if (ar.CompletedSynchronously)
HTTPManager.Logger.Warning(nameof(NonblockingUnderlyingStream), $"CompletedSynchronously!", this._context);
}
catch (Exception ex)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), $"{nameof(this._stream.BeginRead)}", ex, this._context);
BufferPool.Release(Interlocked.Exchange(ref readBuffer, null));
this.Consumer.OnError(ex);
}
}
}
private void OnReceived(IAsyncResult ar)
{
int readCount = 0;
bool isClosed = true;
var readBuffer = ar.AsyncState as byte[];
try
{
readCount = this._stream.EndRead(ar);
isClosed = readCount <= 0;
if (!isClosed)
{
lock (this._locker)
base.Write(readBuffer.AsBuffer(0, readCount));
try
{
this.Consumer?.OnContent();
}
catch (Exception e)
{
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), "ContentConsumer.OnContent", e, this._context);
}
}
}
catch (Exception ex)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), $"{nameof(OnReceived)}", ex, this._context);
}
finally
{
if (!isClosed)
{
Interlocked.Exchange(ref this._receiving, 0);
BeginReceive();
}
else
{
BufferPool.Release(readBuffer);
try
{
this.Consumer?.OnConnectionClosed();
}
catch (Exception e)
{
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), "Consumer.OnConnectionClosed", e, this._context);
}
}
}
}
public override void BeginPeek()
{
lock (this._locker)
{
peek_listIdx = 0;
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
}
}
public override int PeekByte()
{
lock (this._locker)
{
if (base.bufferList.Count == 0)
return -1;
var segment = base.bufferList[this.peek_listIdx];
if (peek_pos >= segment.Offset + segment.Count)
{
if (base.bufferList.Count <= this.peek_listIdx + 1)
return -1;
segment = base.bufferList[++this.peek_listIdx];
this.peek_pos = segment.Offset;
}
return segment.Data[this.peek_pos++];
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this._stream.Dispose();
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: efbc3c9e712aef64b8f0de50c6fa0ad5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267636
packageName: Best HTTP
packageVersion: 3.0.17
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingUnderlyingStream.cs
uploadId: 783279