You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HTTPResponse.cs 38KB

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