HTTPResponse.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  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 && (!UNITY_WEBGL || UNITY_EDITOR)
  12. using BestHTTP.Caching;
  13. #endif
  14. using BestHTTP.Extensions;
  15. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  16. using BestHTTP.Cookies;
  17. #endif
  18. public interface IProtocol
  19. {
  20. bool IsClosed { get; }
  21. void HandleEvents();
  22. }
  23. /// <summary>
  24. ///
  25. /// </summary>
  26. public class HTTPResponse : IDisposable
  27. {
  28. internal const byte CR = 13;
  29. internal const byte LF = 10;
  30. public const int MinBufferSize = 4 * 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. /// <summary>
  51. /// True if the streaming is finished, and no more fragments are coming.
  52. /// </summary>
  53. public bool IsStreamingFinished { get; internal set; }
  54. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  55. /// <summary>
  56. /// Indicates that the response body is read from the cache.
  57. /// </summary>
  58. public bool IsFromCache { get; internal set; }
  59. /// <summary>
  60. /// Provides information about the file used for caching the request.
  61. /// </summary>
  62. public HTTPCacheFileInfo CacheFileInfo { get; internal set; }
  63. /// <summary>
  64. /// Determines if this response is only stored to cache.
  65. /// If both IsCacheOnly and IsStreamed are true, GetStreamedFragments should not be called.
  66. /// </summary>
  67. public bool IsCacheOnly { get; private set; }
  68. #endif
  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 && (!UNITY_WEBGL || UNITY_EDITOR)
  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.ARGB32, false);
  121. texture.LoadImage(Data);
  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. #endregion
  130. #region Internal Fields
  131. internal HTTPRequest baseRequest;
  132. #endregion
  133. #region Protected Properties And Fields
  134. protected Stream Stream;
  135. protected List<byte[]> streamedFragments;
  136. protected object SyncRoot = new object();
  137. protected byte[] fragmentBuffer;
  138. protected int fragmentBufferDataLength;
  139. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  140. protected Stream cacheStream;
  141. #endif
  142. protected int allFragmentSize;
  143. #endregion
  144. public HTTPResponse(HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache)
  145. {
  146. this.baseRequest = request;
  147. this.Stream = stream;
  148. this.IsStreamed = isStreamed;
  149. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  150. this.IsFromCache = isFromCache;
  151. this.IsCacheOnly = request.CacheOnly;
  152. #endif
  153. this.IsClosedManually = false;
  154. }
  155. public virtual bool Receive(int forceReadRawContentLength = -1, bool readPayloadData = true)
  156. {
  157. string statusLine = string.Empty;
  158. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  159. VerboseLogging(string.Format("Receive. forceReadRawContentLength: '{0:N0}', readPayloadData: '{1:N0}'", forceReadRawContentLength, readPayloadData));
  160. // On WP platform we aren't able to determined sure enough whether the tcp connection is closed or not.
  161. // So if we get an exception here, we need to recreate the connection.
  162. try
  163. {
  164. // Read out 'HTTP/1.1' from the "HTTP/1.1 {StatusCode} {Message}"
  165. statusLine = ReadTo(Stream, (byte)' ');
  166. }
  167. catch
  168. {
  169. if (!baseRequest.DisableRetry)
  170. {
  171. HTTPManager.Logger.Warning("HTTPResponse", string.Format("{0} - Failed to read Status Line! Retry is enabled, returning with false.", this.baseRequest.CurrentUri.ToString()));
  172. return false;
  173. }
  174. HTTPManager.Logger.Warning("HTTPResponse", string.Format("{0} - Failed to read Status Line! Retry is disabled, re-throwing exception.", this.baseRequest.CurrentUri.ToString()));
  175. throw;
  176. }
  177. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  178. VerboseLogging(string.Format("Status Line: '{0}'", statusLine));
  179. if (string.IsNullOrEmpty(statusLine))
  180. {
  181. if (!baseRequest.DisableRetry)
  182. return false;
  183. throw new Exception("Remote server closed the connection before sending response header!");
  184. }
  185. string[] versions = statusLine.Split(new char[] { '/', '.' });
  186. this.VersionMajor = int.Parse(versions[1]);
  187. this.VersionMinor = int.Parse(versions[2]);
  188. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  189. VerboseLogging(string.Format("HTTP Version: '{0}.{1}'", this.VersionMajor.ToString(), this.VersionMinor.ToString()));
  190. int statusCode;
  191. string statusCodeStr = NoTrimReadTo(Stream, (byte)' ', LF);
  192. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  193. VerboseLogging(string.Format("Status Code: '{0}'", statusCodeStr));
  194. if (baseRequest.DisableRetry)
  195. statusCode = int.Parse(statusCodeStr);
  196. else if (!int.TryParse(statusCodeStr, out statusCode))
  197. return false;
  198. this.StatusCode = statusCode;
  199. if (statusCodeStr.Length > 0 && (byte)statusCodeStr[statusCodeStr.Length - 1] != LF && (byte)statusCodeStr[statusCodeStr.Length - 1] != CR)
  200. {
  201. this.Message = ReadTo(Stream, LF);
  202. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  203. VerboseLogging(string.Format("Status Message: '{0}'", this.Message));
  204. }
  205. else
  206. {
  207. HTTPManager.Logger.Warning("HTTPResponse", string.Format("{0} - Skipping Status Message reading!", this.baseRequest.CurrentUri.ToString()));
  208. this.Message = string.Empty;
  209. }
  210. //Read Headers
  211. ReadHeaders(Stream);
  212. IsUpgraded = StatusCode == 101 && (HasHeaderWithValue("connection", "upgrade") || HasHeader("upgrade"));
  213. if (IsUpgraded && HTTPManager.Logger.Level == Logger.Loglevels.All)
  214. VerboseLogging("Request Upgraded!");
  215. if (!readPayloadData)
  216. return true;
  217. return ReadPayload(forceReadRawContentLength);
  218. }
  219. protected bool ReadPayload(int forceReadRawContentLength)
  220. {
  221. // Reading from an already unpacked stream (eq. From a file cache)
  222. if (forceReadRawContentLength != -1)
  223. {
  224. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  225. this.IsFromCache = true;
  226. #endif
  227. ReadRaw(Stream, forceReadRawContentLength);
  228. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  229. VerboseLogging("ReadPayload Finished!");
  230. return true;
  231. }
  232. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
  233. // 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)
  234. // is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.
  235. if ((StatusCode >= 100 && StatusCode < 200) || StatusCode == 204 || StatusCode == 304 || baseRequest.MethodType == HTTPMethods.Head)
  236. return true;
  237. #if (!UNITY_WEBGL || UNITY_EDITOR)
  238. if (HasHeaderWithValue("transfer-encoding", "chunked"))
  239. ReadChunked(Stream);
  240. else
  241. #endif
  242. {
  243. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
  244. // Case 3 in the above link.
  245. List<string> contentLengthHeaders = GetHeaderValues("content-length");
  246. var contentRangeHeaders = GetHeaderValues("content-range");
  247. if (contentLengthHeaders != null && contentRangeHeaders == null)
  248. ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
  249. else if (contentRangeHeaders != null)
  250. {
  251. if (contentLengthHeaders != null)
  252. ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
  253. else
  254. {
  255. HTTPRange range = GetRange();
  256. ReadRaw(Stream, (range.LastBytePos - range.FirstBytePos) + 1);
  257. }
  258. }
  259. else
  260. ReadUnknownSize(Stream);
  261. }
  262. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  263. VerboseLogging("ReadPayload Finished!");
  264. return true;
  265. }
  266. #region Header Management
  267. protected void ReadHeaders(Stream stream)
  268. {
  269. string headerName = ReadTo(stream, (byte)':', LF).Trim();
  270. while (headerName != string.Empty)
  271. {
  272. string value = ReadTo(stream, LF);
  273. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  274. VerboseLogging(string.Format("Header - '{0}': '{1}'", headerName, value));
  275. AddHeader(headerName, value);
  276. headerName = ReadTo(stream, (byte)':', LF);
  277. }
  278. }
  279. protected void AddHeader(string name, string value)
  280. {
  281. name = name.ToLower();
  282. if (Headers == null)
  283. Headers = new Dictionary<string, List<string>>();
  284. List<string> values;
  285. if (!Headers.TryGetValue(name, out values))
  286. Headers.Add(name, values = new List<string>(1));
  287. values.Add(value);
  288. }
  289. /// <summary>
  290. /// Returns the list of values that received from the server for the given header name.
  291. /// <remarks>Remarks: All headers converted to lowercase while reading the response.</remarks>
  292. /// </summary>
  293. /// <param name="name">Name of the header</param>
  294. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  295. public List<string> GetHeaderValues(string name)
  296. {
  297. if (Headers == null)
  298. return null;
  299. name = name.ToLower();
  300. List<string> values;
  301. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  302. return null;
  303. return values;
  304. }
  305. /// <summary>
  306. /// Returns the first value in the header list or null if there are no header or value.
  307. /// </summary>
  308. /// <param name="name">Name of the header</param>
  309. /// <returns>If no header found with the given name or there are no values in the list (eg. Count == 0) returns null.</returns>
  310. public string GetFirstHeaderValue(string name)
  311. {
  312. if (Headers == null)
  313. return null;
  314. name = name.ToLower();
  315. List<string> values;
  316. if (!Headers.TryGetValue(name, out values) || values.Count == 0)
  317. return null;
  318. return values[0];
  319. }
  320. /// <summary>
  321. /// Checks if there is a header with the given name and value.
  322. /// </summary>
  323. /// <param name="headerName">Name of the header.</param>
  324. /// <param name="value"></param>
  325. /// <returns>Returns true if there is a header with the given name and value.</returns>
  326. public bool HasHeaderWithValue(string headerName, string value)
  327. {
  328. var values = GetHeaderValues(headerName);
  329. if (values == null)
  330. return false;
  331. for (int i = 0; i < values.Count; ++i)
  332. if (string.Compare(values[i], value, StringComparison.OrdinalIgnoreCase) == 0)
  333. return true;
  334. return false;
  335. }
  336. /// <summary>
  337. /// Checks if there is a header with the given name.
  338. /// </summary>
  339. /// <param name="headerName">Name of the header.</param>
  340. /// <returns>Returns true if there is a header with the given name.</returns>
  341. public bool HasHeader(string headerName)
  342. {
  343. var values = GetHeaderValues(headerName);
  344. if (values == null)
  345. return false;
  346. return true;
  347. }
  348. /// <summary>
  349. /// Parses the 'Content-Range' header's value and returns a HTTPRange object.
  350. /// </summary>
  351. /// <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.
  352. /// (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>
  353. /// <returns>Returns null if no 'Content-Range' header found.</returns>
  354. public HTTPRange GetRange()
  355. {
  356. var rangeHeaders = GetHeaderValues("content-range");
  357. if (rangeHeaders == null)
  358. return null;
  359. // A byte-content-range-spec with a byte-range-resp-spec whose last- byte-pos value is less than its first-byte-pos value,
  360. // or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
  361. // The recipient of an invalid byte-content-range- spec MUST ignore it and any content transferred along with it.
  362. // A valid content-range sample: "bytes 500-1233/1234"
  363. var ranges = rangeHeaders[0].Split(new char[] { ' ', '-', '/' }, StringSplitOptions.RemoveEmptyEntries);
  364. // 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 "*".
  365. // The instance-length specifies the current length of the selected resource.
  366. // "bytes */1234"
  367. if (ranges[1] == "*")
  368. return new HTTPRange(int.Parse(ranges[2]));
  369. return new HTTPRange(int.Parse(ranges[1]), int.Parse(ranges[2]), ranges[3] != "*" ? int.Parse(ranges[3]) : -1);
  370. }
  371. #endregion
  372. #region Static Stream Management Helper Functions
  373. public static string ReadTo(Stream stream, byte blocker)
  374. {
  375. using (var ms = new MemoryStream())
  376. {
  377. int ch = stream.ReadByte();
  378. while (ch != blocker && ch != -1)
  379. {
  380. ms.WriteByte((byte)ch);
  381. ch = stream.ReadByte();
  382. }
  383. return ms.ToArray().AsciiToString().Trim();
  384. }
  385. }
  386. public static string ReadTo(Stream stream, byte blocker1, byte blocker2)
  387. {
  388. using (var ms = new MemoryStream())
  389. {
  390. int ch = stream.ReadByte();
  391. while (ch != blocker1 && ch != blocker2 && ch != -1)
  392. {
  393. ms.WriteByte((byte)ch);
  394. ch = stream.ReadByte();
  395. }
  396. return ms.ToArray().AsciiToString().Trim();
  397. }
  398. }
  399. public static string NoTrimReadTo(Stream stream, byte blocker1, byte blocker2)
  400. {
  401. using (var ms = new MemoryStream())
  402. {
  403. int ch = stream.ReadByte();
  404. while (ch != blocker1 && ch != blocker2 && ch != -1)
  405. {
  406. ms.WriteByte((byte)ch);
  407. ch = stream.ReadByte();
  408. }
  409. return ms.ToArray().AsciiToString();
  410. }
  411. }
  412. #endregion
  413. #region Read Chunked Body
  414. protected int ReadChunkLength(Stream stream)
  415. {
  416. // Read until the end of line, then split the string so we will discard any optional chunk extensions
  417. string line = ReadTo(stream, LF);
  418. string[] splits = line.Split(';');
  419. string num = splits[0];
  420. int result;
  421. if (int.TryParse(num, System.Globalization.NumberStyles.AllowHexSpecifier, null, out result))
  422. return result;
  423. throw new Exception(string.Format("Can't parse '{0}' as a hex number!", num));
  424. }
  425. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
  426. protected void ReadChunked(Stream stream)
  427. {
  428. BeginReceiveStreamFragments();
  429. string contentLengthHeader = GetFirstHeaderValue("Content-Length");
  430. bool hasContentLengthHeader = !string.IsNullOrEmpty(contentLengthHeader);
  431. int realLength = 0;
  432. if (hasContentLengthHeader)
  433. hasContentLengthHeader = int.TryParse(contentLengthHeader, out realLength);
  434. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  435. VerboseLogging(string.Format("ReadChunked - hasContentLengthHeader: {0}, contentLengthHeader: {1} realLength: {2:N0}", hasContentLengthHeader.ToString(), contentLengthHeader, realLength));
  436. using (var output = new MemoryStream())
  437. {
  438. int chunkLength = ReadChunkLength(stream);
  439. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  440. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  441. byte[] buffer = new byte[chunkLength];
  442. int contentLength = 0;
  443. // Progress report:
  444. baseRequest.DownloadLength = hasContentLengthHeader ? realLength : chunkLength;
  445. baseRequest.DownloadProgressChanged = this.IsSuccess
  446. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  447. || this.IsFromCache
  448. #endif
  449. ;
  450. string encoding =
  451. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  452. IsFromCache ? null :
  453. #endif
  454. GetFirstHeaderValue("content-encoding");
  455. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  456. while (chunkLength != 0)
  457. {
  458. // To avoid more GC garbage we use only one buffer, and resize only if the next chunk doesn't fit.
  459. if (buffer.Length < chunkLength)
  460. Array.Resize<byte>(ref buffer, chunkLength);
  461. int readBytes = 0;
  462. // Fill up the buffer
  463. do
  464. {
  465. int bytes = stream.Read(buffer, readBytes, chunkLength - readBytes);
  466. if (bytes <= 0)
  467. throw ExceptionHelper.ServerClosedTCPStream();
  468. readBytes += bytes;
  469. // Progress report:
  470. // Placing reporting inside this cycle will report progress much more frequent
  471. baseRequest.Downloaded += bytes;
  472. baseRequest.DownloadProgressChanged = this.IsSuccess
  473. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  474. || this.IsFromCache
  475. #endif
  476. ;
  477. } while (readBytes < chunkLength);
  478. if (baseRequest.UseStreaming)
  479. {
  480. // If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
  481. WaitWhileHasFragments();
  482. if (gzipped)
  483. {
  484. var decompressed = Decompress(buffer, 0, readBytes);
  485. if (decompressed != null)
  486. FeedStreamFragment(decompressed, 0, decompressed.Length);
  487. }
  488. else
  489. FeedStreamFragment(buffer, 0, readBytes);
  490. }
  491. else
  492. output.Write(buffer, 0, readBytes);
  493. // Every chunk data has a trailing CRLF
  494. ReadTo(stream, LF);
  495. contentLength += readBytes;
  496. // read the next chunk's length
  497. chunkLength = ReadChunkLength(stream);
  498. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  499. VerboseLogging(string.Format("chunkLength: {0:N0}", chunkLength));
  500. if (!hasContentLengthHeader)
  501. baseRequest.DownloadLength += chunkLength;
  502. baseRequest.DownloadProgressChanged = this.IsSuccess
  503. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  504. || this.IsFromCache
  505. #endif
  506. ;
  507. }
  508. if (baseRequest.UseStreaming)
  509. {
  510. if (gzipped)
  511. {
  512. var decompressed = Decompress(null, 0, 0, true);
  513. if (decompressed != null)
  514. FeedStreamFragment(decompressed, 0, decompressed.Length);
  515. }
  516. FlushRemainingFragmentBuffer();
  517. }
  518. // Read the trailing headers or the CRLF
  519. ReadHeaders(stream);
  520. // HTTP servers sometimes use compression (gzip) or deflate methods to optimize transmission.
  521. // How both chunked and gzip encoding interact is dictated by the two-staged encoding of HTTP:
  522. // 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).
  523. // 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.
  524. // The remote endpoint can decode the incoming stream by first decoding it with the Transfer-Encoding, followed by the specified Content-Encoding.
  525. // 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.
  526. if (!baseRequest.UseStreaming)
  527. this.Data = DecodeStream(output);
  528. }
  529. }
  530. #endregion
  531. #region Read Raw Body
  532. // No transfer-encoding just raw bytes.
  533. internal void ReadRaw(Stream stream, long contentLength)
  534. {
  535. BeginReceiveStreamFragments();
  536. // Progress report:
  537. baseRequest.DownloadLength = contentLength;
  538. baseRequest.DownloadProgressChanged = this.IsSuccess
  539. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  540. || this.IsFromCache
  541. #endif
  542. ;
  543. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  544. VerboseLogging(string.Format("ReadRaw - contentLength: {0:N0}", contentLength));
  545. string encoding =
  546. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  547. IsFromCache ? null :
  548. #endif
  549. GetFirstHeaderValue("content-encoding");
  550. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  551. if(!baseRequest.UseStreaming && contentLength > 2147483646)
  552. {
  553. throw new OverflowException("You have to use STREAMING to download files bigger than 2GB!");
  554. }
  555. using (var output = new MemoryStream(baseRequest.UseStreaming ? 0 : (int)contentLength))
  556. {
  557. byte[] buffer = new byte[Math.Max(baseRequest.StreamFragmentSize, MinBufferSize)];
  558. int readBytes = 0;
  559. while (contentLength > 0)
  560. {
  561. readBytes = 0;
  562. do
  563. {
  564. int readbuffer = (int)Math.Min(2147483646, (uint)contentLength);
  565. int bytes = stream.Read(buffer, readBytes, Math.Min(readbuffer, buffer.Length - readBytes));
  566. if (bytes <= 0)
  567. throw ExceptionHelper.ServerClosedTCPStream();
  568. readBytes += bytes;
  569. contentLength -= bytes;
  570. // Progress report:
  571. baseRequest.Downloaded += bytes;
  572. baseRequest.DownloadProgressChanged = this.IsSuccess
  573. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  574. || this.IsFromCache
  575. #endif
  576. ;
  577. } while (readBytes < buffer.Length && contentLength > 0);
  578. if (baseRequest.UseStreaming)
  579. {
  580. // If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
  581. WaitWhileHasFragments();
  582. if (gzipped)
  583. {
  584. var decompressed = Decompress(buffer, 0, readBytes);
  585. if (decompressed != null)
  586. FeedStreamFragment(decompressed, 0, decompressed.Length);
  587. }
  588. else
  589. FeedStreamFragment(buffer, 0, readBytes);
  590. }
  591. else
  592. output.Write(buffer, 0, readBytes);
  593. };
  594. if (baseRequest.UseStreaming)
  595. {
  596. if (gzipped)
  597. {
  598. var decompressed = Decompress(null, 0, 0, true);
  599. if (decompressed != null)
  600. FeedStreamFragment(decompressed, 0, decompressed.Length);
  601. }
  602. FlushRemainingFragmentBuffer();
  603. }
  604. if (!baseRequest.UseStreaming)
  605. this.Data = DecodeStream(output);
  606. }
  607. }
  608. #endregion
  609. #region Read Unknown Size
  610. protected void ReadUnknownSize(Stream stream)
  611. {
  612. string encoding =
  613. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  614. IsFromCache ? null :
  615. #endif
  616. GetFirstHeaderValue("content-encoding");
  617. bool gzipped = !string.IsNullOrEmpty(encoding) && encoding == "gzip";
  618. using (var output = new MemoryStream())
  619. {
  620. byte[] buffer = new byte[Math.Max(baseRequest.StreamFragmentSize, MinBufferSize)];
  621. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  622. VerboseLogging(string.Format("ReadUnknownSize - buffer size: {0:N0}", buffer.Length));
  623. int readBytes = 0;
  624. int bytes = 0;
  625. do
  626. {
  627. readBytes = 0;
  628. do
  629. {
  630. bytes = 0;
  631. #if (!NETFX_CORE && !UNITY_WP8) || UNITY_EDITOR
  632. NetworkStream networkStream = stream as NetworkStream;
  633. // If we have the good-old NetworkStream, than we can use the DataAvailable property. On WP8 platforms, these are omitted... :/
  634. if (networkStream != null && baseRequest.EnableSafeReadOnUnknownContentLength)
  635. {
  636. for (int i = readBytes; i < buffer.Length && networkStream.DataAvailable; ++i)
  637. {
  638. int read = stream.ReadByte();
  639. if (read >= 0)
  640. {
  641. buffer[i] = (byte)read;
  642. bytes++;
  643. }
  644. else
  645. break;
  646. }
  647. }
  648. else // This will be good anyway, but a little slower.
  649. #endif
  650. {
  651. bytes = stream.Read(buffer, readBytes, buffer.Length - readBytes);
  652. }
  653. readBytes += bytes;
  654. // Progress report:
  655. baseRequest.Downloaded += bytes;
  656. baseRequest.DownloadLength = baseRequest.Downloaded;
  657. baseRequest.DownloadProgressChanged = this.IsSuccess
  658. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  659. || this.IsFromCache
  660. #endif
  661. ;
  662. } while (readBytes < buffer.Length && bytes > 0);
  663. if (baseRequest.UseStreaming)
  664. {
  665. // If reading from cache, we don't want to read too much data to memory. So we will wait until the loaded fragment processed.
  666. WaitWhileHasFragments();
  667. if (gzipped)
  668. {
  669. var decompressed = Decompress(buffer, 0, readBytes);
  670. if (decompressed != null)
  671. FeedStreamFragment(decompressed, 0, decompressed.Length);
  672. }
  673. else
  674. FeedStreamFragment(buffer, 0, readBytes);
  675. }
  676. else
  677. output.Write(buffer, 0, readBytes);
  678. } while (bytes > 0);
  679. if (baseRequest.UseStreaming)
  680. {
  681. if (gzipped)
  682. {
  683. var decompressed = Decompress(null, 0, 0, true);
  684. if (decompressed != null)
  685. FeedStreamFragment(decompressed, 0, decompressed.Length);
  686. }
  687. FlushRemainingFragmentBuffer();
  688. }
  689. if (!baseRequest.UseStreaming)
  690. this.Data = DecodeStream(output);
  691. }
  692. }
  693. #endregion
  694. #region Stream Decoding
  695. protected byte[] DecodeStream(MemoryStream streamToDecode)
  696. {
  697. streamToDecode.Seek(0, SeekOrigin.Begin);
  698. // The cache stores the decoded data
  699. var encoding =
  700. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  701. IsFromCache ? null :
  702. #endif
  703. GetHeaderValues("content-encoding");
  704. #if !UNITY_WEBGL || UNITY_EDITOR
  705. Stream decoderStream = null;
  706. #endif
  707. // Return early if there are no encoding used.
  708. if (encoding == null)
  709. return streamToDecode.ToArray();
  710. else
  711. {
  712. switch (encoding[0])
  713. {
  714. #if !UNITY_WEBGL || UNITY_EDITOR
  715. case "gzip": decoderStream = new Decompression.Zlib.GZipStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress); break;
  716. case "deflate": decoderStream = new Decompression.Zlib.DeflateStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress); break;
  717. #endif
  718. //identity, utf-8, etc.
  719. default:
  720. // Do not copy from one stream to an other, just return with the raw bytes
  721. return streamToDecode.ToArray();
  722. }
  723. }
  724. #if !UNITY_WEBGL || UNITY_EDITOR
  725. using (var ms = new MemoryStream((int)streamToDecode.Length))
  726. {
  727. var buf = new byte[1024];
  728. int byteCount = 0;
  729. while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
  730. ms.Write(buf, 0, byteCount);
  731. return ms.ToArray();
  732. }
  733. #endif
  734. }
  735. #endregion
  736. #region Streaming Fragments Support
  737. private System.IO.MemoryStream decompressorInputStream;
  738. private System.IO.MemoryStream decompressorOutputStream;
  739. private Decompression.Zlib.GZipStream decompressorGZipStream;
  740. private byte[] copyBuffer;
  741. const int MinLengthToDecompress = 256;
  742. private byte[] Decompress(byte[] data, int offset, int count, bool forceDecompress = false)
  743. {
  744. if (decompressorInputStream == null)
  745. decompressorInputStream = new MemoryStream(count);
  746. if (data != null)
  747. decompressorInputStream.Write(data, offset, count);
  748. if (!forceDecompress && decompressorInputStream.Length < MinLengthToDecompress)
  749. return null;
  750. decompressorInputStream.Position = 0;
  751. if (decompressorGZipStream == null)
  752. {
  753. decompressorGZipStream = new Decompression.Zlib.GZipStream(decompressorInputStream,
  754. Decompression.Zlib.CompressionMode.Decompress,
  755. Decompression.Zlib.CompressionLevel.Default,
  756. true);
  757. decompressorGZipStream.FlushMode = Decompression.Zlib.FlushType.Sync;
  758. }
  759. if (decompressorOutputStream == null)
  760. decompressorOutputStream = new System.IO.MemoryStream();
  761. decompressorOutputStream.SetLength(0);
  762. if (copyBuffer == null)
  763. copyBuffer = new byte[1024];
  764. int readCount;
  765. while ((readCount = decompressorGZipStream.Read(copyBuffer, 0, copyBuffer.Length)) != 0)
  766. decompressorOutputStream.Write(copyBuffer, 0, readCount);
  767. decompressorGZipStream.SetLength(0);
  768. byte[] result = decompressorOutputStream.ToArray();
  769. return result;
  770. }
  771. protected void BeginReceiveStreamFragments()
  772. {
  773. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  774. if (!baseRequest.DisableCache && baseRequest.UseStreaming)
  775. {
  776. // If caching is enabled and the response not from cache and it's cacheble we will cache the downloaded data.
  777. if (!IsFromCache && HTTPCacheService.IsCacheble(baseRequest.CurrentUri, baseRequest.MethodType, this))
  778. cacheStream = HTTPCacheService.PrepareStreamed(baseRequest.CurrentUri, this);
  779. }
  780. #endif
  781. allFragmentSize = 0;
  782. }
  783. /// <summary>
  784. /// Add data to the fragments list.
  785. /// </summary>
  786. /// <param name="buffer">The buffer to be added.</param>
  787. /// <param name="pos">The position where we start copy the data.</param>
  788. /// <param name="length">How many data we want to copy.</param>
  789. protected void FeedStreamFragment(byte[] buffer, int pos, int length)
  790. {
  791. if (buffer == null || length == 0)
  792. return;
  793. if (fragmentBuffer == null)
  794. {
  795. fragmentBuffer = new byte[baseRequest.StreamFragmentSize];
  796. fragmentBufferDataLength = 0;
  797. }
  798. if (fragmentBufferDataLength + length <= baseRequest.StreamFragmentSize)
  799. {
  800. Array.Copy(buffer, pos, fragmentBuffer, fragmentBufferDataLength, length);
  801. fragmentBufferDataLength += length;
  802. if (fragmentBufferDataLength == baseRequest.StreamFragmentSize)
  803. {
  804. AddStreamedFragment(fragmentBuffer);
  805. fragmentBuffer = null;
  806. fragmentBufferDataLength = 0;
  807. }
  808. }
  809. else
  810. {
  811. int remaining = baseRequest.StreamFragmentSize - fragmentBufferDataLength;
  812. FeedStreamFragment(buffer, pos, remaining);
  813. FeedStreamFragment(buffer, pos + remaining, length - remaining);
  814. }
  815. }
  816. protected void FlushRemainingFragmentBuffer()
  817. {
  818. if (fragmentBuffer != null)
  819. {
  820. Array.Resize<byte>(ref fragmentBuffer, fragmentBufferDataLength);
  821. AddStreamedFragment(fragmentBuffer);
  822. fragmentBuffer = null;
  823. fragmentBufferDataLength = 0;
  824. }
  825. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  826. if (cacheStream != null)
  827. {
  828. cacheStream.Dispose();
  829. cacheStream = null;
  830. HTTPCacheService.SetBodyLength(baseRequest.CurrentUri, allFragmentSize);
  831. }
  832. #endif
  833. }
  834. protected void AddStreamedFragment(byte[] buffer)
  835. {
  836. lock (SyncRoot)
  837. {
  838. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  839. if (!IsCacheOnly)
  840. #endif
  841. {
  842. if (streamedFragments == null)
  843. streamedFragments = new List<byte[]>();
  844. streamedFragments.Add(buffer);
  845. }
  846. if (HTTPManager.Logger.Level == Logger.Loglevels.All && buffer != null && streamedFragments != null)
  847. VerboseLogging(string.Format("AddStreamedFragment buffer length: {0:N0} streamedFragments: {1:N0}", buffer.Length, streamedFragments.Count));
  848. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  849. if (cacheStream != null)
  850. {
  851. cacheStream.Write(buffer, 0, buffer.Length);
  852. allFragmentSize += buffer.Length;
  853. }
  854. #endif
  855. }
  856. }
  857. protected
  858. #if NETFX_CORE
  859. async
  860. #endif
  861. void WaitWhileHasFragments()
  862. {
  863. VerboseLogging("WaitWhileHasFragments");
  864. #if !UNITY_WEBGL || UNITY_EDITOR
  865. while (baseRequest.UseStreaming && HasFragmentsInQueue())
  866. {
  867. #if NETFX_CORE
  868. await System.Threading.Tasks.Task.Delay(16);
  869. #else
  870. System.Threading.Thread.Sleep(16);
  871. #endif
  872. }
  873. #endif
  874. }
  875. bool HasFragmentsInQueue()
  876. {
  877. lock (SyncRoot)
  878. {
  879. bool result = streamedFragments != null && streamedFragments.Count >= baseRequest.MaxFragmentQueueLength;
  880. if (result && HTTPManager.Logger.Level == Logger.Loglevels.All)
  881. VerboseLogging(string.Format("HasFragmentsInQueue - {0} / {1}", streamedFragments.Count, baseRequest.MaxFragmentQueueLength));
  882. return result;
  883. }
  884. }
  885. /// <summary>
  886. /// If streaming is used, then every time this callback function called we can use this function to
  887. /// retrieve the downloaded and buffered data. The returned list can be null, if there is no data yet.
  888. /// </summary>
  889. /// <returns></returns>
  890. public List<byte[]> GetStreamedFragments()
  891. {
  892. lock (SyncRoot)
  893. {
  894. if (streamedFragments == null || streamedFragments.Count == 0)
  895. {
  896. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  897. VerboseLogging("GetStreamedFragments - no fragments, returning with null");
  898. return null;
  899. }
  900. var result = new List<byte[]>(streamedFragments);
  901. streamedFragments.Clear();
  902. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  903. VerboseLogging(string.Format("GetStreamedFragments - returning with {0:N0} fragments", result.Count.ToString()));
  904. return result;
  905. }
  906. }
  907. internal bool HasStreamedFragments()
  908. {
  909. lock (SyncRoot)
  910. return streamedFragments != null && streamedFragments.Count >= baseRequest.MaxFragmentQueueLength;
  911. }
  912. internal void FinishStreaming()
  913. {
  914. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  915. VerboseLogging("FinishStreaming");
  916. IsStreamingFinished = true;
  917. Dispose();
  918. }
  919. #endregion
  920. void VerboseLogging(string str)
  921. {
  922. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  923. HTTPManager.Logger.Verbose("HTTPResponse", "'" + this.baseRequest.CurrentUri.ToString() + "' - " + str);
  924. }
  925. /// <summary>
  926. /// IDisposable implementation.
  927. /// </summary>
  928. public void Dispose()
  929. {
  930. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  931. if (cacheStream != null)
  932. {
  933. cacheStream.Dispose();
  934. cacheStream = null;
  935. }
  936. #endif
  937. }
  938. }
  939. }