HTTPResponse.cs 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242
  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(0, 0, 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 virtual 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>>() : 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. name = name.ToLower();
  327. if (Headers == null)
  328. Headers = new Dictionary<string, List<string>>();
  329. List<string> values;
  330. if (!Headers.TryGetValue(name, out values))
  331. Headers.Add(name, values = new List<string>(1));
  332. values.Add(value);
  333. bool isFromCache = false;
  334. #if !BESTHTTP_DISABLE_CACHING
  335. isFromCache = this.IsFromCache;
  336. #endif
  337. if (!isFromCache && name.Equals("alt-svc", StringComparison.Ordinal))
  338. PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(this.baseRequest.CurrentUri.Host, this)));
  339. }
  340. /// <summary>
  341. /// Returns the list of values that received from the server for the given header name.
  342. /// <remarks>Remarks: All headers converted to lowercase while reading the response.</remarks>
  343. /// </summary>
  344. /// <param name="name">Name of the header</param>
  345. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  346. public List<string> GetHeaderValues(string name)
  347. {
  348. if (Headers == null)
  349. return null;
  350. name = name.ToLower();
  351. List<string> values;
  352. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  353. return null;
  354. return values;
  355. }
  356. /// <summary>
  357. /// Returns the first value in the header list or null if there are no header or value.
  358. /// </summary>
  359. /// <param name="name">Name of the header</param>
  360. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  361. public string GetFirstHeaderValue(string name)
  362. {
  363. if (Headers == null)
  364. return null;
  365. name = name.ToLower();
  366. List<string> values;
  367. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  368. return null;
  369. return values[0];
  370. }
  371. /// <summary>
  372. /// Checks if there is a header with the given name and value.
  373. /// </summary>
  374. /// <param name="headerName">Name of the header.</param>
  375. /// <param name="value"></param>
  376. /// <returns>Returns true if there is a header with the given name and value.</returns>
  377. public bool HasHeaderWithValue(string headerName, string value)
  378. {
  379. var values = GetHeaderValues(headerName);
  380. if (values == null)
  381. return false;
  382. for (int i = 0; i < values.Count; ++i)
  383. if (string.Compare(values[i], value, StringComparison.OrdinalIgnoreCase) == 0)
  384. return true;
  385. return false;
  386. }
  387. /// <summary>
  388. /// Checks if there is a header with the given name.
  389. /// </summary>
  390. /// <param name="headerName">Name of the header.</param>
  391. /// <returns>Returns true if there is a header with the given name.</returns>
  392. public bool HasHeader(string headerName)
  393. {
  394. var values = GetHeaderValues(headerName);
  395. if (values == null)
  396. return false;
  397. return true;
  398. }
  399. /// <summary>
  400. /// Parses the 'Content-Range' header's value and returns a HTTPRange object.
  401. /// </summary>
  402. /// <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.
  403. /// (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>
  404. /// <returns>Returns null if no 'Content-Range' header found.</returns>
  405. public HTTPRange GetRange()
  406. {
  407. var rangeHeaders = GetHeaderValues("content-range");
  408. if (rangeHeaders == null)
  409. return null;
  410. // A byte-content-range-spec with a byte-range-resp-spec whose last- byte-pos value is less than its first-byte-pos value,
  411. // or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
  412. // The recipient of an invalid byte-content-range- spec MUST ignore it and any content transferred along with it.
  413. // A valid content-range sample: "bytes 500-1233/1234"
  414. var ranges = rangeHeaders[0].Split(new char[] { ' ', '-', '/' }, StringSplitOptions.RemoveEmptyEntries);
  415. // 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 "*".
  416. // The instance-length specifies the current length of the selected resource.
  417. // "bytes */1234"
  418. if (ranges[1] == "*")
  419. return new HTTPRange(int.Parse(ranges[2]));
  420. return new HTTPRange(int.Parse(ranges[1]), int.Parse(ranges[2]), ranges[3] != "*" ? int.Parse(ranges[3]) : -1);
  421. }
  422. #endregion
  423. #region Static Stream Management Helper Functions
  424. internal static string ReadTo(Stream stream, byte blocker)
  425. {
  426. byte[] readBuf = BufferPool.Get(1024, true);
  427. try
  428. {
  429. int bufpos = 0;
  430. int ch = stream.ReadByte();
  431. while (ch != blocker && ch != -1)
  432. {
  433. if (ch > 0x7f) //replaces asciitostring
  434. ch = '?';
  435. //make buffer larger if too short
  436. if (readBuf.Length <= bufpos)
  437. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, false);
  438. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  439. readBuf[bufpos++] = (byte)ch;
  440. ch = stream.ReadByte();
  441. }
  442. while (bufpos > 0 && char.IsWhiteSpace((char)readBuf[bufpos - 1]))
  443. bufpos--;
  444. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  445. }
  446. finally
  447. {
  448. BufferPool.Release(readBuf);
  449. }
  450. }
  451. internal static string ReadTo(Stream stream, byte blocker1, byte blocker2)
  452. {
  453. byte[] readBuf = BufferPool.Get(1024, true);
  454. try {
  455. int bufpos = 0;
  456. int ch = stream.ReadByte();
  457. while (ch != blocker1 && ch != blocker2 && ch != -1)
  458. {
  459. if (ch > 0x7f) //replaces asciitostring
  460. ch = '?';
  461. //make buffer larger if too short
  462. if (readBuf.Length <= bufpos)
  463. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, true);
  464. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  465. readBuf[bufpos++] = (byte)ch;
  466. ch = stream.ReadByte();
  467. }
  468. while (bufpos > 0 && char.IsWhiteSpace((char)readBuf[bufpos - 1]))
  469. bufpos--;
  470. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  471. }
  472. finally
  473. {
  474. BufferPool.Release(readBuf);
  475. }
  476. }
  477. internal static string NoTrimReadTo(Stream stream, byte blocker1, byte blocker2)
  478. {
  479. byte[] readBuf = BufferPool.Get(1024, true);
  480. try {
  481. int bufpos = 0;
  482. int ch = stream.ReadByte();
  483. while (ch != blocker1 && ch != blocker2 && ch != -1)
  484. {
  485. if (ch > 0x7f) //replaces asciitostring
  486. ch = '?';
  487. //make buffer larger if too short
  488. if (readBuf.Length <= bufpos)
  489. BufferPool.Resize(ref readBuf, readBuf.Length * 2, true, true);
  490. if (bufpos > 0 || !char.IsWhiteSpace((char)ch)) //trimstart
  491. readBuf[bufpos++] = (byte)ch;
  492. ch = stream.ReadByte();
  493. }
  494. return System.Text.Encoding.UTF8.GetString(readBuf, 0, bufpos);
  495. }
  496. finally
  497. {
  498. BufferPool.Release(readBuf);
  499. }
  500. }
  501. #endregion
  502. #region Read Chunked Body
  503. protected int ReadChunkLength(Stream stream)
  504. {
  505. // Read until the end of line, then split the string so we will discard any optional chunk extensions
  506. string line = ReadTo(stream, LF);
  507. string[] splits = line.Split(';');
  508. string num = splits[0];
  509. int result;
  510. if (int.TryParse(num, System.Globalization.NumberStyles.AllowHexSpecifier, null, out result))
  511. return result;
  512. throw new Exception(string.Format("Can't parse '{0}' as a hex number!", num));
  513. }
  514. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
  515. protected void ReadChunked(Stream stream)
  516. {
  517. BeginReceiveStreamFragments();
  518. string contentLengthHeader = GetFirstHeaderValue("Content-Length");
  519. bool hasContentLengthHeader = !string.IsNullOrEmpty(contentLengthHeader);
  520. int realLength = 0;
  521. if (hasContentLengthHeader)
  522. hasContentLengthHeader = int.TryParse(contentLengthHeader, out realLength);
  523. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  524. VerboseLogging(string.Format("ReadChunked - hasContentLengthHeader: {0}, contentLengthHeader: {1} realLength: {2:N0}", hasContentLengthHeader.ToString(), contentLengthHeader, realLength));
  525. using (var output = new BufferPoolMemoryStream())
  526. {
  527. int chunkLength = ReadChunkLength(stream);
  528. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  529. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  530. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  531. // Progress report:
  532. long Downloaded = 0;
  533. long DownloadLength = hasContentLengthHeader ? realLength : chunkLength;
  534. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  535. #if !BESTHTTP_DISABLE_CACHING
  536. || this.IsFromCache
  537. #endif
  538. );
  539. if (sendProgressChanged)
  540. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  541. string encoding =
  542. #if !BESTHTTP_DISABLE_CACHING
  543. IsFromCache ? null :
  544. #endif
  545. GetFirstHeaderValue("content-encoding");
  546. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  547. Decompression.GZipDecompressor decompressor = gzipped ? new Decompression.GZipDecompressor(256) : null;
  548. while (chunkLength != 0)
  549. {
  550. if (this.baseRequest.IsCancellationRequested)
  551. return;
  552. int totalBytes = 0;
  553. // Fill up the buffer
  554. do
  555. {
  556. int tryToReadCount = (int)Math.Min(chunkLength - totalBytes, buffer.Length);
  557. int bytes = stream.Read(buffer, 0, tryToReadCount);
  558. if (bytes <= 0)
  559. throw ExceptionHelper.ServerClosedTCPStream();
  560. // Progress report:
  561. // Placing reporting inside this cycle will report progress much more frequent
  562. Downloaded += bytes;
  563. if (sendProgressChanged)
  564. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  565. if (baseRequest.UseStreaming)
  566. {
  567. if (gzipped)
  568. {
  569. var decompressed = decompressor.Decompress(buffer, 0, bytes, false, true);
  570. if (decompressed.Data != null)
  571. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  572. }
  573. else
  574. FeedStreamFragment(buffer, 0, bytes);
  575. }
  576. else
  577. output.Write(buffer, 0, bytes);
  578. totalBytes += bytes;
  579. } while (totalBytes < chunkLength);
  580. // Every chunk data has a trailing CRLF
  581. ReadTo(stream, LF);
  582. // read the next chunk's length
  583. chunkLength = ReadChunkLength(stream);
  584. if (!hasContentLengthHeader)
  585. DownloadLength += chunkLength;
  586. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  587. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  588. }
  589. BufferPool.Release(buffer);
  590. if (baseRequest.UseStreaming)
  591. {
  592. if (gzipped)
  593. {
  594. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  595. if (decompressed.Data != null)
  596. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  597. }
  598. FlushRemainingFragmentBuffer();
  599. }
  600. // Read the trailing headers or the CRLF
  601. ReadHeaders(stream);
  602. // HTTP servers sometimes use compression (gzip) or deflate methods to optimize transmission.
  603. // How both chunked and gzip encoding interact is dictated by the two-staged encoding of HTTP:
  604. // 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).
  605. // 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.
  606. // The remote endpoint can decode the incoming stream by first decoding it with the Transfer-Encoding, followed by the specified Content-Encoding.
  607. // 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.
  608. if (!baseRequest.UseStreaming)
  609. this.Data = DecodeStream(output);
  610. if (decompressor != null)
  611. decompressor.Dispose();
  612. }
  613. }
  614. #endregion
  615. #region Read Raw Body
  616. // No transfer-encoding just raw bytes.
  617. internal void ReadRaw(Stream stream, long contentLength)
  618. {
  619. BeginReceiveStreamFragments();
  620. // Progress report:
  621. long downloaded = 0;
  622. long downloadLength = contentLength;
  623. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  624. #if !BESTHTTP_DISABLE_CACHING
  625. || this.IsFromCache
  626. #endif
  627. );
  628. if (sendProgressChanged)
  629. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, downloaded, downloadLength));
  630. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  631. VerboseLogging(string.Format("ReadRaw - contentLength: {0:N0}", contentLength));
  632. string encoding =
  633. #if !BESTHTTP_DISABLE_CACHING
  634. IsFromCache ? null :
  635. #endif
  636. GetFirstHeaderValue("content-encoding");
  637. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  638. Decompression.GZipDecompressor decompressor = gzipped ? new Decompression.GZipDecompressor(256) : null;
  639. if (!baseRequest.UseStreaming && contentLength > 2147483646)
  640. {
  641. throw new OverflowException("You have to use STREAMING to download files bigger than 2GB!");
  642. }
  643. using (var output = new BufferPoolMemoryStream(baseRequest.UseStreaming ? 0 : (int)contentLength))
  644. {
  645. // Because of the last parameter, buffer's size can be larger than the requested but there's no reason to use
  646. // an exact sized one if there's an larger one available in the pool. Later we will use the whole buffer.
  647. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  648. int readBytes = 0;
  649. while (contentLength > 0)
  650. {
  651. if (this.baseRequest.IsCancellationRequested)
  652. return;
  653. readBytes = 0;
  654. do
  655. {
  656. // tryToReadCount contain how much bytes we want to read in once. We try to read the buffer fully in once,
  657. // but with a limit of the remaining contentLength.
  658. int tryToReadCount = (int)Math.Min(Math.Min(int.MaxValue, contentLength), buffer.Length - readBytes);
  659. int bytes = stream.Read(buffer, readBytes, tryToReadCount);
  660. if (bytes <= 0)
  661. throw ExceptionHelper.ServerClosedTCPStream();
  662. readBytes += bytes;
  663. contentLength -= bytes;
  664. // Progress report:
  665. if (sendProgressChanged)
  666. {
  667. downloaded += bytes;
  668. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, downloaded, downloadLength));
  669. }
  670. } while (readBytes < buffer.Length && contentLength > 0);
  671. if (baseRequest.UseStreaming)
  672. {
  673. if (gzipped)
  674. {
  675. var decompressed = decompressor.Decompress(buffer, 0, readBytes, false, true);
  676. if (decompressed.Data != null)
  677. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  678. }
  679. else
  680. FeedStreamFragment(buffer, 0, readBytes);
  681. }
  682. else
  683. output.Write(buffer, 0, readBytes);
  684. };
  685. BufferPool.Release(buffer);
  686. if (baseRequest.UseStreaming)
  687. {
  688. if (gzipped)
  689. {
  690. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  691. if (decompressed.Data != null)
  692. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  693. }
  694. FlushRemainingFragmentBuffer();
  695. }
  696. if (!baseRequest.UseStreaming)
  697. this.Data = DecodeStream(output);
  698. }
  699. if (decompressor != null)
  700. decompressor.Dispose();
  701. }
  702. #endregion
  703. #region Read Unknown Size
  704. protected void ReadUnknownSize(Stream stream)
  705. {
  706. // Progress report:
  707. long Downloaded = 0;
  708. long DownloadLength = 0;
  709. bool sendProgressChanged = this.baseRequest.OnDownloadProgress != null && (this.IsSuccess
  710. #if !BESTHTTP_DISABLE_CACHING
  711. || this.IsFromCache
  712. #endif
  713. );
  714. if (sendProgressChanged)
  715. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  716. string encoding =
  717. #if !BESTHTTP_DISABLE_CACHING
  718. IsFromCache ? null :
  719. #endif
  720. GetFirstHeaderValue("content-encoding");
  721. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  722. Decompression.GZipDecompressor decompressor = gzipped ? new Decompression.GZipDecompressor(256) : null;
  723. using (var output = new BufferPoolMemoryStream())
  724. {
  725. byte[] buffer = baseRequest.ReadBufferSizeOverride > 0 ? BufferPool.Get(baseRequest.ReadBufferSizeOverride, false) : BufferPool.Get(MinReadBufferSize, true);
  726. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  727. VerboseLogging(string.Format("ReadUnknownSize - buffer size: {0:N0}", buffer.Length));
  728. int readBytes = 0;
  729. int bytes = 0;
  730. do
  731. {
  732. readBytes = 0;
  733. do
  734. {
  735. if (this.baseRequest.IsCancellationRequested)
  736. return;
  737. bytes = 0;
  738. #if !NETFX_CORE || UNITY_EDITOR
  739. NetworkStream networkStream = stream as NetworkStream;
  740. // If we have the good-old NetworkStream, than we can use the DataAvailable property. On WP8 platforms, these are omitted... :/
  741. if (networkStream != null && baseRequest.EnableSafeReadOnUnknownContentLength)
  742. {
  743. for (int i = readBytes; i < buffer.Length && networkStream.DataAvailable; ++i)
  744. {
  745. int read = stream.ReadByte();
  746. if (read >= 0)
  747. {
  748. buffer[i] = (byte)read;
  749. bytes++;
  750. }
  751. else
  752. break;
  753. }
  754. }
  755. else // This will be good anyway, but a little slower.
  756. #endif
  757. {
  758. bytes = stream.Read(buffer, readBytes, buffer.Length - readBytes);
  759. }
  760. readBytes += bytes;
  761. // Progress report:
  762. Downloaded += bytes;
  763. DownloadLength = Downloaded;
  764. if (sendProgressChanged)
  765. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.DownloadProgress, Downloaded, DownloadLength));
  766. } while (readBytes < buffer.Length && bytes > 0);
  767. if (baseRequest.UseStreaming)
  768. {
  769. if (gzipped)
  770. {
  771. var decompressed = decompressor.Decompress(buffer, 0, readBytes, false, true);
  772. if (decompressed.Data != null)
  773. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  774. }
  775. else
  776. FeedStreamFragment(buffer, 0, readBytes);
  777. }
  778. else if (readBytes > 0)
  779. output.Write(buffer, 0, readBytes);
  780. } while (bytes > 0);
  781. BufferPool.Release(buffer);
  782. if (baseRequest.UseStreaming)
  783. {
  784. if (gzipped)
  785. {
  786. var decompressed = decompressor.Decompress(null, 0, 0, true, true);
  787. if (decompressed.Data != null)
  788. FeedStreamFragment(decompressed.Data, 0, decompressed.Length);
  789. }
  790. FlushRemainingFragmentBuffer();
  791. }
  792. if (!baseRequest.UseStreaming)
  793. this.Data = DecodeStream(output);
  794. }
  795. if (decompressor != null)
  796. decompressor.Dispose();
  797. }
  798. #endregion
  799. #region Stream Decoding
  800. protected byte[] DecodeStream(BufferPoolMemoryStream streamToDecode)
  801. {
  802. streamToDecode.Seek(0, SeekOrigin.Begin);
  803. // The cache stores the decoded data
  804. var encoding =
  805. #if !BESTHTTP_DISABLE_CACHING
  806. IsFromCache ? null :
  807. #endif
  808. GetHeaderValues("content-encoding");
  809. #if !UNITY_WEBGL || UNITY_EDITOR
  810. Stream decoderStream = null;
  811. #endif
  812. // Return early if there are no encoding used.
  813. if (encoding == null)
  814. return streamToDecode.ToArray();
  815. else
  816. {
  817. switch (encoding[0])
  818. {
  819. #if !UNITY_WEBGL || UNITY_EDITOR
  820. case "gzip": decoderStream = new Decompression.Zlib.GZipStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress); break;
  821. case "deflate": decoderStream = new Decompression.Zlib.DeflateStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress); break;
  822. #endif
  823. //identity, utf-8, etc.
  824. default:
  825. // Do not copy from one stream to an other, just return with the raw bytes
  826. return streamToDecode.ToArray();
  827. }
  828. }
  829. #if !UNITY_WEBGL || UNITY_EDITOR
  830. using (var ms = new BufferPoolMemoryStream((int)streamToDecode.Length))
  831. {
  832. var buf = BufferPool.Get(1024, true);
  833. int byteCount = 0;
  834. while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
  835. ms.Write(buf, 0, byteCount);
  836. BufferPool.Release(buf);
  837. decoderStream.Dispose();
  838. return ms.ToArray();
  839. }
  840. #endif
  841. }
  842. #endregion
  843. #region Streaming Fragments Support
  844. protected void BeginReceiveStreamFragments()
  845. {
  846. #if !BESTHTTP_DISABLE_CACHING
  847. if (!baseRequest.DisableCache && baseRequest.UseStreaming)
  848. {
  849. // If caching is enabled and the response not from cache and it's cacheble we will cache the downloaded data.
  850. if (!IsFromCache && HTTPCacheService.IsCacheble(baseRequest.CurrentUri, baseRequest.MethodType, this))
  851. cacheStream = HTTPCacheService.PrepareStreamed(baseRequest.CurrentUri, this);
  852. }
  853. #endif
  854. allFragmentSize = 0;
  855. }
  856. /// <summary>
  857. /// Add data to the fragments list.
  858. /// </summary>
  859. /// <param name="buffer">The buffer to be added.</param>
  860. /// <param name="pos">The position where we start copy the data.</param>
  861. /// <param name="length">How many data we want to copy.</param>
  862. protected void FeedStreamFragment(byte[] buffer, int pos, int length)
  863. {
  864. if (buffer == null || length == 0)
  865. return;
  866. // If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
  867. #if !UNITY_WEBGL || UNITY_EDITOR
  868. #if CSHARP_7_3_OR_NEWER
  869. SpinWait spinWait = new SpinWait();
  870. #endif
  871. while (!this.baseRequest.IsCancellationRequested &&
  872. this.baseRequest.State == HTTPRequestStates.Processing &&
  873. baseRequest.UseStreaming &&
  874. FragmentQueueIsFull())
  875. {
  876. VerboseLogging("WaitWhileFragmentQueueIsFull");
  877. #if CSHARP_7_3_OR_NEWER
  878. spinWait.SpinOnce();
  879. #elif !NETFX_CORE
  880. System.Threading.Thread.Sleep(1);
  881. #endif
  882. }
  883. #endif
  884. if (fragmentBuffer == null)
  885. {
  886. fragmentBuffer = BufferPool.Get(baseRequest.StreamFragmentSize, true);
  887. fragmentBufferDataLength = 0;
  888. }
  889. if (fragmentBufferDataLength + length <= fragmentBuffer.Length)
  890. {
  891. Array.Copy(buffer, pos, fragmentBuffer, fragmentBufferDataLength, length);
  892. fragmentBufferDataLength += length;
  893. if (fragmentBufferDataLength == fragmentBuffer.Length || baseRequest.StreamChunksImmediately)
  894. {
  895. AddStreamedFragment(fragmentBuffer, fragmentBufferDataLength);
  896. fragmentBuffer = null;
  897. fragmentBufferDataLength = 0;
  898. }
  899. }
  900. else
  901. {
  902. int remaining = fragmentBuffer.Length - fragmentBufferDataLength;
  903. FeedStreamFragment(buffer, pos, remaining);
  904. FeedStreamFragment(buffer, pos + remaining, length - remaining);
  905. }
  906. }
  907. protected void FlushRemainingFragmentBuffer()
  908. {
  909. if (fragmentBuffer != null)
  910. {
  911. AddStreamedFragment(fragmentBuffer, fragmentBufferDataLength);
  912. fragmentBuffer = null;
  913. fragmentBufferDataLength = 0;
  914. }
  915. #if !BESTHTTP_DISABLE_CACHING
  916. if (cacheStream != null)
  917. {
  918. cacheStream.Dispose();
  919. cacheStream = null;
  920. HTTPCacheService.SetBodyLength(baseRequest.CurrentUri, allFragmentSize);
  921. }
  922. #endif
  923. }
  924. #if NET_STANDARD_2_0 || NETFX_CORE
  925. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  926. #endif
  927. protected void AddStreamedFragment(byte[] buffer, int bufferLength)
  928. {
  929. #if !BESTHTTP_DISABLE_CACHING
  930. if (!IsCacheOnly)
  931. #endif
  932. {
  933. if (this.baseRequest.UseStreaming && buffer != null && bufferLength > 0)
  934. {
  935. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, buffer, bufferLength));
  936. Interlocked.Increment(ref this.UnprocessedFragments);
  937. }
  938. }
  939. if (HTTPManager.Logger.Level == Logger.Loglevels.All && buffer != null)
  940. VerboseLogging(string.Format("AddStreamedFragment buffer length: {0:N0} UnprocessedFragments: {1:N0}", bufferLength, Interlocked.Read(ref this.UnprocessedFragments)));
  941. #if !BESTHTTP_DISABLE_CACHING
  942. if (cacheStream != null)
  943. {
  944. cacheStream.Write(buffer, 0, bufferLength);
  945. allFragmentSize += bufferLength;
  946. }
  947. #endif
  948. }
  949. #if NET_STANDARD_2_0 || NETFX_CORE
  950. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  951. #endif
  952. private bool FragmentQueueIsFull()
  953. {
  954. #if !UNITY_WEBGL || UNITY_EDITOR
  955. long unprocessedFragments = Interlocked.Read(ref UnprocessedFragments);
  956. bool result = unprocessedFragments >= baseRequest.MaxFragmentQueueLength;
  957. if (result && HTTPManager.Logger.Level == Logger.Loglevels.All)
  958. VerboseLogging(string.Format("FragmentQueueIsFull - {0} / {1}", unprocessedFragments, baseRequest.MaxFragmentQueueLength));
  959. return result;
  960. #else
  961. return false;
  962. #endif
  963. }
  964. #endregion
  965. void VerboseLogging(string str)
  966. {
  967. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  968. HTTPManager.Logger.Verbose("HTTPResponse", str, this.Context, this.baseRequest.Context);
  969. }
  970. /// <summary>
  971. /// IDisposable implementation.
  972. /// </summary>
  973. public void Dispose()
  974. {
  975. Dispose(true);
  976. GC.SuppressFinalize(this);
  977. }
  978. protected virtual void Dispose(bool disposing)
  979. {
  980. if (disposing)
  981. {
  982. // Release resources in case we are using ReadOnlyBufferedStream, it will not close its inner stream.
  983. // Otherwise, closing the (inner) Stream is the connection's responsibility
  984. if (Stream != null && Stream is ReadOnlyBufferedStream)
  985. (Stream as IDisposable).Dispose();
  986. Stream = null;
  987. #if !BESTHTTP_DISABLE_CACHING
  988. if (cacheStream != null)
  989. {
  990. cacheStream.Dispose();
  991. cacheStream = null;
  992. }
  993. #endif
  994. }
  995. }
  996. }
  997. }