2024-02-01 19:06:51 +08:00
using System ;
2024-01-26 19:15:07 +08:00
using System.Collections.Generic ;
using System.IO ;
using System.Text ;
#if ! NETFX_CORE | | UNITY_EDITOR
using System.Net.Sockets ;
#endif
using UnityEngine ;
namespace BestHTTP
{
#if ! BESTHTTP_DISABLE_CACHING
using BestHTTP.Caching ;
#endif
using BestHTTP.Extensions ;
#if ! BESTHTTP_DISABLE_COOKIES
using BestHTTP.Cookies ;
#endif
2024-02-01 19:06:51 +08:00
using System.Threading ;
using BestHTTP.Core ;
using BestHTTP.PlatformSupport.Memory ;
using BestHTTP.Logger ;
using BestHTTP.Timings ;
2024-01-26 19:15:07 +08:00
public class HTTPResponse : IDisposable
{
internal const byte CR = 13 ;
internal const byte LF = 10 ;
2024-02-01 19:06:51 +08:00
/// <summary>
/// Minimum size of the read buffer.
/// </summary>
public static int MinReadBufferSize = 16 * 1024 ;
2024-01-26 19:15:07 +08:00
#region Public Properties
public int VersionMajor { get ; protected set ; }
2024-02-01 19:06:51 +08:00
2024-01-26 19:15:07 +08:00
public int VersionMinor { get ; protected set ; }
/// <summary>
/// The status code that sent from the server.
/// </summary>
public int StatusCode { get ; protected set ; }
/// <summary>
/// Returns true if the status code is in the range of [200..300[ or 304 (Not Modified)
/// </summary>
public bool IsSuccess { get { return ( this . StatusCode > = 200 & & this . StatusCode < 300 ) | | this . StatusCode = = 304 ; } }
/// <summary>
/// The message that sent along with the StatusCode from the server. You can check it for errors from the server.
/// </summary>
public string Message { get ; protected set ; }
/// <summary>
/// True if it's a streamed response.
/// </summary>
public bool IsStreamed { get ; protected set ; }
2024-02-01 19:06:51 +08:00
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
/// <summary>
/// Indicates that the response body is read from the cache.
/// </summary>
public bool IsFromCache { get ; internal set ; }
/// <summary>
/// Provides information about the file used for caching the request.
/// </summary>
public HTTPCacheFileInfo CacheFileInfo { get ; internal set ; }
/// <summary>
/// Determines if this response is only stored to cache.
2024-02-01 19:06:51 +08:00
/// If both IsCacheOnly and IsStreamed are true, OnStreamingData isn't called.
2024-01-26 19:15:07 +08:00
/// </summary>
public bool IsCacheOnly { get ; private set ; }
#endif
2024-02-01 19:06:51 +08:00
/// <summary>
/// True, if this is a response for a HTTPProxy request.
/// </summary>
public bool IsProxyResponse { get ; private set ; }
2024-01-26 19:15:07 +08:00
/// <summary>
/// The headers that sent from the server.
/// </summary>
public Dictionary < string , List < string > > Headers { get ; protected set ; }
/// <summary>
/// The data that downloaded from the server. All Transfer and Content encodings decoded if any(eg. chunked, gzip, deflate).
/// </summary>
public byte [ ] Data { get ; internal set ; }
/// <summary>
/// The normal HTTP protocol is upgraded to an other.
/// </summary>
public bool IsUpgraded { get ; protected set ; }
#if ! BESTHTTP_DISABLE_COOKIES
/// <summary>
/// The cookies that the server sent to the client.
/// </summary>
public List < Cookie > Cookies { get ; internal set ; }
#endif
/// <summary>
/// Cached, converted data.
/// </summary>
protected string dataAsText ;
/// <summary>
/// The data converted to an UTF8 string.
/// </summary>
public string DataAsText
{
get
{
if ( Data = = null )
return string . Empty ;
if ( ! string . IsNullOrEmpty ( dataAsText ) )
return dataAsText ;
return dataAsText = Encoding . UTF8 . GetString ( Data , 0 , Data . Length ) ;
}
}
/// <summary>
/// Cached converted data.
/// </summary>
protected Texture2D texture ;
/// <summary>
/// The data loaded to a Texture2D.
/// </summary>
public Texture2D DataAsTexture2D
{
get
{
if ( Data = = null )
return null ;
if ( texture ! = null )
return texture ;
2024-02-01 19:06:51 +08:00
texture = new Texture2D ( 1 , 1 , TextureFormat . RGBA32 , false ) ;
texture . LoadImage ( Data , true ) ;
2024-01-26 19:15:07 +08:00
return texture ;
}
}
/// <summary>
/// True if the connection's stream will be closed manually. Used in custom protocols (WebSocket, EventSource).
/// </summary>
public bool IsClosedManually { get ; protected set ; }
2024-02-01 19:06:51 +08:00
/// <summary>
/// IProtocol.LoggingContext implementation.
/// </summary>
public LoggingContext Context { get ; private set ; }
/// <summary>
/// Count of streaming data fragments sitting in the HTTPManager's request event queue.
/// </summary>
#if UNITY_EDITOR
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
#endif
internal long UnprocessedFragments ;
2024-01-26 19:15:07 +08:00
#endregion
#region Internal Fields
internal HTTPRequest baseRequest ;
#endregion
#region Protected Properties And Fields
protected Stream Stream ;
protected byte [ ] fragmentBuffer ;
protected int fragmentBufferDataLength ;
#if ! BESTHTTP_DISABLE_CACHING
protected Stream cacheStream ;
#endif
protected int allFragmentSize ;
#endregion
2024-02-01 19:06:51 +08:00
protected HTTPResponse ( HTTPRequest request , bool isFromCache )
2024-01-26 19:15:07 +08:00
{
this . baseRequest = request ;
2024-02-01 19:06:51 +08:00
#if ! BESTHTTP_DISABLE_CACHING
this . IsFromCache = isFromCache ;
#endif
this . Context = new LoggingContext ( this ) ;
this . Context . Add ( "BaseRequest" , request . Context ) ;
this . Context . Add ( "IsFromCache" , isFromCache ) ;
}
public HTTPResponse ( HTTPRequest request , Stream stream , bool isStreamed , bool isFromCache , bool isProxyResponse = false )
{
this . baseRequest = request ;
this . Stream = stream ;
2024-01-26 19:15:07 +08:00
this . IsStreamed = isStreamed ;
2024-02-01 19:06:51 +08:00
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
this . IsFromCache = isFromCache ;
this . IsCacheOnly = request . CacheOnly ;
#endif
2024-02-01 19:06:51 +08:00
this . IsProxyResponse = isProxyResponse ;
2024-01-26 19:15:07 +08:00
this . IsClosedManually = false ;
2024-02-01 19:06:51 +08:00
this . Context = new LoggingContext ( this ) ;
this . Context . Add ( "BaseRequest" , request . GetHashCode ( ) ) ;
this . Context . Add ( "IsStreamed" , isStreamed ) ;
this . Context . Add ( "IsFromCache" , isFromCache ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
public bool Receive ( long forceReadRawContentLength = - 1 , bool readPayloadData = true , bool sendUpgradedEvent = true )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( this . baseRequest . IsCancellationRequested )
return false ;
2024-01-26 19:15:07 +08:00
string statusLine = string . Empty ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "Receive. forceReadRawContentLength: '{0:N0}', readPayloadData: '{1:N0}'" , forceReadRawContentLength , readPayloadData ) ) ;
// On WP platform we aren't able to determined sure enough whether the tcp connection is closed or not.
// So if we get an exception here, we need to recreate the connection.
try
{
// Read out 'HTTP/1.1' from the "HTTP/1.1 {StatusCode} {Message}"
statusLine = ReadTo ( Stream , ( byte ) ' ' ) ;
}
catch
{
2024-02-01 19:06:51 +08:00
if ( baseRequest . IsCancellationRequested )
return false ;
if ( baseRequest . Retries > = baseRequest . MaxRetries )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
HTTPManager . Logger . Warning ( "HTTPResponse" , "Failed to read Status Line! Retry is enabled, returning with false." , this . Context , this . baseRequest . Context ) ;
2024-01-26 19:15:07 +08:00
return false ;
}
2024-02-01 19:06:51 +08:00
HTTPManager . Logger . Warning ( "HTTPResponse" , "Failed to read Status Line! Retry is disabled, re-throwing exception." , this . Context , this . baseRequest . Context ) ;
2024-01-26 19:15:07 +08:00
throw ;
}
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "Status Line: '{0}'" , statusLine ) ) ;
if ( string . IsNullOrEmpty ( statusLine ) )
{
2024-02-01 19:06:51 +08:00
if ( baseRequest . Retries > = baseRequest . MaxRetries )
2024-01-26 19:15:07 +08:00
return false ;
2024-02-01 19:06:51 +08:00
throw new Exception ( "Network error! TCP Connection got closed before receiving any data!" ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
if ( ! this . IsProxyResponse )
baseRequest . Timing . Add ( TimingEventNames . Waiting_TTFB ) ;
2024-01-26 19:15:07 +08:00
string [ ] versions = statusLine . Split ( new char [ ] { '/' , '.' } ) ;
this . VersionMajor = int . Parse ( versions [ 1 ] ) ;
this . VersionMinor = int . Parse ( versions [ 2 ] ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "HTTP Version: '{0}.{1}'" , this . VersionMajor . ToString ( ) , this . VersionMinor . ToString ( ) ) ) ;
int statusCode ;
string statusCodeStr = NoTrimReadTo ( Stream , ( byte ) ' ' , LF ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "Status Code: '{0}'" , statusCodeStr ) ) ;
2024-02-01 19:06:51 +08:00
if ( baseRequest . Retries > = baseRequest . MaxRetries )
2024-01-26 19:15:07 +08:00
statusCode = int . Parse ( statusCodeStr ) ;
else if ( ! int . TryParse ( statusCodeStr , out statusCode ) )
return false ;
this . StatusCode = statusCode ;
if ( statusCodeStr . Length > 0 & & ( byte ) statusCodeStr [ statusCodeStr . Length - 1 ] ! = LF & & ( byte ) statusCodeStr [ statusCodeStr . Length - 1 ] ! = CR )
{
this . Message = ReadTo ( Stream , LF ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "Status Message: '{0}'" , this . Message ) ) ;
}
else
{
2024-02-01 19:06:51 +08:00
HTTPManager . Logger . Warning ( "HTTPResponse" , "Skipping Status Message reading!" , this . Context , this . baseRequest . Context ) ;
2024-01-26 19:15:07 +08:00
this . Message = string . Empty ;
}
//Read Headers
ReadHeaders ( Stream ) ;
2024-02-01 19:06:51 +08:00
if ( ! this . IsProxyResponse )
baseRequest . Timing . Add ( TimingEventNames . Headers ) ;
2024-01-26 19:15:07 +08:00
IsUpgraded = StatusCode = = 101 & & ( HasHeaderWithValue ( "connection" , "upgrade" ) | | HasHeader ( "upgrade" ) ) ;
2024-02-01 19:06:51 +08:00
if ( IsUpgraded )
{
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( "Request Upgraded!" ) ;
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . Upgraded ) ) ;
}
2024-01-26 19:15:07 +08:00
if ( ! readPayloadData )
return true ;
2024-02-01 19:06:51 +08:00
if ( this . StatusCode = = 200 & & this . IsProxyResponse )
return true ;
2024-01-26 19:15:07 +08:00
return ReadPayload ( forceReadRawContentLength ) ;
}
2024-02-01 19:06:51 +08:00
protected bool ReadPayload ( long forceReadRawContentLength )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
// Reading from an already unpacked stream (eq. From a file cache or all responses under webgl)
2024-01-26 19:15:07 +08:00
if ( forceReadRawContentLength ! = - 1 )
{
ReadRaw ( Stream , forceReadRawContentLength ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( "ReadPayload Finished!" ) ;
return true ;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
// 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, and 304 responses and any response to a HEAD request)
// is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.
if ( ( StatusCode > = 100 & & StatusCode < 200 ) | | StatusCode = = 204 | | StatusCode = = 304 | | baseRequest . MethodType = = HTTPMethods . Head )
return true ;
#if ( ! UNITY_WEBGL | | UNITY_EDITOR )
if ( HasHeaderWithValue ( "transfer-encoding" , "chunked" ) )
ReadChunked ( Stream ) ;
else
#endif
{
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
// Case 3 in the above link.
List < string > contentLengthHeaders = GetHeaderValues ( "content-length" ) ;
var contentRangeHeaders = GetHeaderValues ( "content-range" ) ;
if ( contentLengthHeaders ! = null & & contentRangeHeaders = = null )
ReadRaw ( Stream , long . Parse ( contentLengthHeaders [ 0 ] ) ) ;
else if ( contentRangeHeaders ! = null )
{
if ( contentLengthHeaders ! = null )
ReadRaw ( Stream , long . Parse ( contentLengthHeaders [ 0 ] ) ) ;
else
{
HTTPRange range = GetRange ( ) ;
ReadRaw ( Stream , ( range . LastBytePos - range . FirstBytePos ) + 1 ) ;
}
}
else
ReadUnknownSize ( Stream ) ;
}
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( "ReadPayload Finished!" ) ;
return true ;
}
#region Header Management
protected void ReadHeaders ( Stream stream )
{
2024-02-01 19:06:51 +08:00
var newHeaders = this . baseRequest . OnHeadersReceived ! = null ? new Dictionary < string , List < string > > ( StringComparer . OrdinalIgnoreCase ) : null ;
2024-01-26 19:15:07 +08:00
string headerName = ReadTo ( stream , ( byte ) ':' , LF ) /*.Trim()*/ ;
while ( headerName ! = string . Empty )
{
string value = ReadTo ( stream , LF ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "Header - '{0}': '{1}'" , headerName , value ) ) ;
AddHeader ( headerName , value ) ;
2024-02-01 19:06:51 +08:00
if ( newHeaders ! = null )
{
List < string > values ;
if ( ! newHeaders . TryGetValue ( headerName , out values ) )
newHeaders . Add ( headerName , values = new List < string > ( 1 ) ) ;
values . Add ( value ) ;
}
2024-01-26 19:15:07 +08:00
headerName = ReadTo ( stream , ( byte ) ':' , LF ) ;
}
2024-02-01 19:06:51 +08:00
if ( this . baseRequest . OnHeadersReceived ! = null )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , newHeaders ) ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
public void AddHeader ( string name , string value )
2024-01-26 19:15:07 +08:00
{
if ( Headers = = null )
2024-02-01 19:06:51 +08:00
Headers = new Dictionary < string , List < string > > ( StringComparer . OrdinalIgnoreCase ) ;
2024-01-26 19:15:07 +08:00
List < string > values ;
if ( ! Headers . TryGetValue ( name , out values ) )
Headers . Add ( name , values = new List < string > ( 1 ) ) ;
values . Add ( value ) ;
2024-02-01 19:06:51 +08:00
bool isFromCache = false ;
#if ! BESTHTTP_DISABLE_CACHING
isFromCache = this . IsFromCache ;
#endif
if ( ! isFromCache & & name . Equals ( "alt-svc" , StringComparison . Ordinal ) )
PluginEventHelper . EnqueuePluginEvent ( new PluginEventInfo ( PluginEvents . AltSvcHeader , new AltSvcEventInfo ( this . baseRequest . CurrentUri . Host , this ) ) ) ;
2024-01-26 19:15:07 +08:00
}
/// <summary>
/// Returns the list of values that received from the server for the given header name.
/// </summary>
/// <param name="name">Name of the header</param>
/// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
public List < string > GetHeaderValues ( string name )
{
if ( Headers = = null )
return null ;
List < string > values ;
if ( ! Headers . TryGetValue ( name , out values ) | | values . Count = = 0 )
return null ;
return values ;
}
/// <summary>
/// Returns the first value in the header list or null if there are no header or value.
/// </summary>
/// <param name="name">Name of the header</param>
/// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
public string GetFirstHeaderValue ( string name )
{
if ( Headers = = null )
return null ;
List < string > values ;
if ( ! Headers . TryGetValue ( name , out values ) | | values . Count = = 0 )
return null ;
return values [ 0 ] ;
}
/// <summary>
/// Checks if there is a header with the given name and value.
/// </summary>
/// <param name="headerName">Name of the header.</param>
/// <param name="value"></param>
/// <returns>Returns true if there is a header with the given name and value.</returns>
public bool HasHeaderWithValue ( string headerName , string value )
{
var values = GetHeaderValues ( headerName ) ;
if ( values = = null )
return false ;
for ( int i = 0 ; i < values . Count ; + + i )
if ( string . Compare ( values [ i ] , value , StringComparison . OrdinalIgnoreCase ) = = 0 )
return true ;
return false ;
}
/// <summary>
/// Checks if there is a header with the given name.
/// </summary>
/// <param name="headerName">Name of the header.</param>
/// <returns>Returns true if there is a header with the given name.</returns>
public bool HasHeader ( string headerName )
{
var values = GetHeaderValues ( headerName ) ;
if ( values = = null )
return false ;
return true ;
}
/// <summary>
/// Parses the 'Content-Range' header's value and returns a HTTPRange object.
/// </summary>
/// <remarks>If the server ignores a byte-range-spec because it is syntactically invalid, the server SHOULD treat the request as if the invalid Range header field did not exist.
/// (Normally, this means return a 200 response containing the full entity). In this case because of there are no 'Content-Range' header, this function will return null!</remarks>
/// <returns>Returns null if no 'Content-Range' header found.</returns>
public HTTPRange GetRange ( )
{
var rangeHeaders = GetHeaderValues ( "content-range" ) ;
if ( rangeHeaders = = null )
return null ;
// A byte-content-range-spec with a byte-range-resp-spec whose last- byte-pos value is less than its first-byte-pos value,
// or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
// The recipient of an invalid byte-content-range- spec MUST ignore it and any content transferred along with it.
// A valid content-range sample: "bytes 500-1233/1234"
var ranges = rangeHeaders [ 0 ] . Split ( new char [ ] { ' ' , '-' , '/' } , StringSplitOptions . RemoveEmptyEntries ) ;
// A server sending a response with status code 416 (Requested range not satisfiable) SHOULD include a Content-Range field with a byte-range-resp-spec of "*".
// The instance-length specifies the current length of the selected resource.
// "bytes */1234"
if ( ranges [ 1 ] = = "*" )
return new HTTPRange ( int . Parse ( ranges [ 2 ] ) ) ;
return new HTTPRange ( int . Parse ( ranges [ 1 ] ) , int . Parse ( ranges [ 2 ] ) , ranges [ 3 ] ! = "*" ? int . Parse ( ranges [ 3 ] ) : - 1 ) ;
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Static Stream Management Helper Functions
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
internal static string ReadTo ( Stream stream , byte blocker )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
byte [ ] readBuf = BufferPool . Get ( 1024 , true ) ;
2024-01-26 19:15:07 +08:00
try
{
int bufpos = 0 ;
int ch = stream . ReadByte ( ) ;
while ( ch ! = blocker & & ch ! = - 1 )
{
if ( ch > 0x7f ) //replaces asciitostring
ch = '?' ;
//make buffer larger if too short
if ( readBuf . Length < = bufpos )
2024-02-01 19:06:51 +08:00
BufferPool . Resize ( ref readBuf , readBuf . Length * 2 , true , false ) ;
2024-01-26 19:15:07 +08:00
if ( bufpos > 0 | | ! char . IsWhiteSpace ( ( char ) ch ) ) //trimstart
readBuf [ bufpos + + ] = ( byte ) ch ;
ch = stream . ReadByte ( ) ;
}
while ( bufpos > 0 & & char . IsWhiteSpace ( ( char ) readBuf [ bufpos - 1 ] ) )
bufpos - - ;
return System . Text . Encoding . UTF8 . GetString ( readBuf , 0 , bufpos ) ;
}
finally
{
2024-02-01 19:06:51 +08:00
BufferPool . Release ( readBuf ) ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
internal static string ReadTo ( Stream stream , byte blocker1 , byte blocker2 )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
byte [ ] readBuf = BufferPool . Get ( 1024 , true ) ;
try
{
2024-01-26 19:15:07 +08:00
int bufpos = 0 ;
int ch = stream . ReadByte ( ) ;
while ( ch ! = blocker1 & & ch ! = blocker2 & & ch ! = - 1 )
{
if ( ch > 0x7f ) //replaces asciitostring
ch = '?' ;
//make buffer larger if too short
if ( readBuf . Length < = bufpos )
2024-02-01 19:06:51 +08:00
BufferPool . Resize ( ref readBuf , readBuf . Length * 2 , true , true ) ;
2024-01-26 19:15:07 +08:00
if ( bufpos > 0 | | ! char . IsWhiteSpace ( ( char ) ch ) ) //trimstart
readBuf [ bufpos + + ] = ( byte ) ch ;
ch = stream . ReadByte ( ) ;
}
while ( bufpos > 0 & & char . IsWhiteSpace ( ( char ) readBuf [ bufpos - 1 ] ) )
bufpos - - ;
return System . Text . Encoding . UTF8 . GetString ( readBuf , 0 , bufpos ) ;
}
finally
{
2024-02-01 19:06:51 +08:00
BufferPool . Release ( readBuf ) ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
internal static string NoTrimReadTo ( Stream stream , byte blocker1 , byte blocker2 )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
byte [ ] readBuf = BufferPool . Get ( 1024 , true ) ;
try
{
2024-01-26 19:15:07 +08:00
int bufpos = 0 ;
int ch = stream . ReadByte ( ) ;
while ( ch ! = blocker1 & & ch ! = blocker2 & & ch ! = - 1 )
{
if ( ch > 0x7f ) //replaces asciitostring
ch = '?' ;
//make buffer larger if too short
if ( readBuf . Length < = bufpos )
2024-02-01 19:06:51 +08:00
BufferPool . Resize ( ref readBuf , readBuf . Length * 2 , true , true ) ;
2024-01-26 19:15:07 +08:00
if ( bufpos > 0 | | ! char . IsWhiteSpace ( ( char ) ch ) ) //trimstart
readBuf [ bufpos + + ] = ( byte ) ch ;
ch = stream . ReadByte ( ) ;
}
return System . Text . Encoding . UTF8 . GetString ( readBuf , 0 , bufpos ) ;
}
finally
{
2024-02-01 19:06:51 +08:00
BufferPool . Release ( readBuf ) ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Read Chunked Body
2024-01-26 19:15:07 +08:00
protected int ReadChunkLength ( Stream stream )
{
// Read until the end of line, then split the string so we will discard any optional chunk extensions
string line = ReadTo ( stream , LF ) ;
string [ ] splits = line . Split ( ';' ) ;
string num = splits [ 0 ] ;
int result ;
if ( int . TryParse ( num , System . Globalization . NumberStyles . AllowHexSpecifier , null , out result ) )
return result ;
throw new Exception ( string . Format ( "Can't parse '{0}' as a hex number!" , num ) ) ;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
protected void ReadChunked ( Stream stream )
{
BeginReceiveStreamFragments ( ) ;
string contentLengthHeader = GetFirstHeaderValue ( "Content-Length" ) ;
bool hasContentLengthHeader = ! string . IsNullOrEmpty ( contentLengthHeader ) ;
int realLength = 0 ;
if ( hasContentLengthHeader )
hasContentLengthHeader = int . TryParse ( contentLengthHeader , out realLength ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "ReadChunked - hasContentLengthHeader: {0}, contentLengthHeader: {1} realLength: {2:N0}" , hasContentLengthHeader . ToString ( ) , contentLengthHeader , realLength ) ) ;
using ( var output = new BufferPoolMemoryStream ( ) )
{
int chunkLength = ReadChunkLength ( stream ) ;
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "chunkLength: {0:N0}" , chunkLength ) ) ;
2024-02-01 19:06:51 +08:00
byte [ ] buffer = baseRequest . ReadBufferSizeOverride > 0 ? BufferPool . Get ( baseRequest . ReadBufferSizeOverride , false ) : BufferPool . Get ( MinReadBufferSize , true ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
using ( var _ = new PooledBuffer ( buffer ) )
{
// Progress report:
long Downloaded = 0 ;
long DownloadLength = hasContentLengthHeader ? realLength : chunkLength ;
bool sendProgressChanged = this . baseRequest . OnDownloadProgress ! = null & & ( this . IsSuccess
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
2024-02-01 19:06:51 +08:00
| | this . IsFromCache
2024-01-26 19:15:07 +08:00
#endif
2024-02-01 19:06:51 +08:00
) ;
if ( sendProgressChanged )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , Downloaded , DownloadLength ) ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
string encoding =
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
2024-02-01 19:06:51 +08:00
IsFromCache ? null :
2024-01-26 19:15:07 +08:00
#endif
2024-02-01 19:06:51 +08:00
GetFirstHeaderValue ( "content-encoding" ) ;
bool compressed = ! string . IsNullOrEmpty ( encoding ) ;
Decompression . IDecompressor decompressor = baseRequest . UseStreaming ? Decompression . DecompressorFactory . GetDecompressor ( encoding , this . Context ) : null ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
while ( chunkLength ! = 0 )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( this . baseRequest . IsCancellationRequested )
return ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
int totalBytes = 0 ;
// Fill up the buffer
do
{
int tryToReadCount = ( int ) Math . Min ( chunkLength - totalBytes , buffer . Length ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
int bytes = stream . Read ( buffer , 0 , tryToReadCount ) ;
if ( bytes < = 0 )
throw ExceptionHelper . ServerClosedTCPStream ( ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// Progress report:
// Placing reporting inside this cycle will report progress much more frequent
Downloaded + = bytes ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( sendProgressChanged )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , Downloaded , DownloadLength ) ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( baseRequest . UseStreaming )
{
if ( compressed )
{
var decompressed = decompressor . Decompress ( buffer , 0 , bytes , false , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
else
FeedStreamFragment ( buffer , 0 , bytes ) ;
}
else
output . Write ( buffer , 0 , bytes ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
totalBytes + = bytes ;
} while ( totalBytes < chunkLength ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// Every chunk data has a trailing CRLF
ReadTo ( stream , LF ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// read the next chunk's length
chunkLength = ReadChunkLength ( stream ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( ! hasContentLengthHeader )
DownloadLength + = chunkLength ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "chunkLength: {0:N0}" , chunkLength ) ) ;
}
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
//BufferPool.Release(buffer);
if ( baseRequest . UseStreaming )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( compressed )
{
var decompressed = decompressor . Decompress ( null , 0 , 0 , true , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
FlushRemainingFragmentBuffer ( ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
// Read the trailing headers or the CRLF
ReadHeaders ( stream ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// HTTP servers sometimes use compression (gzip) or deflate methods to optimize transmission.
// How both chunked and gzip encoding interact is dictated by the two-staged encoding of HTTP:
// first the content stream is encoded as (Content-Encoding: gzip), after which the resulting byte stream is encoded for transfer using another encoder (Transfer-Encoding: chunked).
// This means that in case both compression and chunked encoding are enabled, the chunk encoding itself is not compressed, and the data in each chunk should not be compressed individually.
// The remote endpoint can decode the incoming stream by first decoding it with the Transfer-Encoding, followed by the specified Content-Encoding.
// It would be a better implementation when the chunk would be decododed on-the-fly. Becouse now the whole stream must be downloaded, and then decoded. It needs more memory.
if ( ! baseRequest . UseStreaming )
this . Data = DecodeStream ( output ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( decompressor ! = null )
decompressor . Dispose ( ) ;
}
}
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Read Raw Body
2024-01-26 19:15:07 +08:00
// No transfer-encoding just raw bytes.
internal void ReadRaw ( Stream stream , long contentLength )
{
BeginReceiveStreamFragments ( ) ;
// Progress report:
2024-02-01 19:06:51 +08:00
long downloaded = 0 ;
long downloadLength = contentLength ;
bool sendProgressChanged = this . baseRequest . OnDownloadProgress ! = null & & ( this . IsSuccess
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
| | this . IsFromCache
#endif
2024-02-01 19:06:51 +08:00
) ;
if ( sendProgressChanged )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , downloaded , downloadLength ) ) ;
2024-01-26 19:15:07 +08:00
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "ReadRaw - contentLength: {0:N0}" , contentLength ) ) ;
string encoding =
#if ! BESTHTTP_DISABLE_CACHING
IsFromCache ? null :
#endif
GetFirstHeaderValue ( "content-encoding" ) ;
2024-02-01 19:06:51 +08:00
bool compressed = ! string . IsNullOrEmpty ( encoding ) ;
Decompression . IDecompressor decompressor = baseRequest . UseStreaming ? Decompression . DecompressorFactory . GetDecompressor ( encoding , this . Context ) : null ;
if ( ! baseRequest . UseStreaming & & contentLength > 2147483646 )
2024-01-26 19:15:07 +08:00
{
throw new OverflowException ( "You have to use STREAMING to download files bigger than 2GB!" ) ;
}
using ( var output = new BufferPoolMemoryStream ( baseRequest . UseStreaming ? 0 : ( int ) contentLength ) )
{
2024-02-01 19:06:51 +08:00
// Because of the last parameter, buffer's size can be larger than the requested but there's no reason to use
// an exact sized one if there's an larger one available in the pool. Later we will use the whole buffer.
byte [ ] buffer = baseRequest . ReadBufferSizeOverride > 0 ? BufferPool . Get ( baseRequest . ReadBufferSizeOverride , false ) : BufferPool . Get ( MinReadBufferSize , true ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
using ( var _ = new PooledBuffer ( buffer ) )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
int readBytes = 0 ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
while ( contentLength > 0 )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( this . baseRequest . IsCancellationRequested )
return ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
readBytes = 0 ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
do
{
// tryToReadCount contain how much bytes we want to read in once. We try to read the buffer fully in once,
// but with a limit of the remaining contentLength.
int tryToReadCount = ( int ) Math . Min ( Math . Min ( int . MaxValue , contentLength ) , buffer . Length - readBytes ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
int bytes = stream . Read ( buffer , readBytes , tryToReadCount ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( bytes < = 0 )
throw ExceptionHelper . ServerClosedTCPStream ( ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
readBytes + = bytes ;
contentLength - = bytes ;
// Progress report:
if ( sendProgressChanged )
{
downloaded + = bytes ;
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , downloaded , downloadLength ) ) ;
}
} while ( readBytes < buffer . Length & & contentLength > 0 ) ;
if ( baseRequest . UseStreaming )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( compressed )
{
var decompressed = decompressor . Decompress ( buffer , 0 , readBytes , false , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
else
FeedStreamFragment ( buffer , 0 , readBytes ) ;
2024-01-26 19:15:07 +08:00
}
else
2024-02-01 19:06:51 +08:00
output . Write ( buffer , 0 , readBytes ) ;
} ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
//BufferPool.Release(buffer);
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( baseRequest . UseStreaming )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( compressed )
{
var decompressed = decompressor . Decompress ( null , 0 , 0 , true , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
FlushRemainingFragmentBuffer ( ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
if ( ! baseRequest . UseStreaming )
this . Data = DecodeStream ( output ) ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
if ( decompressor ! = null )
decompressor . Dispose ( ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Read Unknown Size
2024-01-26 19:15:07 +08:00
protected void ReadUnknownSize ( Stream stream )
{
2024-02-01 19:06:51 +08:00
// Progress report:
long Downloaded = 0 ;
long DownloadLength = 0 ;
bool sendProgressChanged = this . baseRequest . OnDownloadProgress ! = null & & ( this . IsSuccess
#if ! BESTHTTP_DISABLE_CACHING
| | this . IsFromCache
#endif
) ;
if ( sendProgressChanged )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , Downloaded , DownloadLength ) ) ;
2024-01-26 19:15:07 +08:00
string encoding =
#if ! BESTHTTP_DISABLE_CACHING
IsFromCache ? null :
#endif
GetFirstHeaderValue ( "content-encoding" ) ;
2024-02-01 19:06:51 +08:00
bool compressed = ! string . IsNullOrEmpty ( encoding ) ;
Decompression . IDecompressor decompressor = baseRequest . UseStreaming ? Decompression . DecompressorFactory . GetDecompressor ( encoding , this . Context ) : null ;
2024-01-26 19:15:07 +08:00
using ( var output = new BufferPoolMemoryStream ( ) )
{
2024-02-01 19:06:51 +08:00
byte [ ] buffer = baseRequest . ReadBufferSizeOverride > 0 ? BufferPool . Get ( baseRequest . ReadBufferSizeOverride , false ) : BufferPool . Get ( MinReadBufferSize , true ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
using ( var _ = new PooledBuffer ( buffer ) )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "ReadUnknownSize - buffer size: {0:N0}" , buffer . Length ) ) ;
int readBytes = 0 ;
int bytes = 0 ;
2024-01-26 19:15:07 +08:00
do
{
2024-02-01 19:06:51 +08:00
readBytes = 0 ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
do
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( this . baseRequest . IsCancellationRequested )
return ;
bytes = 0 ;
#if ! NETFX_CORE | | UNITY_EDITOR
NetworkStream networkStream = stream as NetworkStream ;
// If we have the good-old NetworkStream, than we can use the DataAvailable property. On WP8 platforms, these are omitted... :/
if ( networkStream ! = null & & baseRequest . EnableSafeReadOnUnknownContentLength )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
for ( int i = readBytes ; i < buffer . Length & & networkStream . DataAvailable ; + + i )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
int read = stream . ReadByte ( ) ;
if ( read > = 0 )
{
buffer [ i ] = ( byte ) read ;
bytes + + ;
}
else
break ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
else // This will be good anyway, but a little slower.
2024-01-26 19:15:07 +08:00
#endif
2024-02-01 19:06:51 +08:00
{
bytes = stream . Read ( buffer , readBytes , buffer . Length - readBytes ) ;
}
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
readBytes + = bytes ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// Progress report:
Downloaded + = bytes ;
DownloadLength = Downloaded ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( sendProgressChanged )
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , RequestEvents . DownloadProgress , Downloaded , DownloadLength ) ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
} while ( readBytes < buffer . Length & & bytes > 0 ) ;
if ( baseRequest . UseStreaming )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( compressed )
{
var decompressed = decompressor . Decompress ( buffer , 0 , readBytes , false , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
else
FeedStreamFragment ( buffer , 0 , readBytes ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
else if ( readBytes > 0 )
output . Write ( buffer , 0 , readBytes ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
} while ( bytes > 0 ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
//BufferPool.Release(buffer);
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( baseRequest . UseStreaming )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
if ( compressed )
{
var decompressed = decompressor . Decompress ( null , 0 , 0 , true , true ) ;
if ( decompressed . Data ! = null )
FeedStreamFragment ( decompressed . Data , 0 , decompressed . Length ) ;
}
FlushRemainingFragmentBuffer ( ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
if ( ! baseRequest . UseStreaming )
this . Data = DecodeStream ( output ) ;
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
if ( decompressor ! = null )
decompressor . Dispose ( ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Stream Decoding
2024-01-26 19:15:07 +08:00
protected byte [ ] DecodeStream ( BufferPoolMemoryStream streamToDecode )
{
streamToDecode . Seek ( 0 , SeekOrigin . Begin ) ;
// The cache stores the decoded data
var encoding =
#if ! BESTHTTP_DISABLE_CACHING
IsFromCache ? null :
#endif
GetHeaderValues ( "content-encoding" ) ;
2024-02-01 19:06:51 +08:00
Stream decoderStream = Decompression . DecompressorFactory . GetDecoderStream ( streamToDecode , encoding ? [ 0 ] ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
// Under WebGL decoderStream is always null.
if ( decoderStream = = null )
return streamToDecode . ToArray ( canBeLarger : false ) ;
2024-01-26 19:15:07 +08:00
#if ! UNITY_WEBGL | | UNITY_EDITOR
using ( var ms = new BufferPoolMemoryStream ( ( int ) streamToDecode . Length ) )
{
2024-02-01 19:06:51 +08:00
var buf = BufferPool . Get ( HTTPResponse . MinReadBufferSize , true ) ;
2024-01-26 19:15:07 +08:00
int byteCount = 0 ;
while ( ( byteCount = decoderStream . Read ( buf , 0 , buf . Length ) ) > 0 )
ms . Write ( buf , 0 , byteCount ) ;
2024-02-01 19:06:51 +08:00
BufferPool . Release ( buf ) ;
2024-01-26 19:15:07 +08:00
decoderStream . Dispose ( ) ;
return ms . ToArray ( ) ;
}
2024-02-01 19:06:51 +08:00
#else
return streamToDecode . ToArray ( canBeLarger : false ) ;
2024-01-26 19:15:07 +08:00
#endif
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
#region Streaming Fragments Support
2024-01-26 19:15:07 +08:00
protected void BeginReceiveStreamFragments ( )
{
#if ! BESTHTTP_DISABLE_CACHING
if ( ! baseRequest . DisableCache & & baseRequest . UseStreaming )
{
// If caching is enabled and the response not from cache and it's cacheble we will cache the downloaded data.
if ( ! IsFromCache & & HTTPCacheService . IsCacheble ( baseRequest . CurrentUri , baseRequest . MethodType , this ) )
cacheStream = HTTPCacheService . PrepareStreamed ( baseRequest . CurrentUri , this ) ;
}
#endif
allFragmentSize = 0 ;
}
/// <summary>
/// Add data to the fragments list.
/// </summary>
/// <param name="buffer">The buffer to be added.</param>
/// <param name="pos">The position where we start copy the data.</param>
/// <param name="length">How many data we want to copy.</param>
protected void FeedStreamFragment ( byte [ ] buffer , int pos , int length )
{
if ( buffer = = null | | length = = 0 )
return ;
// If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
2024-02-01 19:06:51 +08:00
#if ! UNITY_WEBGL | | UNITY_EDITOR
#if CSHARP_7_3_OR_NEWER
SpinWait spinWait = new SpinWait ( ) ;
#endif
while ( ! this . baseRequest . IsCancellationRequested & &
this . baseRequest . State = = HTTPRequestStates . Processing & &
baseRequest . UseStreaming & &
FragmentQueueIsFull ( ) )
{
VerboseLogging ( "WaitWhileFragmentQueueIsFull" ) ;
#if CSHARP_7_3_OR_NEWER
spinWait . SpinOnce ( ) ;
#elif ! NETFX_CORE
System . Threading . Thread . Sleep ( 1 ) ;
#endif
}
#endif
2024-01-26 19:15:07 +08:00
if ( fragmentBuffer = = null )
{
2024-02-01 19:06:51 +08:00
fragmentBuffer = BufferPool . Get ( baseRequest . StreamFragmentSize , true ) ;
2024-01-26 19:15:07 +08:00
fragmentBufferDataLength = 0 ;
}
2024-02-01 19:06:51 +08:00
if ( fragmentBufferDataLength + length < = fragmentBuffer . Length )
2024-01-26 19:15:07 +08:00
{
Array . Copy ( buffer , pos , fragmentBuffer , fragmentBufferDataLength , length ) ;
fragmentBufferDataLength + = length ;
2024-02-01 19:06:51 +08:00
if ( fragmentBufferDataLength = = fragmentBuffer . Length | | baseRequest . StreamChunksImmediately )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
AddStreamedFragment ( fragmentBuffer , fragmentBufferDataLength ) ;
2024-01-26 19:15:07 +08:00
fragmentBuffer = null ;
fragmentBufferDataLength = 0 ;
}
}
else
{
2024-02-01 19:06:51 +08:00
int remaining = fragmentBuffer . Length - fragmentBufferDataLength ;
2024-01-26 19:15:07 +08:00
FeedStreamFragment ( buffer , pos , remaining ) ;
FeedStreamFragment ( buffer , pos + remaining , length - remaining ) ;
}
}
protected void FlushRemainingFragmentBuffer ( )
{
if ( fragmentBuffer ! = null )
{
2024-02-01 19:06:51 +08:00
AddStreamedFragment ( fragmentBuffer , fragmentBufferDataLength ) ;
2024-01-26 19:15:07 +08:00
fragmentBuffer = null ;
fragmentBufferDataLength = 0 ;
}
#if ! BESTHTTP_DISABLE_CACHING
if ( cacheStream ! = null )
{
cacheStream . Dispose ( ) ;
cacheStream = null ;
HTTPCacheService . SetBodyLength ( baseRequest . CurrentUri , allFragmentSize ) ;
}
#endif
}
2024-02-01 19:06:51 +08:00
#if NET_STANDARD_2_0 | | NETFX_CORE
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
#endif
protected void AddStreamedFragment ( byte [ ] buffer , int bufferLength )
2024-01-26 19:15:07 +08:00
{
#if ! BESTHTTP_DISABLE_CACHING
2024-02-01 19:06:51 +08:00
if ( ! IsCacheOnly )
2024-01-26 19:15:07 +08:00
#endif
2024-02-01 19:06:51 +08:00
{
if ( this . baseRequest . UseStreaming & & buffer ! = null & & bufferLength > 0 )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
RequestEventHelper . EnqueueRequestEvent ( new RequestEventInfo ( this . baseRequest , buffer , bufferLength ) ) ;
Interlocked . Increment ( ref this . UnprocessedFragments ) ;
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
}
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All & & buffer ! = null )
VerboseLogging ( string . Format ( "AddStreamedFragment buffer length: {0:N0} UnprocessedFragments: {1:N0}" , bufferLength , Interlocked . Read ( ref this . UnprocessedFragments ) ) ) ;
2024-01-26 19:15:07 +08:00
#if ! BESTHTTP_DISABLE_CACHING
2024-02-01 19:06:51 +08:00
if ( cacheStream ! = null )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
cacheStream . Write ( buffer , 0 , bufferLength ) ;
allFragmentSize + = bufferLength ;
2024-01-26 19:15:07 +08:00
}
#endif
}
2024-02-01 19:06:51 +08:00
#if NET_STANDARD_2_0 | | NETFX_CORE
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
#endif
private bool FragmentQueueIsFull ( )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
#if ! UNITY_WEBGL | | UNITY_EDITOR
long unprocessedFragments = Interlocked . Read ( ref UnprocessedFragments ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
bool result = unprocessedFragments > = baseRequest . MaxFragmentQueueLength ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
if ( result & & HTTPManager . Logger . Level = = Logger . Loglevels . All )
VerboseLogging ( string . Format ( "FragmentQueueIsFull - {0} / {1}" , unprocessedFragments , baseRequest . MaxFragmentQueueLength ) ) ;
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
return result ;
#else
return false ;
#endif
2024-01-26 19:15:07 +08:00
}
2024-02-01 19:06:51 +08:00
#endregion
2024-01-26 19:15:07 +08:00
void VerboseLogging ( string str )
{
if ( HTTPManager . Logger . Level = = Logger . Loglevels . All )
2024-02-01 19:06:51 +08:00
HTTPManager . Logger . Verbose ( "HTTPResponse" , str , this . Context , this . baseRequest . Context ) ;
2024-01-26 19:15:07 +08:00
}
/// <summary>
/// IDisposable implementation.
/// </summary>
public void Dispose ( )
{
2024-02-01 19:06:51 +08:00
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
2024-01-26 19:15:07 +08:00
2024-02-01 19:06:51 +08:00
protected virtual void Dispose ( bool disposing )
{
if ( disposing )
2024-01-26 19:15:07 +08:00
{
2024-02-01 19:06:51 +08:00
// Release resources in case we are using ReadOnlyBufferedStream, it will not close its inner stream.
// Otherwise, closing the (inner) Stream is the connection's responsibility
if ( Stream ! = null & & Stream is ReadOnlyBufferedStream )
( Stream as IDisposable ) . Dispose ( ) ;
Stream = null ;
#if ! BESTHTTP_DISABLE_CACHING
if ( cacheStream ! = null )
{
cacheStream . Dispose ( ) ;
cacheStream = null ;
}
2024-01-26 19:15:07 +08:00
#endif
2024-02-01 19:06:51 +08:00
}
2024-01-26 19:15:07 +08:00
}
}
2024-02-01 19:06:51 +08:00
}