HTTPResponse.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. #if !NETFX_CORE || UNITY_EDITOR
  6. using System.Net.Sockets;
  7. #endif
  8. using UnityEngine;
  9. namespace BestHTTP
  10. {
  11. #if !BESTHTTP_DISABLE_CACHING
  12. using BestHTTP.Caching;
  13. #endif
  14. using BestHTTP.Extensions;
  15. #if !BESTHTTP_DISABLE_COOKIES
  16. using BestHTTP.Cookies;
  17. #endif
  18. using System.Threading;
  19. using BestHTTP.Core;
  20. using BestHTTP.PlatformSupport.Memory;
  21. using BestHTTP.Logger;
  22. using BestHTTP.Timings;
  23. public class HTTPResponse : IDisposable
  24. {
  25. internal const byte CR = 13;
  26. internal const byte LF = 10;
  27. /// <summary>
  28. /// Minimum size of the read buffer.
  29. /// </summary>
  30. public static int MinReadBufferSize = 16 * 1024;
  31. #region Public Properties
  32. public int VersionMajor { get; protected set; }
  33. public int VersionMinor { get; protected set; }
  34. /// <summary>
  35. /// The status code that sent from the server.
  36. /// </summary>
  37. public int StatusCode { get; protected set; }
  38. /// <summary>
  39. /// Returns true if the status code is in the range of [200..300[ or 304 (Not Modified)
  40. /// </summary>
  41. public bool IsSuccess { get { return (this.StatusCode >= 200 && this.StatusCode < 300) || this.StatusCode == 304; } }
  42. /// <summary>
  43. /// The message that sent along with the StatusCode from the server. You can check it for errors from the server.
  44. /// </summary>
  45. public string Message { get; protected set; }
  46. /// <summary>
  47. /// True if it's a streamed response.
  48. /// </summary>
  49. public bool IsStreamed { get; protected set; }
  50. #if !BESTHTTP_DISABLE_CACHING
  51. /// <summary>
  52. /// Indicates that the response body is read from the cache.
  53. /// </summary>
  54. public bool IsFromCache { get; internal set; }
  55. /// <summary>
  56. /// Provides information about the file used for caching the request.
  57. /// </summary>
  58. public HTTPCacheFileInfo CacheFileInfo { get; internal set; }
  59. /// <summary>
  60. /// Determines if this response is only stored to cache.
  61. /// If both IsCacheOnly and IsStreamed are true, OnStreamingData isn't called.
  62. /// </summary>
  63. public bool IsCacheOnly { get; private set; }
  64. #endif
  65. /// <summary>
  66. /// True, if this is a response for a HTTPProxy request.
  67. /// </summary>
  68. public bool IsProxyResponse { get; private set; }
  69. /// <summary>
  70. /// The headers that sent from the server.
  71. /// </summary>
  72. public Dictionary<string, List<string>> Headers { get; protected set; }
  73. /// <summary>
  74. /// The data that downloaded from the server. All Transfer and Content encodings decoded if any(eg. chunked, gzip, deflate).
  75. /// </summary>
  76. public byte[] Data { get; internal set; }
  77. /// <summary>
  78. /// The normal HTTP protocol is upgraded to an other.
  79. /// </summary>
  80. public bool IsUpgraded { get; protected set; }
  81. #if !BESTHTTP_DISABLE_COOKIES
  82. /// <summary>
  83. /// The cookies that the server sent to the client.
  84. /// </summary>
  85. public List<Cookie> Cookies { get; internal set; }
  86. #endif
  87. /// <summary>
  88. /// Cached, converted data.
  89. /// </summary>
  90. protected string dataAsText;
  91. /// <summary>
  92. /// The data converted to an UTF8 string.
  93. /// </summary>
  94. public string DataAsText
  95. {
  96. get
  97. {
  98. if (Data == null)
  99. return string.Empty;
  100. if (!string.IsNullOrEmpty(dataAsText))
  101. return dataAsText;
  102. return dataAsText = Encoding.UTF8.GetString(Data, 0, Data.Length);
  103. }
  104. }
  105. /// <summary>
  106. /// Cached converted data.
  107. /// </summary>
  108. protected Texture2D texture;
  109. /// <summary>
  110. /// The data loaded to a Texture2D.
  111. /// </summary>
  112. public Texture2D DataAsTexture2D
  113. {
  114. get
  115. {
  116. if (Data == null)
  117. return null;
  118. if (texture != null)
  119. return texture;
  120. texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
  121. texture.LoadImage(Data, true);
  122. return texture;
  123. }
  124. }
  125. /// <summary>
  126. /// True if the connection's stream will be closed manually. Used in custom protocols (WebSocket, EventSource).
  127. /// </summary>
  128. public bool IsClosedManually { get; protected set; }
  129. /// <summary>
  130. /// IProtocol.LoggingContext implementation.
  131. /// </summary>
  132. public LoggingContext Context { get; private set; }
  133. /// <summary>
  134. /// Count of streaming data fragments sitting in the HTTPManager's request event queue.
  135. /// </summary>
  136. #if UNITY_EDITOR
  137. [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
  138. #endif
  139. internal long UnprocessedFragments;
  140. #endregion
  141. #region Internal Fields
  142. internal HTTPRequest baseRequest;
  143. #endregion
  144. #region Protected Properties And Fields
  145. protected Stream Stream;
  146. protected byte[] fragmentBuffer;
  147. protected int fragmentBufferDataLength;
  148. #if !BESTHTTP_DISABLE_CACHING
  149. protected Stream cacheStream;
  150. #endif
  151. protected int allFragmentSize;
  152. #endregion
  153. protected HTTPResponse(HTTPRequest request, bool isFromCache)
  154. {
  155. this.baseRequest = request;
  156. #if !BESTHTTP_DISABLE_CACHING
  157. this.IsFromCache = isFromCache;
  158. #endif
  159. this.Context = new LoggingContext(this);
  160. this.Context.Add("BaseRequest", request.Context);
  161. this.Context.Add("IsFromCache", isFromCache);
  162. }
  163. public HTTPResponse(HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache, bool isProxyResponse = false)
  164. {
  165. this.baseRequest = request;
  166. this.Stream = stream;
  167. this.IsStreamed = isStreamed;
  168. #if !BESTHTTP_DISABLE_CACHING
  169. this.IsFromCache = isFromCache;
  170. this.IsCacheOnly = request.CacheOnly;
  171. #endif
  172. this.IsProxyResponse = isProxyResponse;
  173. this.IsClosedManually = false;
  174. this.Context = new LoggingContext(this);
  175. this.Context.Add("BaseRequest", request.GetHashCode());
  176. this.Context.Add("IsStreamed", isStreamed);
  177. this.Context.Add("IsFromCache", isFromCache);
  178. }
  179. public bool Receive(long forceReadRawContentLength = -1, bool readPayloadData = true, bool sendUpgradedEvent = true)
  180. {
  181. if (this.baseRequest.IsCancellationRequested)
  182. return false;
  183. string statusLine = string.Empty;
  184. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  185. VerboseLogging(string.Format("Receive. forceReadRawContentLength: '{0:N0}', readPayloadData: '{1:N0}'", forceReadRawContentLength, readPayloadData));
  186. // On WP platform we aren't able to determined sure enough whether the tcp connection is closed or not.
  187. // So if we get an exception here, we need to recreate the connection.
  188. try
  189. {
  190. // Read out 'HTTP/1.1' from the "HTTP/1.1 {StatusCode} {Message}"
  191. statusLine = ReadTo(Stream, (byte)' ');
  192. }
  193. catch
  194. {
  195. if (baseRequest.IsCancellationRequested)
  196. return false;
  197. if (baseRequest.Retries >= baseRequest.MaxRetries)
  198. {
  199. HTTPManager.Logger.Warning("HTTPResponse", "Failed to read Status Line! Retry is enabled, returning with false.", this.Context, this.baseRequest.Context);
  200. return false;
  201. }
  202. HTTPManager.Logger.Warning("HTTPResponse", "Failed to read Status Line! Retry is disabled, re-throwing exception.", this.Context, this.baseRequest.Context);
  203. throw;
  204. }
  205. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  206. VerboseLogging(string.Format("Status Line: '{0}'", statusLine));
  207. if (string.IsNullOrEmpty(statusLine))
  208. {
  209. if (baseRequest.Retries >= baseRequest.MaxRetries)
  210. return false;
  211. throw new Exception("Network error! TCP Connection got closed before receiving any data!");
  212. }
  213. if (!this.IsProxyResponse)
  214. baseRequest.Timing.Add(TimingEventNames.Waiting_TTFB);
  215. string[] versions = statusLine.Split(new char[] { '/', '.' });
  216. this.VersionMajor = int.Parse(versions[1]);
  217. this.VersionMinor = int.Parse(versions[2]);
  218. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  219. VerboseLogging(string.Format("HTTP Version: '{0}.{1}'", this.VersionMajor.ToString(), this.VersionMinor.ToString()));
  220. int statusCode;
  221. string statusCodeStr = NoTrimReadTo(Stream, (byte)' ', LF);
  222. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  223. VerboseLogging(string.Format("Status Code: '{0}'", statusCodeStr));
  224. if (baseRequest.Retries >= baseRequest.MaxRetries)
  225. statusCode = int.Parse(statusCodeStr);
  226. else if (!int.TryParse(statusCodeStr, out statusCode))
  227. return false;
  228. this.StatusCode = statusCode;
  229. if (statusCodeStr.Length > 0 && (byte)statusCodeStr[statusCodeStr.Length - 1] != LF && (byte)statusCodeStr[statusCodeStr.Length - 1] != CR)
  230. {
  231. this.Message = ReadTo(Stream, LF);
  232. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  233. VerboseLogging(string.Format("Status Message: '{0}'", this.Message));
  234. }
  235. else
  236. {
  237. HTTPManager.Logger.Warning("HTTPResponse", "Skipping Status Message reading!", this.Context, this.baseRequest.Context);
  238. this.Message = string.Empty;
  239. }
  240. //Read Headers
  241. ReadHeaders(Stream);
  242. if (!this.IsProxyResponse)
  243. baseRequest.Timing.Add(TimingEventNames.Headers);
  244. IsUpgraded = StatusCode == 101 && (HasHeaderWithValue("connection", "upgrade") || HasHeader("upgrade"));
  245. if (IsUpgraded)
  246. {
  247. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  248. VerboseLogging("Request Upgraded!");
  249. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.Upgraded));
  250. }
  251. if (!readPayloadData)
  252. return true;
  253. if (this.StatusCode == 200 && this.IsProxyResponse)
  254. return true;
  255. return ReadPayload(forceReadRawContentLength);
  256. }
  257. protected bool ReadPayload(long forceReadRawContentLength)
  258. {
  259. // Reading from an already unpacked stream (eq. From a file cache or all responses under webgl)
  260. if (forceReadRawContentLength != -1)
  261. {
  262. ReadRaw(Stream, forceReadRawContentLength);
  263. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  264. VerboseLogging("ReadPayload Finished!");
  265. return true;
  266. }
  267. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
  268. // 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)
  269. // is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.
  270. if ((StatusCode >= 100 && StatusCode < 200) || StatusCode == 204 || StatusCode == 304 || baseRequest.MethodType == HTTPMethods.Head)
  271. return true;
  272. #if (!UNITY_WEBGL || UNITY_EDITOR)
  273. if (HasHeaderWithValue("transfer-encoding", "chunked"))
  274. ReadChunked(Stream);
  275. else
  276. #endif
  277. {
  278. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
  279. // Case 3 in the above link.
  280. List<string> contentLengthHeaders = GetHeaderValues("content-length");
  281. var contentRangeHeaders = GetHeaderValues("content-range");
  282. if (contentLengthHeaders != null && contentRangeHeaders == null)
  283. ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
  284. else if (contentRangeHeaders != null)
  285. {
  286. if (contentLengthHeaders != null)
  287. ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
  288. else
  289. {
  290. HTTPRange range = GetRange();
  291. ReadRaw(Stream, (range.LastBytePos - range.FirstBytePos) + 1);
  292. }
  293. }
  294. else
  295. ReadUnknownSize(Stream);
  296. }
  297. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  298. VerboseLogging("ReadPayload Finished!");
  299. return true;
  300. }
  301. #region Header Management
  302. protected void ReadHeaders(Stream stream)
  303. {
  304. var newHeaders = this.baseRequest.OnHeadersReceived != null ? new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase) : null;
  305. string headerName = ReadTo(stream, (byte)':', LF)/*.Trim()*/;
  306. while (headerName != string.Empty)
  307. {
  308. string value = ReadTo(stream, LF);
  309. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  310. VerboseLogging(string.Format("Header - '{0}': '{1}'", headerName, value));
  311. AddHeader(headerName, value);
  312. if (newHeaders != null)
  313. {
  314. List<string> values;
  315. if (!newHeaders.TryGetValue(headerName, out values))
  316. newHeaders.Add(headerName, values = new List<string>(1));
  317. values.Add(value);
  318. }
  319. headerName = ReadTo(stream, (byte)':', LF);
  320. }
  321. if (this.baseRequest.OnHeadersReceived != null)
  322. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, newHeaders));
  323. }
  324. public void AddHeader(string name, string value)
  325. {
  326. if (Headers == null)
  327. Headers = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
  328. List<string> values;
  329. if (!Headers.TryGetValue(name, out values))
  330. Headers.Add(name, values = new List<string>(1));
  331. values.Add(value);
  332. bool isFromCache = false;
  333. #if !BESTHTTP_DISABLE_CACHING
  334. isFromCache = this.IsFromCache;
  335. #endif
  336. if (!isFromCache && name.Equals("alt-svc", StringComparison.Ordinal))
  337. PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(this.baseRequest.CurrentUri.Host, this)));
  338. }
  339. /// <summary>
  340. /// Returns the list of values that received from the server for the given header name.
  341. /// </summary>
  342. /// <param name="name">Name of the header</param>
  343. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  344. public List<string> GetHeaderValues(string name)
  345. {
  346. if (Headers == null)
  347. return null;
  348. List<string> values;
  349. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  350. return null;
  351. return values;
  352. }
  353. /// <summary>
  354. /// Returns the first value in the header list or null if there are no header or value.
  355. /// </summary>
  356. /// <param name="name">Name of the header</param>
  357. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  358. public string GetFirstHeaderValue(string name)
  359. {
  360. if (Headers == null)
  361. return null;
  362. List<string> values;
  363. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  364. return null;
  365. return values[0];
  366. }
  367. /// <summary>
  368. /// Checks if there is a header with the given name and value.
  369. /// </summary>
  370. /// <param name="headerName">Name of the header.</param>
  371. /// <param name="value"></param>
  372. /// <returns>Returns true if there is a header with the given name and value.</returns>
  373. public bool HasHeaderWithValue(string headerName, string value)
  374. {
  375. var values = GetHeaderValues(headerName);
  376. if (values == null)
  377. return false;
  378. for (int i = 0; i < values.Count; ++i)
  379. if (string.Compare(values[i], value, StringComparison.OrdinalIgnoreCase) == 0)
  380. return true;
  381. return false;
  382. }
  383. /// <summary>
  384. /// Checks if there is a header with the given name.
  385. /// </summary>
  386. /// <param name="headerName">Name of the header.</param>
  387. /// <returns>Returns true if there is a header with the given name.</returns>
  388. public bool HasHeader(string headerName)
  389. {
  390. var values = GetHeaderValues(headerName);
  391. if (values == null)
  392. return false;
  393. return true;
  394. }
  395. /// <summary>
  396. /// Parses the 'Content-Range' header's value and returns a HTTPRange object.
  397. /// </summary>
  398. /// <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.
  399. /// (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>
  400. /// <returns>Returns null if no 'Content-Range' header found.</returns>
  401. public HTTPRange GetRange()
  402. {
  403. var rangeHeaders = GetHeaderValues("content-range");
  404. if (rangeHeaders == null)
  405. return null;
  406. // A byte-content-range-spec with a byte-range-resp-spec whose last- byte-pos value is less than its first-byte-pos value,
  407. // or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
  408. // The recipient of an invalid byte-content-range- spec MUST ignore it and any content transferred along with it.
  409. // A valid content-range sample: "bytes 500-1233/1234"
  410. var ranges = rangeHeaders[0].Split(new char[] { ' ', '-', '/' }, StringSplitOptions.RemoveEmptyEntries);
  411. // 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 "*".
  412. // The instance-length specifies the current length of the selected resource.
  413. // "bytes */1234"
  414. if (ranges[1] == "*")
  415. return new HTTPRange(int.Parse(ranges[2]));
  416. return new HTTPRange(int.Parse(ranges[1]), int.Parse(ranges[2]), ranges[3] != "*" ? int.Parse(ranges[3]) : -1);
  417. }
  418. #endregion
  419. #region Static Stream Management Helper Functions
  420. internal static string ReadTo(Stream stream, byte blocker)
  421. {
  422. byte[] readBuf = BufferPool.Get(1024, true);
  423. try
  424. {
  425. int bufpos = 0;
  426. int ch = stream.ReadByte();
  427. while (ch != blocker && ch != -1)
  428. {
  429. if (ch > 0x7f) //replaces asciitostring
  430. ch = '?';
  431. //make buffer larger if too short
  432. if (readBuf.Length <= bufpos)
  433. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, false);
  434. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  435. readBuf[bufpos++] = (byte)ch;
  436. ch = stream.ReadByte();
  437. }
  438. while (bufpos > 0 && char.IsWhiteSpace((char)readBuf[bufpos - 1]))
  439. bufpos--;
  440. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  441. }
  442. finally
  443. {
  444. BufferPool.Release(readBuf);
  445. }
  446. }
  447. internal static string ReadTo(Stream stream, byte blocker1, byte blocker2)
  448. {
  449. byte[] readBuf = BufferPool.Get(1024, true);
  450. try
  451. {
  452. int bufpos = 0;
  453. int ch = stream.ReadByte();
  454. while (ch != blocker1 && ch != blocker2 && ch != -1)
  455. {
  456. if (ch > 0x7f) //replaces asciitostring
  457. ch = '?';
  458. //make buffer larger if too short
  459. if (readBuf.Length <= bufpos)
  460. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, true);
  461. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  462. readBuf[bufpos++] = (byte)ch;
  463. ch = stream.ReadByte();
  464. }
  465. while (bufpos > 0 && char.IsWhiteSpace((char)readBuf[bufpos - 1]))
  466. bufpos--;
  467. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  468. }
  469. finally
  470. {
  471. BufferPool.Release(readBuf);
  472. }
  473. }
  474. internal static string NoTrimReadTo(Stream stream, byte blocker1, byte blocker2)
  475. {
  476. byte[] readBuf = BufferPool.Get(1024, true);
  477. try
  478. {
  479. int bufpos = 0;
  480. int ch = stream.ReadByte();
  481. while (ch != blocker1 && ch != blocker2 && ch != -1)
  482. {
  483. if (ch > 0x7f) //replaces asciitostring
  484. ch = '?';
  485. //make buffer larger if too short
  486. if (readBuf.Length <= bufpos)
  487. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, true);
  488. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  489. readBuf[bufpos++] = (byte)ch;
  490. ch = stream.ReadByte();
  491. }
  492. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  493. }
  494. finally
  495. {
  496. BufferPool.Release(readBuf);
  497. }
  498. }
  499. #endregion
  500. #region Read Chunked Body
  501. protected int ReadChunkLength(Stream stream)
  502. {
  503. // Read until the end of line, then split the string so we will discard any optional chunk extensions
  504. string line = ReadTo(stream, LF);
  505. string[] splits = line.Split(';');
  506. string num = splits[0];
  507. int result;
  508. if (int.TryParse(num, System.Globalization.NumberStyles.AllowHexSpecifier, null, out result))
  509. return result;
  510. throw new Exception(string.Format("Can't parse '{0}' as a hex number!", num));
  511. }
  512. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
  513. protected void ReadChunked(Stream stream)
  514. {
  515. BeginReceiveStreamFragments();
  516. string contentLengthHeader = GetFirstHeaderValue("Content-Length");
  517. bool hasContentLengthHeader = !string.IsNullOrEmpty(contentLengthHeader);
  518. int realLength = 0;
  519. if (hasContentLengthHeader)
  520. hasContentLengthHeader = int.TryParse(contentLengthHeader, out realLength);
  521. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  522. VerboseLogging(string.Format("ReadChunked - hasContentLengthHeader: {0}, contentLengthHeader: {1} realLength: {2:N0}", hasContentLengthHeader.ToString(), contentLengthHeader, realLength));
  523. using (var output = new BufferPoolMemoryStream())
  524. {
  525. int chunkLength = ReadChunkLength(stream);
  526. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  527. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  528. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  529. // wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
  530. using (var _ = new PooledBuffer(buffer))
  531. {
  532. // Progress report:
  533. long Downloaded = 0;
  534. long DownloadLength = hasContentLengthHeader ? realLength : chunkLength;
  535. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  536. #if !BESTHTTP_DISABLE_CACHING
  537. || this.IsFromCache
  538. #endif
  539. );
  540. if (sendProgressChanged)
  541. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  542. string encoding =
  543. #if !BESTHTTP_DISABLE_CACHING
  544. IsFromCache ? null :
  545. #endif
  546. GetFirstHeaderValue("content-encoding");
  547. bool compressed = !string.IsNullOrEmpty(encoding);
  548. Decompression.IDecompressor decompressor = baseRequest.UseStreaming ? Decompression.DecompressorFactory.GetDecompressor(encoding, this.Context) : null;
  549. while (chunkLength != 0)
  550. {
  551. if (this.baseRequest.IsCancellationRequested)
  552. return;
  553. int totalBytes = 0;
  554. // Fill up the buffer
  555. do
  556. {
  557. int tryToReadCount = (int)Math.Min(chunkLength - totalBytes, buffer.Length);
  558. int bytes = stream.Read(buffer, 0, tryToReadCount);
  559. if (bytes <= 0)
  560. throw ExceptionHelper.ServerClosedTCPStream();
  561. // Progress report:
  562. // Placing reporting inside this cycle will report progress much more frequent
  563. Downloaded += bytes;
  564. if (sendProgressChanged)
  565. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  566. if (baseRequest.UseStreaming)
  567. {
  568. if (compressed)
  569. {
  570. var decompressed = decompressor.Decompress(buffer, 0, bytes, false, true);
  571. if (decompressed.Data != null)
  572. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  573. }
  574. else
  575. FeedStreamFragment(buffer, 0, bytes);
  576. }
  577. else
  578. output.Write(buffer, 0, bytes);
  579. totalBytes += bytes;
  580. } while (totalBytes < chunkLength);
  581. // Every chunk data has a trailing CRLF
  582. ReadTo(stream, LF);
  583. // read the next chunk's length
  584. chunkLength = ReadChunkLength(stream);
  585. if (!hasContentLengthHeader)
  586. DownloadLength += chunkLength;
  587. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  588. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  589. }
  590. //BufferPool.Release(buffer);
  591. if (baseRequest.UseStreaming)
  592. {
  593. if (compressed)
  594. {
  595. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  596. if (decompressed.Data != null)
  597. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  598. }
  599. FlushRemainingFragmentBuffer();
  600. }
  601. // Read the trailing headers or the CRLF
  602. ReadHeaders(stream);
  603. // HTTP servers sometimes use compression (gzip) or deflate methods to optimize transmission.
  604. // How both chunked and gzip encoding interact is dictated by the two-staged encoding of HTTP:
  605. // 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).
  606. // 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.
  607. // The remote endpoint can decode the incoming stream by first decoding it with the Transfer-Encoding, followed by the specified Content-Encoding.
  608. // 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.
  609. if (!baseRequest.UseStreaming)
  610. this.Data = DecodeStream(output);
  611. if (decompressor != null)
  612. decompressor.Dispose();
  613. }
  614. }
  615. }
  616. #endregion
  617. #region Read Raw Body
  618. // No transfer-encoding just raw bytes.
  619. internal void ReadRaw(Stream stream, long contentLength)
  620. {
  621. BeginReceiveStreamFragments();
  622. // Progress report:
  623. long downloaded = 0;
  624. long downloadLength = contentLength;
  625. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  626. #if !BESTHTTP_DISABLE_CACHING
  627. || this.IsFromCache
  628. #endif
  629. );
  630. if (sendProgressChanged)
  631. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, downloaded, downloadLength));
  632. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  633. VerboseLogging(string.Format("ReadRaw - contentLength: {0:N0}", contentLength));
  634. string encoding =
  635. #if !BESTHTTP_DISABLE_CACHING
  636. IsFromCache ? null :
  637. #endif
  638. GetFirstHeaderValue("content-encoding");
  639. bool compressed = !string.IsNullOrEmpty(encoding);
  640. Decompression.IDecompressor decompressor = baseRequest.UseStreaming ? Decompression.DecompressorFactory.GetDecompressor(encoding, this.Context) : null;
  641. if (!baseRequest.UseStreaming && contentLength > 2147483646)
  642. {
  643. throw new OverflowException("You have to use STREAMING to download files bigger than 2GB!");
  644. }
  645. using (var output = new BufferPoolMemoryStream(baseRequest.UseStreaming ? 0 : (int)contentLength))
  646. {
  647. // Because of the last parameter, buffer's size can be larger than the requested but there's no reason to use
  648. // an exact sized one if there's an larger one available in the pool. Later we will use the whole buffer.
  649. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  650. // wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
  651. using (var _ = new PooledBuffer(buffer))
  652. {
  653. int readBytes = 0;
  654. while (contentLength > 0)
  655. {
  656. if (this.baseRequest.IsCancellationRequested)
  657. return;
  658. readBytes = 0;
  659. do
  660. {
  661. // tryToReadCount contain how much bytes we want to read in once. We try to read the buffer fully in once,
  662. // but with a limit of the remaining contentLength.
  663. int tryToReadCount = (int)Math.Min(Math.Min(int.MaxValue, contentLength), buffer.Length - readBytes);
  664. int bytes = stream.Read(buffer, readBytes, tryToReadCount);
  665. if (bytes <= 0)
  666. throw ExceptionHelper.ServerClosedTCPStream();
  667. readBytes += bytes;
  668. contentLength -= bytes;
  669. // Progress report:
  670. if (sendProgressChanged)
  671. {
  672. downloaded += bytes;
  673. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, downloaded, downloadLength));
  674. }
  675. } while (readBytes < buffer.Length && contentLength > 0);
  676. if (baseRequest.UseStreaming)
  677. {
  678. if (compressed)
  679. {
  680. var decompressed = decompressor.Decompress(buffer, 0, readBytes, false, true);
  681. if (decompressed.Data != null)
  682. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  683. }
  684. else
  685. FeedStreamFragment(buffer, 0, readBytes);
  686. }
  687. else
  688. output.Write(buffer, 0, readBytes);
  689. };
  690. //BufferPool.Release(buffer);
  691. if (baseRequest.UseStreaming)
  692. {
  693. if (compressed)
  694. {
  695. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  696. if (decompressed.Data != null)
  697. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  698. }
  699. FlushRemainingFragmentBuffer();
  700. }
  701. if (!baseRequest.UseStreaming)
  702. this.Data = DecodeStream(output);
  703. }
  704. }
  705. if (decompressor != null)
  706. decompressor.Dispose();
  707. }
  708. #endregion
  709. #region Read Unknown Size
  710. protected void ReadUnknownSize(Stream stream)
  711. {
  712. // Progress report:
  713. long Downloaded = 0;
  714. long DownloadLength = 0;
  715. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  716. #if !BESTHTTP_DISABLE_CACHING
  717. || this.IsFromCache
  718. #endif
  719. );
  720. if (sendProgressChanged)
  721. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  722. string encoding =
  723. #if !BESTHTTP_DISABLE_CACHING
  724. IsFromCache ? null :
  725. #endif
  726. GetFirstHeaderValue("content-encoding");
  727. bool compressed = !string.IsNullOrEmpty(encoding);
  728. Decompression.IDecompressor decompressor = baseRequest.UseStreaming ? Decompression.DecompressorFactory.GetDecompressor(encoding, this.Context) : null;
  729. using (var output = new BufferPoolMemoryStream())
  730. {
  731. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  732. // wrap buffer in a PooledBuffer to release it back to the pool when leaving the current block.
  733. using (var _ = new PooledBuffer(buffer))
  734. {
  735. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  736. VerboseLogging(string.Format("ReadUnknownSize - buffer size: {0:N0}", buffer.Length));
  737. int readBytes = 0;
  738. int bytes = 0;
  739. do
  740. {
  741. readBytes = 0;
  742. do
  743. {
  744. if (this.baseRequest.IsCancellationRequested)
  745. return;
  746. bytes = 0;
  747. #if !NETFX_CORE || UNITY_EDITOR
  748. NetworkStream networkStream = stream as NetworkStream;
  749. // If we have the good-old NetworkStream, than we can use the DataAvailable property. On WP8 platforms, these are omitted... :/
  750. if (networkStream != null && baseRequest.EnableSafeReadOnUnknownContentLength)
  751. {
  752. for (int i = readBytes; i < buffer.Length && networkStream.DataAvailable; ++i)
  753. {
  754. int read = stream.ReadByte();
  755. if (read >= 0)
  756. {
  757. buffer[i] = (byte)read;
  758. bytes++;
  759. }
  760. else
  761. break;
  762. }
  763. }
  764. else // This will be good anyway, but a little slower.
  765. #endif
  766. {
  767. bytes = stream.Read(buffer, readBytes, buffer.Length - readBytes);
  768. }
  769. readBytes += bytes;
  770. // Progress report:
  771. Downloaded += bytes;
  772. DownloadLength = Downloaded;
  773. if (sendProgressChanged)
  774. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  775. } while (readBytes < buffer.Length && bytes > 0);
  776. if (baseRequest.UseStreaming)
  777. {
  778. if (compressed)
  779. {
  780. var decompressed = decompressor.Decompress(buffer, 0, readBytes, false, true);
  781. if (decompressed.Data != null)
  782. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  783. }
  784. else
  785. FeedStreamFragment(buffer, 0, readBytes);
  786. }
  787. else if (readBytes > 0)
  788. output.Write(buffer, 0, readBytes);
  789. } while (bytes > 0);
  790. //BufferPool.Release(buffer);
  791. if (baseRequest.UseStreaming)
  792. {
  793. if (compressed)
  794. {
  795. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  796. if (decompressed.Data != null)
  797. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  798. }
  799. FlushRemainingFragmentBuffer();
  800. }
  801. if (!baseRequest.UseStreaming)
  802. this.Data = DecodeStream(output);
  803. }
  804. }
  805. if (decompressor != null)
  806. decompressor.Dispose();
  807. }
  808. #endregion
  809. #region Stream Decoding
  810. protected byte[] DecodeStream(BufferPoolMemoryStream streamToDecode)
  811. {
  812. streamToDecode.Seek(0, SeekOrigin.Begin);
  813. // The cache stores the decoded data
  814. var encoding =
  815. #if !BESTHTTP_DISABLE_CACHING
  816. IsFromCache ? null :
  817. #endif
  818. GetHeaderValues("content-encoding");
  819. Stream decoderStream = Decompression.DecompressorFactory.GetDecoderStream(streamToDecode, encoding?[0]);
  820. // Under WebGL decoderStream is always null.
  821. if (decoderStream == null)
  822. return streamToDecode.ToArray(canBeLarger: false);
  823. #if !UNITY_WEBGL || UNITY_EDITOR
  824. using (var ms = new BufferPoolMemoryStream((int)streamToDecode.Length))
  825. {
  826. var buf = BufferPool.Get(HTTPResponse.MinReadBufferSize, true);
  827. int byteCount = 0;
  828. while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
  829. ms.Write(buf, 0, byteCount);
  830. BufferPool.Release(buf);
  831. decoderStream.Dispose();
  832. return ms.ToArray();
  833. }
  834. #else
  835. return streamToDecode.ToArray(canBeLarger: false);
  836. #endif
  837. }
  838. #endregion
  839. #region Streaming Fragments Support
  840. protected void BeginReceiveStreamFragments()
  841. {
  842. #if !BESTHTTP_DISABLE_CACHING
  843. if (!baseRequest.DisableCache && baseRequest.UseStreaming)
  844. {
  845. // If caching is enabled and the response not from cache and it's cacheble we will cache the downloaded data.
  846. if (!IsFromCache && HTTPCacheService.IsCacheble(baseRequest.CurrentUri, baseRequest.MethodType, this))
  847. cacheStream = HTTPCacheService.PrepareStreamed(baseRequest.CurrentUri, this);
  848. }
  849. #endif
  850. allFragmentSize = 0;
  851. }
  852. /// <summary>
  853. /// Add data to the fragments list.
  854. /// </summary>
  855. /// <param name="buffer">The buffer to be added.</param>
  856. /// <param name="pos">The position where we start copy the data.</param>
  857. /// <param name="length">How many data we want to copy.</param>
  858. protected void FeedStreamFragment(byte[] buffer, int pos, int length)
  859. {
  860. if (buffer == null || length == 0)
  861. return;
  862. // If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
  863. #if !UNITY_WEBGL || UNITY_EDITOR
  864. #if CSHARP_7_3_OR_NEWER
  865. SpinWait spinWait = new SpinWait();
  866. #endif
  867. while (!this.baseRequest.IsCancellationRequested &&
  868. this.baseRequest.State == HTTPRequestStates.Processing &&
  869. baseRequest.UseStreaming &&
  870. FragmentQueueIsFull())
  871. {
  872. VerboseLogging("WaitWhileFragmentQueueIsFull");
  873. #if CSHARP_7_3_OR_NEWER
  874. spinWait.SpinOnce();
  875. #elif !NETFX_CORE
  876. System.Threading.Thread.Sleep(1);
  877. #endif
  878. }
  879. #endif
  880. if (fragmentBuffer == null)
  881. {
  882. fragmentBuffer = BufferPool.Get(baseRequest.StreamFragmentSize, true);
  883. fragmentBufferDataLength = 0;
  884. }
  885. if (fragmentBufferDataLength + length <= fragmentBuffer.Length)
  886. {
  887. Array.Copy(buffer, pos, fragmentBuffer, fragmentBufferDataLength, length);
  888. fragmentBufferDataLength += length;
  889. if (fragmentBufferDataLength == fragmentBuffer.Length || baseRequest.StreamChunksImmediately)
  890. {
  891. AddStreamedFragment(fragmentBuffer, fragmentBufferDataLength);
  892. fragmentBuffer = null;
  893. fragmentBufferDataLength = 0;
  894. }
  895. }
  896. else
  897. {
  898. int remaining = fragmentBuffer.Length - fragmentBufferDataLength;
  899. FeedStreamFragment(buffer, pos, remaining);
  900. FeedStreamFragment(buffer, pos + remaining, length - remaining);
  901. }
  902. }
  903. protected void FlushRemainingFragmentBuffer()
  904. {
  905. if (fragmentBuffer != null)
  906. {
  907. AddStreamedFragment(fragmentBuffer, fragmentBufferDataLength);
  908. fragmentBuffer = null;
  909. fragmentBufferDataLength = 0;
  910. }
  911. #if !BESTHTTP_DISABLE_CACHING
  912. if (cacheStream != null)
  913. {
  914. cacheStream.Dispose();
  915. cacheStream = null;
  916. HTTPCacheService.SetBodyLength(baseRequest.CurrentUri, allFragmentSize);
  917. }
  918. #endif
  919. }
  920. #if NET_STANDARD_2_0 || NETFX_CORE
  921. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  922. #endif
  923. protected void AddStreamedFragment(byte[] buffer, int bufferLength)
  924. {
  925. #if !BESTHTTP_DISABLE_CACHING
  926. if (!IsCacheOnly)
  927. #endif
  928. {
  929. if (this.baseRequest.UseStreaming && buffer != null && bufferLength > 0)
  930. {
  931. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, buffer, bufferLength));
  932. Interlocked.Increment(ref this.UnprocessedFragments);
  933. }
  934. }
  935. if (HTTPManager.Logger.Level == Logger.Loglevels.All && buffer != null)
  936. VerboseLogging(string.Format("AddStreamedFragment buffer length: {0:N0} UnprocessedFragments: {1:N0}", bufferLength, Interlocked.Read(ref this.UnprocessedFragments)));
  937. #if !BESTHTTP_DISABLE_CACHING
  938. if (cacheStream != null)
  939. {
  940. cacheStream.Write(buffer, 0, bufferLength);
  941. allFragmentSize += bufferLength;
  942. }
  943. #endif
  944. }
  945. #if NET_STANDARD_2_0 || NETFX_CORE
  946. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  947. #endif
  948. private bool FragmentQueueIsFull()
  949. {
  950. #if !UNITY_WEBGL || UNITY_EDITOR
  951. long unprocessedFragments = Interlocked.Read(ref UnprocessedFragments);
  952. bool result = unprocessedFragments >= baseRequest.MaxFragmentQueueLength;
  953. if (result && HTTPManager.Logger.Level == Logger.Loglevels.All)
  954. VerboseLogging(string.Format("FragmentQueueIsFull - {0} / {1}", unprocessedFragments, baseRequest.MaxFragmentQueueLength));
  955. return result;
  956. #else
  957. return false;
  958. #endif
  959. }
  960. #endregion
  961. void VerboseLogging(string str)
  962. {
  963. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  964. HTTPManager.Logger.Verbose("HTTPResponse", str, this.Context, this.baseRequest.Context);
  965. }
  966. /// <summary>
  967. /// IDisposable implementation.
  968. /// </summary>
  969. public void Dispose()
  970. {
  971. Dispose(true);
  972. GC.SuppressFinalize(this);
  973. }
  974. protected virtual void Dispose(bool disposing)
  975. {
  976. if (disposing)
  977. {
  978. // Release resources in case we are using ReadOnlyBufferedStream, it will not close its inner stream.
  979. // Otherwise, closing the (inner) Stream is the connection's responsibility
  980. if (Stream != null && Stream is ReadOnlyBufferedStream)
  981. (Stream as IDisposable).Dispose();
  982. Stream = null;
  983. #if !BESTHTTP_DISABLE_CACHING
  984. if (cacheStream != null)
  985. {
  986. cacheStream.Dispose();
  987. cacheStream = null;
  988. }
  989. #endif
  990. }
  991. }
  992. }
  993. }