HTTPRequest.cs 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. namespace BestHTTP
  7. {
  8. using BestHTTP.Authentication;
  9. using BestHTTP.Extensions;
  10. using BestHTTP.Forms;
  11. #if !BESTHTTP_DISABLE_COOKIES
  12. using BestHTTP.Cookies;
  13. #endif
  14. using BestHTTP.Core;
  15. using BestHTTP.PlatformSupport.Memory;
  16. using BestHTTP.Connections;
  17. using BestHTTP.Logger;
  18. using BestHTTP.Timings;
  19. /// <summary>
  20. /// Possible logical states of a HTTTPRequest object.
  21. /// </summary>
  22. public enum HTTPRequestStates
  23. {
  24. /// <summary>
  25. /// Initial status of a request. No callback will be called with this status.
  26. /// </summary>
  27. Initial,
  28. /// <summary>
  29. /// The request queued for processing.
  30. /// </summary>
  31. Queued,
  32. /// <summary>
  33. /// Processing of the request started. In this state the client will send the request, and parse the response. No callback will be called with this status.
  34. /// </summary>
  35. Processing,
  36. /// <summary>
  37. /// The request finished without problem. Parsing the response done, the result can be used. The user defined callback will be called with a valid response object. The request’s Exception property will be null.
  38. /// </summary>
  39. Finished,
  40. /// <summary>
  41. /// The request finished with an unexpected error. The user defined callback will be called with a null response object. The request's Exception property may contain more info about the error, but it can be null.
  42. /// </summary>
  43. Error,
  44. /// <summary>
  45. /// The request aborted by the client(HTTPRequest’s Abort() function). The user defined callback will be called with a null response. The request’s Exception property will be null.
  46. /// </summary>
  47. Aborted,
  48. /// <summary>
  49. /// Connecting to the server timed out. The user defined callback will be called with a null response. The request’s Exception property will be null.
  50. /// </summary>
  51. ConnectionTimedOut,
  52. /// <summary>
  53. /// The request didn't finished in the given time. The user defined callback will be called with a null response. The request’s Exception property will be null.
  54. /// </summary>
  55. TimedOut
  56. }
  57. public delegate void OnRequestFinishedDelegate(HTTPRequest originalRequest, HTTPResponse response);
  58. public delegate void OnDownloadProgressDelegate(HTTPRequest originalRequest, long downloaded, long downloadLength);
  59. public delegate void OnUploadProgressDelegate(HTTPRequest originalRequest, long uploaded, long uploadLength);
  60. public delegate bool OnBeforeRedirectionDelegate(HTTPRequest originalRequest, HTTPResponse response, Uri redirectUri);
  61. public delegate void OnHeaderEnumerationDelegate(string header, List<string> values);
  62. public delegate void OnBeforeHeaderSendDelegate(HTTPRequest req);
  63. public delegate void OnHeadersReceivedDelegate(HTTPRequest originalRequest, HTTPResponse response, Dictionary<string, List<string>> headers);
  64. /// <summary>
  65. /// Called for every fragment of data downloaded from the server. Its return value indicates whether the plugin free to reuse the dataFragment array.
  66. /// </summary>
  67. /// <param name="request">The parent HTTPRequest object</param>
  68. /// <param name="response">The HTTPResponse object.</param>
  69. /// <param name="dataFragment">The downloaded data. The byte[] can be larger than the actual payload! Its valid length that can be used is in the dataFragmentLength param.</param>
  70. /// <param name="dataFragmentLength">Length of the downloaded data.</param>
  71. public delegate bool OnStreamingDataDelegate(HTTPRequest request, HTTPResponse response, byte[] dataFragment, int dataFragmentLength);
  72. public sealed class HTTPRequest : IEnumerator, IEnumerator<HTTPRequest>
  73. {
  74. #region Statics
  75. public static readonly byte[] EOL = { HTTPResponse.CR, HTTPResponse.LF };
  76. /// <summary>
  77. /// Cached uppercase values to save some cpu cycles and GC alloc per request.
  78. /// </summary>
  79. public static readonly string[] MethodNames = {
  80. HTTPMethods.Get.ToString().ToUpper(),
  81. HTTPMethods.Head.ToString().ToUpper(),
  82. HTTPMethods.Post.ToString().ToUpper(),
  83. HTTPMethods.Put.ToString().ToUpper(),
  84. HTTPMethods.Delete.ToString().ToUpper(),
  85. HTTPMethods.Patch.ToString().ToUpper(),
  86. HTTPMethods.Merge.ToString().ToUpper(),
  87. HTTPMethods.Options.ToString().ToUpper(),
  88. HTTPMethods.Connect.ToString().ToUpper(),
  89. };
  90. /// <summary>
  91. /// Size of the internal buffer, and upload progress will be fired when this size of data sent to the wire. Its default value is 4 KiB.
  92. /// </summary>
  93. public static int UploadChunkSize = 4 * 1024;
  94. #endregion
  95. #region Properties
  96. /// <summary>
  97. /// The original request's Uri.
  98. /// </summary>
  99. public Uri Uri { get; set; }
  100. /// <summary>
  101. /// The method that how we want to process our request the server.
  102. /// </summary>
  103. public HTTPMethods MethodType { get; set; }
  104. /// <summary>
  105. /// The raw data to send in a POST request. If it set all other fields that added to this request will be ignored.
  106. /// </summary>
  107. public byte[] RawData { get; set; }
  108. /// <summary>
  109. /// The stream that the plugin will use to get the data to send out the server. When this property is set, no forms or the RawData property will be used
  110. /// </summary>
  111. public Stream UploadStream { get; set; }
  112. /// <summary>
  113. /// When set to true(its default value) the plugin will call the UploadStream's Dispose() function when finished uploading the data from it. Default value is true.
  114. /// </summary>
  115. public bool DisposeUploadStream { get; set; }
  116. /// <summary>
  117. /// If it's true, the plugin will use the Stream's Length property. Otherwise the plugin will send the data chunked. Default value is true.
  118. /// </summary>
  119. public bool UseUploadStreamLength { get; set; }
  120. /// <summary>
  121. /// Called after data sent out to the wire.
  122. /// </summary>
  123. public OnUploadProgressDelegate OnUploadProgress;
  124. /// <summary>
  125. /// Indicates that the connection should be open after the response received. If its true, then the internal TCP connections will be reused if it's possible. Default value is true.
  126. /// The default value can be changed in the HTTPManager class. If you make rare request to the server it's should be changed to false.
  127. /// </summary>
  128. public bool IsKeepAlive
  129. {
  130. get { return isKeepAlive; }
  131. set
  132. {
  133. if (State == HTTPRequestStates.Processing)
  134. throw new NotSupportedException("Changing the IsKeepAlive property while processing the request is not supported.");
  135. isKeepAlive = value;
  136. }
  137. }
  138. #if !BESTHTTP_DISABLE_CACHING
  139. /// <summary>
  140. /// With this property caching can be enabled/disabled on a per-request basis.
  141. /// </summary>
  142. public bool DisableCache
  143. {
  144. get { return disableCache; }
  145. set
  146. {
  147. if (State == HTTPRequestStates.Processing)
  148. throw new NotSupportedException("Changing the DisableCache property while processing the request is not supported.");
  149. disableCache = value;
  150. }
  151. }
  152. /// <summary>
  153. /// It can be used with streaming. When set to true, no OnStreamingData event is called, the streamed content will be saved straight to the cache if all requirements are met(caching is enabled and there's a caching headers).
  154. /// </summary>
  155. public bool CacheOnly
  156. {
  157. get { return cacheOnly; }
  158. set
  159. {
  160. if (State == HTTPRequestStates.Processing)
  161. throw new NotSupportedException("Changing the CacheOnly property while processing the request is not supported.");
  162. cacheOnly = value;
  163. }
  164. }
  165. #endif
  166. /// <summary>
  167. /// Maximum size of a data chunk that we want to receive when streaming is set. Its default value is 1 MB.
  168. /// </summary>
  169. public int StreamFragmentSize
  170. {
  171. get{ return streamFragmentSize; }
  172. set
  173. {
  174. if (State == HTTPRequestStates.Processing)
  175. throw new NotSupportedException("Changing the StreamFragmentSize property while processing the request is not supported.");
  176. if (value < 1)
  177. throw new System.ArgumentException("StreamFragmentSize must be at least 1.");
  178. streamFragmentSize = value;
  179. }
  180. }
  181. /// <summary>
  182. /// When set to true, StreamFragmentSize will be ignored and downloaded chunks will be sent immediately.
  183. /// </summary>
  184. public bool StreamChunksImmediately { get; set; }
  185. /// <summary>
  186. /// This property can be used to force the HTTPRequest to use an exact sized read buffer.
  187. /// </summary>
  188. public int ReadBufferSizeOverride { get; set; }
  189. /// <summary>
  190. /// Maximum unprocessed fragments allowed to queue up.
  191. /// </summary>
  192. public int MaxFragmentQueueLength { get; set; }
  193. /// <summary>
  194. /// The callback function that will be called when a request is fully processed or when any downloaded fragment is available if UseStreaming is true. Can be null for fire-and-forget requests.
  195. /// </summary>
  196. public OnRequestFinishedDelegate Callback { get; set; }
  197. /// <summary>
  198. /// When the request is queued for processing.
  199. /// </summary>
  200. public DateTime QueuedAt { get; internal set; }
  201. public bool IsConnectTimedOut { get { return this.QueuedAt != DateTime.MinValue && DateTime.UtcNow - this.QueuedAt > this.ConnectTimeout; } }
  202. /// <summary>
  203. /// When the processing of the request started
  204. /// </summary>
  205. public DateTime ProcessingStarted { get; internal set; }
  206. /// <summary>
  207. /// Returns true if the time passed the Timeout setting since processing started.
  208. /// </summary>
  209. public bool IsTimedOut
  210. {
  211. get
  212. {
  213. DateTime now = DateTime.UtcNow;
  214. return (!this.UseStreaming || (this.UseStreaming && this.EnableTimoutForStreaming)) &&
  215. ((this.ProcessingStarted != DateTime.MinValue && now - this.ProcessingStarted > this.Timeout) ||
  216. this.IsConnectTimedOut);
  217. }
  218. }
  219. /// <summary>
  220. /// Called for every fragment of data downloaded from the server. Return true if dataFrament is processed and the plugin can recycle the byte[].
  221. /// </summary>
  222. public OnStreamingDataDelegate OnStreamingData;
  223. /// <summary>
  224. /// This event is called when the plugin received and parsed all headers.
  225. /// </summary>
  226. public OnHeadersReceivedDelegate OnHeadersReceived;
  227. /// <summary>
  228. /// Number of times that the plugin retried the request.
  229. /// </summary>
  230. public int Retries { get; internal set; }
  231. /// <summary>
  232. /// Maximum number of tries allowed. To disable it set to 0. Its default value is 1 for GET requests, otherwise 0.
  233. /// </summary>
  234. public int MaxRetries { get; set; }
  235. /// <summary>
  236. /// True if Abort() is called on this request.
  237. /// </summary>
  238. public bool IsCancellationRequested { get; internal set; }
  239. /// <summary>
  240. /// Called when new data downloaded from the server.
  241. /// The first parameter is the original HTTTPRequest object itself, the second parameter is the downloaded bytes while the third parameter is the content length.
  242. /// <remarks>There are download modes where we can't figure out the exact length of the final content. In these cases we just guarantee that the third parameter will be at least the size of the second one.</remarks>
  243. /// </summary>
  244. public OnDownloadProgressDelegate OnDownloadProgress;
  245. /// <summary>
  246. /// Indicates that the request is redirected. If a request is redirected, the connection that served it will be closed regardless of the value of IsKeepAlive.
  247. /// </summary>
  248. public bool IsRedirected { get; internal set; }
  249. /// <summary>
  250. /// The Uri that the request redirected to.
  251. /// </summary>
  252. public Uri RedirectUri { get; internal set; }
  253. /// <summary>
  254. /// If redirected it contains the RedirectUri.
  255. /// </summary>
  256. public Uri CurrentUri { get { return IsRedirected ? RedirectUri : Uri; } }
  257. /// <summary>
  258. /// The response to the query.
  259. /// <remarks>If an exception occurred during reading of the response stream or can't connect to the server, this will be null!</remarks>
  260. /// </summary>
  261. public HTTPResponse Response { get; internal set; }
  262. #if !BESTHTTP_DISABLE_PROXY
  263. /// <summary>
  264. /// Response from the Proxy server. It's null with transparent proxies.
  265. /// </summary>
  266. public HTTPResponse ProxyResponse { get; internal set; }
  267. #endif
  268. /// <summary>
  269. /// It there is an exception while processing the request or response the Response property will be null, and the Exception will be stored in this property.
  270. /// </summary>
  271. public Exception Exception { get; internal set; }
  272. /// <summary>
  273. /// Any object can be passed with the request with this property. (eq. it can be identified, etc.)
  274. /// </summary>
  275. public object Tag { get; set; }
  276. /// <summary>
  277. /// The UserName, Password pair that the plugin will use to authenticate to the remote server.
  278. /// </summary>
  279. public Credentials Credentials { get; set; }
  280. #if !BESTHTTP_DISABLE_PROXY
  281. /// <summary>
  282. /// True, if there is a Proxy object.
  283. /// </summary>
  284. public bool HasProxy { get { return Proxy != null && Proxy.UseProxyForAddress(this.CurrentUri); } }
  285. /// <summary>
  286. /// A web proxy's properties where the request must pass through.
  287. /// </summary>
  288. public Proxy Proxy { get; set; }
  289. #endif
  290. /// <summary>
  291. /// How many redirection supported for this request. The default is 10. 0 or a negative value means no redirection supported.
  292. /// </summary>
  293. public int MaxRedirects { get; set; }
  294. #if !BESTHTTP_DISABLE_COOKIES
  295. /// <summary>
  296. /// If true cookies will be added to the headers (if any), and parsed from the response. If false, all cookie operations will be ignored. It's default value is HTTPManager's IsCookiesEnabled.
  297. /// </summary>
  298. public bool IsCookiesEnabled { get; set; }
  299. /// <summary>
  300. /// Cookies that are added to this list will be sent to the server alongside withe the server sent ones. If cookies are disabled only these cookies will be sent.
  301. /// </summary>
  302. public List<Cookie> Cookies
  303. {
  304. get
  305. {
  306. if (customCookies == null)
  307. customCookies = new List<Cookie>();
  308. return customCookies;
  309. }
  310. set { customCookies = value; }
  311. }
  312. private List<Cookie> customCookies;
  313. #endif
  314. /// <summary>
  315. /// What form should used. Its default value is Automatic.
  316. /// </summary>
  317. public HTTPFormUsage FormUsage { get; set; }
  318. /// <summary>
  319. /// Current state of this request.
  320. /// </summary>
  321. public HTTPRequestStates State {
  322. get { return this._state; }
  323. internal set {
  324. lock (this)
  325. {
  326. if (this._state != value)
  327. {
  328. //if (this._state >= HTTPRequestStates.Finished && value >= HTTPRequestStates.Finished)
  329. // return;
  330. this._state = value;
  331. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, this._state));
  332. }
  333. }
  334. }
  335. }
  336. private volatile HTTPRequestStates _state;
  337. /// <summary>
  338. /// How many times redirected.
  339. /// </summary>
  340. public int RedirectCount { get; internal set; }
  341. /// <summary>
  342. /// Maximum time we wait to establish the connection to the target server. If set to TimeSpan.Zero or lower, no connect timeout logic is executed. Default value is 20 seconds.
  343. /// </summary>
  344. public TimeSpan ConnectTimeout { get; set; }
  345. /// <summary>
  346. /// Maximum time we want to wait to the request to finish after the connection is established. Default value is 60 seconds.
  347. /// <remarks>It's disabled for streaming requests! See <see cref="EnableTimoutForStreaming"/>.</remarks>
  348. /// </summary>
  349. public TimeSpan Timeout { get; set; }
  350. /// <summary>
  351. /// Set to true to enable Timeouts on streaming request. Default value is false.
  352. /// </summary>
  353. public bool EnableTimoutForStreaming { get; set; }
  354. /// <summary>
  355. /// Enables safe read method when the response's length of the content is unknown. Its default value is enabled (true).
  356. /// </summary>
  357. public bool EnableSafeReadOnUnknownContentLength { get; set; }
  358. /// <summary>
  359. /// It's called before the plugin will do a new request to the new uri. The return value of this function will control the redirection: if it's false the redirection is aborted.
  360. /// This function is called on a thread other than the main Unity thread!
  361. /// </summary>
  362. public event OnBeforeRedirectionDelegate OnBeforeRedirection
  363. {
  364. add { onBeforeRedirection += value; }
  365. remove { onBeforeRedirection -= value; }
  366. }
  367. private OnBeforeRedirectionDelegate onBeforeRedirection;
  368. /// <summary>
  369. /// This event will be fired before the plugin will write headers to the wire. New headers can be added in this callback. This event is called on a non-Unity thread!
  370. /// </summary>
  371. public event OnBeforeHeaderSendDelegate OnBeforeHeaderSend
  372. {
  373. add { _onBeforeHeaderSend += value; }
  374. remove { _onBeforeHeaderSend -= value; }
  375. }
  376. private OnBeforeHeaderSendDelegate _onBeforeHeaderSend;
  377. /// <summary>
  378. /// Logging context of the request.
  379. /// </summary>
  380. public LoggingContext Context { get; private set; }
  381. /// <summary>
  382. /// Timing information.
  383. /// </summary>
  384. public TimingCollector Timing { get; private set; }
  385. #if UNITY_WEBGL
  386. /// <summary>
  387. /// Its value will be set to the XmlHTTPRequest's withCredentials field. Its default value is HTTPManager.IsCookiesEnabled's value.
  388. /// </summary>
  389. public bool WithCredentials { get; set; }
  390. #endif
  391. #if !UNITY_WEBGL || UNITY_EDITOR
  392. /// <summary>
  393. /// Called when the current protocol is upgraded to an other. (HTTP => WebSocket for example)
  394. /// </summary>
  395. internal OnRequestFinishedDelegate OnUpgraded;
  396. #endif
  397. #region Internal Properties For Progress Report Support
  398. /// <summary>
  399. /// If it's true, the Callback will be called every time if we can send out at least one fragment.
  400. /// </summary>
  401. internal bool UseStreaming { get { return this.OnStreamingData != null; } }
  402. /// <summary>
  403. /// Will return the length of the UploadStream, or -1 if it's not supported.
  404. /// </summary>
  405. internal long UploadStreamLength
  406. {
  407. get
  408. {
  409. if (UploadStream == null || !UseUploadStreamLength)
  410. return -1;
  411. try
  412. {
  413. // This may will throw a NotSupportedException
  414. return UploadStream.Length;
  415. }
  416. catch
  417. {
  418. // We will fall back to chunked
  419. return -1;
  420. }
  421. }
  422. }
  423. #if !UNITY_WEBGL || UNITY_EDITOR
  424. /// <summary>
  425. /// This action is called when a user calls the Abort function. Do not use it outside of the plugin!
  426. /// </summary>
  427. internal Action<HTTPRequest> OnCancellationRequested;
  428. #endif
  429. #endregion
  430. #endregion
  431. #region Privates
  432. private bool isKeepAlive;
  433. #if !BESTHTTP_DISABLE_CACHING
  434. private bool disableCache;
  435. private bool cacheOnly;
  436. #endif
  437. private int streamFragmentSize;
  438. private Dictionary<string, List<string>> Headers { get; set; }
  439. /// <summary>
  440. /// We will collect the fields and values to the FieldCollector through the AddField and AddBinaryData functions.
  441. /// </summary>
  442. private HTTPFormBase FieldCollector;
  443. /// <summary>
  444. /// When the request about to send the request we will create a specialized form implementation(url-encoded, multipart, or the legacy WWWForm based).
  445. /// And we will use this instance to create the data that we will send to the server.
  446. /// </summary>
  447. private HTTPFormBase FormImpl;
  448. #endregion
  449. #region Constructors
  450. #region Default Get Constructors
  451. public HTTPRequest(Uri uri)
  452. : this(uri, HTTPMethods.Get, HTTPManager.KeepAliveDefaultValue,
  453. #if !BESTHTTP_DISABLE_CACHING
  454. HTTPManager.IsCachingDisabled
  455. #else
  456. true
  457. #endif
  458. , null)
  459. {
  460. }
  461. public HTTPRequest(Uri uri, OnRequestFinishedDelegate callback)
  462. : this(uri, HTTPMethods.Get, HTTPManager.KeepAliveDefaultValue,
  463. #if !BESTHTTP_DISABLE_CACHING
  464. HTTPManager.IsCachingDisabled
  465. #else
  466. true
  467. #endif
  468. , callback)
  469. {
  470. }
  471. public HTTPRequest(Uri uri, bool isKeepAlive, OnRequestFinishedDelegate callback)
  472. : this(uri, HTTPMethods.Get, isKeepAlive,
  473. #if !BESTHTTP_DISABLE_CACHING
  474. HTTPManager.IsCachingDisabled
  475. #else
  476. true
  477. #endif
  478. , callback)
  479. {
  480. }
  481. public HTTPRequest(Uri uri, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
  482. : this(uri, HTTPMethods.Get, isKeepAlive, disableCache, callback)
  483. {
  484. }
  485. #endregion
  486. public HTTPRequest(Uri uri, HTTPMethods methodType)
  487. : this(uri, methodType, HTTPManager.KeepAliveDefaultValue,
  488. #if !BESTHTTP_DISABLE_CACHING
  489. HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
  490. #else
  491. true
  492. #endif
  493. , null)
  494. {
  495. }
  496. public HTTPRequest(Uri uri, HTTPMethods methodType, OnRequestFinishedDelegate callback)
  497. : this(uri, methodType, HTTPManager.KeepAliveDefaultValue,
  498. #if !BESTHTTP_DISABLE_CACHING
  499. HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
  500. #else
  501. true
  502. #endif
  503. , callback)
  504. {
  505. }
  506. public HTTPRequest(Uri uri, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
  507. : this(uri, methodType, isKeepAlive,
  508. #if !BESTHTTP_DISABLE_CACHING
  509. HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
  510. #else
  511. true
  512. #endif
  513. , callback)
  514. {
  515. }
  516. public HTTPRequest(Uri uri, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
  517. {
  518. this.Uri = uri;
  519. this.MethodType = methodType;
  520. this.IsKeepAlive = isKeepAlive;
  521. #if !BESTHTTP_DISABLE_CACHING
  522. this.DisableCache = disableCache;
  523. #endif
  524. this.Callback = callback;
  525. this.StreamFragmentSize = 1024 * 1024;
  526. this.MaxFragmentQueueLength = 10;
  527. this.MaxRetries = methodType == HTTPMethods.Get ? 1 : 0;
  528. this.MaxRedirects = 10;
  529. this.RedirectCount = 0;
  530. #if !BESTHTTP_DISABLE_COOKIES
  531. this.IsCookiesEnabled = HTTPManager.IsCookiesEnabled;
  532. #endif
  533. this.State = HTTPRequestStates.Initial;
  534. this.ConnectTimeout = HTTPManager.ConnectTimeout;
  535. this.Timeout = HTTPManager.RequestTimeout;
  536. this.EnableTimoutForStreaming = false;
  537. this.EnableSafeReadOnUnknownContentLength = true;
  538. #if !BESTHTTP_DISABLE_PROXY
  539. this.Proxy = HTTPManager.Proxy;
  540. #endif
  541. this.UseUploadStreamLength = true;
  542. this.DisposeUploadStream = true;
  543. #if UNITY_WEBGL && !BESTHTTP_DISABLE_COOKIES
  544. this.WithCredentials = this.IsCookiesEnabled;
  545. #endif
  546. this.Context = new LoggingContext(this);
  547. this.Timing = new TimingCollector(this);
  548. }
  549. #endregion
  550. #region Public Field Functions
  551. /// <summary>
  552. /// Add a field with a given string value.
  553. /// </summary>
  554. public void AddField(string fieldName, string value)
  555. {
  556. AddField(fieldName, value, System.Text.Encoding.UTF8);
  557. }
  558. /// <summary>
  559. /// Add a field with a given string value.
  560. /// </summary>
  561. public void AddField(string fieldName, string value, System.Text.Encoding e)
  562. {
  563. if (FieldCollector == null)
  564. FieldCollector = new HTTPFormBase();
  565. FieldCollector.AddField(fieldName, value, e);
  566. }
  567. /// <summary>
  568. /// Add a field with binary content to the form.
  569. /// </summary>
  570. public void AddBinaryData(string fieldName, byte[] content)
  571. {
  572. AddBinaryData(fieldName, content, null, null);
  573. }
  574. /// <summary>
  575. /// Add a field with binary content to the form.
  576. /// </summary>
  577. public void AddBinaryData(string fieldName, byte[] content, string fileName)
  578. {
  579. AddBinaryData(fieldName, content, fileName, null);
  580. }
  581. /// <summary>
  582. /// Add a field with binary content to the form.
  583. /// </summary>
  584. public void AddBinaryData(string fieldName, byte[] content, string fileName, string mimeType)
  585. {
  586. if (FieldCollector == null)
  587. FieldCollector = new HTTPFormBase();
  588. FieldCollector.AddBinaryData(fieldName, content, fileName, mimeType);
  589. }
  590. /// <summary>
  591. /// Manually set a HTTP Form.
  592. /// </summary>
  593. public void SetForm(HTTPFormBase form)
  594. {
  595. FormImpl = form;
  596. }
  597. /// <summary>
  598. /// Returns with the added form-fields or null if no one added.
  599. /// </summary>
  600. public List<HTTPFieldData> GetFormFields()
  601. {
  602. if (this.FieldCollector == null || this.FieldCollector.IsEmpty)
  603. return null;
  604. return new List<HTTPFieldData>(this.FieldCollector.Fields);
  605. }
  606. /// <summary>
  607. /// Clears all data from the form.
  608. /// </summary>
  609. public void ClearForm()
  610. {
  611. FormImpl = null;
  612. FieldCollector = null;
  613. }
  614. /// <summary>
  615. /// Will create the form implementation based on the value of the FormUsage property.
  616. /// </summary>
  617. private HTTPFormBase SelectFormImplementation()
  618. {
  619. // Our form already created with a previous
  620. if (FormImpl != null)
  621. return FormImpl;
  622. // No field added to this request yet
  623. if (FieldCollector == null)
  624. return null;
  625. switch (FormUsage)
  626. {
  627. case HTTPFormUsage.Automatic:
  628. // A really simple decision making: if there are at least one field with binary data, or a 'long' string value then we will choose a Multipart form.
  629. // Otherwise Url Encoded form will be used.
  630. if (FieldCollector.HasBinary || FieldCollector.HasLongValue)
  631. goto case HTTPFormUsage.Multipart;
  632. else
  633. goto case HTTPFormUsage.UrlEncoded;
  634. case HTTPFormUsage.UrlEncoded: FormImpl = new HTTPUrlEncodedForm(); break;
  635. case HTTPFormUsage.Multipart: FormImpl = new HTTPMultiPartForm(); break;
  636. }
  637. // Copy the fields, and other properties to the new implementation
  638. FormImpl.CopyFrom(FieldCollector);
  639. return FormImpl;
  640. }
  641. #endregion
  642. #region Header Management
  643. #region General Management
  644. /// <summary>
  645. /// Adds a header and value pair to the Headers. Use it to add custom headers to the request.
  646. /// </summary>
  647. /// <example>AddHeader("User-Agent', "FooBar 1.0")</example>
  648. public void AddHeader(string name, string value)
  649. {
  650. if (Headers == null)
  651. Headers = new Dictionary<string, List<string>>();
  652. List<string> values;
  653. if (!Headers.TryGetValue(name, out values))
  654. Headers.Add(name, values = new List<string>(1));
  655. values.Add(value);
  656. }
  657. /// <summary>
  658. /// Removes any previously added values, and sets the given one.
  659. /// </summary>
  660. public void SetHeader(string name, string value)
  661. {
  662. if (Headers == null)
  663. Headers = new Dictionary<string, List<string>>();
  664. List<string> values;
  665. if (!Headers.TryGetValue(name, out values))
  666. Headers.Add(name, values = new List<string>(1));
  667. values.Clear();
  668. values.Add(value);
  669. }
  670. /// <summary>
  671. /// Removes the specified header. Returns true, if the header found and succesfully removed.
  672. /// </summary>
  673. /// <param name="name"></param>
  674. /// <returns></returns>
  675. public bool RemoveHeader(string name)
  676. {
  677. if (Headers == null)
  678. return false;
  679. return Headers.Remove(name);
  680. }
  681. /// <summary>
  682. /// Returns true if the given head name is already in the Headers.
  683. /// </summary>
  684. public bool HasHeader(string name)
  685. {
  686. return Headers != null && Headers.ContainsKey(name);
  687. }
  688. /// <summary>
  689. /// Returns the first header or null for the given header name.
  690. /// </summary>
  691. public string GetFirstHeaderValue(string name)
  692. {
  693. if (Headers == null)
  694. return null;
  695. List<string> headers = null;
  696. if (Headers.TryGetValue(name, out headers) && headers.Count > 0)
  697. return headers[0];
  698. return null;
  699. }
  700. /// <summary>
  701. /// Returns all header values for the given header or null.
  702. /// </summary>
  703. public List<string> GetHeaderValues(string name)
  704. {
  705. if (Headers == null)
  706. return null;
  707. List<string> headers = null;
  708. if (Headers.TryGetValue(name, out headers) && headers.Count > 0)
  709. return headers;
  710. return null;
  711. }
  712. /// <summary>
  713. /// Removes all headers.
  714. /// </summary>
  715. public void RemoveHeaders()
  716. {
  717. if (Headers == null)
  718. return;
  719. Headers.Clear();
  720. }
  721. #endregion
  722. #region Range Headers
  723. /// <summary>
  724. /// Sets the Range header to download the content from the given byte position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
  725. /// </summary>
  726. /// <param name="firstBytePos">Start position of the download.</param>
  727. public void SetRangeHeader(long firstBytePos)
  728. {
  729. SetHeader("Range", string.Format("bytes={0}-", firstBytePos));
  730. }
  731. /// <summary>
  732. /// Sets the Range header to download the content from the given byte position to the given last position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
  733. /// </summary>
  734. /// <param name="firstBytePos">Start position of the download.</param>
  735. /// <param name="lastBytePos">The end position of the download.</param>
  736. public void SetRangeHeader(long firstBytePos, long lastBytePos)
  737. {
  738. SetHeader("Range", string.Format("bytes={0}-{1}", firstBytePos, lastBytePos));
  739. }
  740. #endregion
  741. public void EnumerateHeaders(OnHeaderEnumerationDelegate callback)
  742. {
  743. EnumerateHeaders(callback, false);
  744. }
  745. public void EnumerateHeaders(OnHeaderEnumerationDelegate callback, bool callBeforeSendCallback)
  746. {
  747. #if !UNITY_WEBGL || UNITY_EDITOR
  748. if (!HasHeader("Host"))
  749. {
  750. if (CurrentUri.Port == 80 || CurrentUri.Port == 443)
  751. SetHeader("Host", CurrentUri.Host);
  752. else
  753. SetHeader("Host", CurrentUri.Authority);
  754. }
  755. if (IsRedirected && !HasHeader("Referer"))
  756. AddHeader("Referer", Uri.ToString());
  757. if (!HasHeader("Accept-Encoding"))
  758. #if BESTHTTP_DISABLE_GZIP
  759. AddHeader("Accept-Encoding", "identity");
  760. #else
  761. AddHeader("Accept-Encoding", "gzip, identity");
  762. #endif
  763. #if !BESTHTTP_DISABLE_PROXY
  764. if (!HTTPProtocolFactory.IsSecureProtocol(this.CurrentUri) && HasProxy && !HasHeader("Proxy-Connection"))
  765. AddHeader("Proxy-Connection", IsKeepAlive ? "Keep-Alive" : "Close");
  766. #endif
  767. if (!HasHeader("Connection"))
  768. AddHeader("Connection", IsKeepAlive ? "Keep-Alive, TE" : "Close, TE");
  769. if (IsKeepAlive && !HasHeader("Keep-Alive"))
  770. {
  771. // Send the server a slightly larger value to make sure it's not going to close sooner than the client
  772. int seconds = (int)Math.Ceiling(HTTPManager.MaxConnectionIdleTime.TotalSeconds + 1);
  773. AddHeader("Keep-Alive", "timeout=" + seconds);
  774. }
  775. if (!HasHeader("TE"))
  776. AddHeader("TE", "identity");
  777. if (!string.IsNullOrEmpty(HTTPManager.UserAgent) && !HasHeader("User-Agent"))
  778. AddHeader("User-Agent", HTTPManager.UserAgent);
  779. #endif
  780. long contentLength = -1;
  781. if (UploadStream == null)
  782. {
  783. byte[] entityBody = GetEntityBody();
  784. contentLength = entityBody != null ? entityBody.Length : 0;
  785. if (RawData == null && (FormImpl != null || (FieldCollector != null && !FieldCollector.IsEmpty)))
  786. {
  787. SelectFormImplementation();
  788. if (FormImpl != null)
  789. FormImpl.PrepareRequest(this);
  790. }
  791. }
  792. else
  793. {
  794. contentLength = UploadStreamLength;
  795. if (contentLength == -1)
  796. SetHeader("Transfer-Encoding", "Chunked");
  797. if (!HasHeader("Content-Type"))
  798. SetHeader("Content-Type", "application/octet-stream");
  799. }
  800. // Always set the Content-Length header if possible
  801. // http://tools.ietf.org/html/rfc2616#section-4.4 : For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 compliant.
  802. // 2018.06.03: Changed the condition so that content-length header will be included for zero length too.
  803. if (
  804. #if !UNITY_WEBGL || UNITY_EDITOR
  805. contentLength >= 0
  806. #else
  807. contentLength != -1
  808. #endif
  809. && !HasHeader("Content-Length"))
  810. SetHeader("Content-Length", contentLength.ToString());
  811. #if !UNITY_WEBGL || UNITY_EDITOR
  812. #if !BESTHTTP_DISABLE_PROXY
  813. // Proxy Authentication
  814. if (!HTTPProtocolFactory.IsSecureProtocol(this.CurrentUri) && HasProxy && Proxy.Credentials != null)
  815. {
  816. switch (Proxy.Credentials.Type)
  817. {
  818. case AuthenticationTypes.Basic:
  819. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  820. SetHeader("Proxy-Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":" + Proxy.Credentials.Password))));
  821. break;
  822. case AuthenticationTypes.Unknown:
  823. case AuthenticationTypes.Digest:
  824. var digest = DigestStore.Get(Proxy.Address);
  825. if (digest != null)
  826. {
  827. string authentication = digest.GenerateResponseHeader(this, Proxy.Credentials);
  828. if (!string.IsNullOrEmpty(authentication))
  829. SetHeader("Proxy-Authorization", authentication);
  830. }
  831. break;
  832. }
  833. }
  834. #endif
  835. #endif
  836. // Server authentication
  837. if (Credentials != null)
  838. {
  839. switch (Credentials.Type)
  840. {
  841. case AuthenticationTypes.Basic:
  842. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  843. SetHeader("Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Credentials.UserName + ":" + Credentials.Password))));
  844. break;
  845. case AuthenticationTypes.Unknown:
  846. case AuthenticationTypes.Digest:
  847. var digest = DigestStore.Get(this.CurrentUri);
  848. if (digest != null)
  849. {
  850. string authentication = digest.GenerateResponseHeader(this, Credentials);
  851. if (!string.IsNullOrEmpty(authentication))
  852. SetHeader("Authorization", authentication);
  853. }
  854. break;
  855. }
  856. }
  857. // Cookies.
  858. #if !BESTHTTP_DISABLE_COOKIES
  859. // User added cookies are sent even when IsCookiesEnabled is set to false
  860. List<Cookie> cookies = IsCookiesEnabled ? CookieJar.Get(CurrentUri) : null;
  861. // Merge server sent cookies with user-set cookies
  862. if (cookies == null || cookies.Count == 0)
  863. cookies = this.customCookies;
  864. else if (this.customCookies != null)
  865. {
  866. // Merge
  867. int idx = 0;
  868. while (idx < this.customCookies.Count)
  869. {
  870. Cookie customCookie = customCookies[idx];
  871. int foundIdx = cookies.FindIndex(c => c.Name.Equals(customCookie.Name));
  872. if (foundIdx >= 0)
  873. cookies[foundIdx] = customCookie;
  874. else
  875. cookies.Add(customCookie);
  876. idx++;
  877. }
  878. }
  879. // http://tools.ietf.org/html/rfc6265#section-5.4
  880. // -When the user agent generates an HTTP request, the user agent MUST NOT attach more than one Cookie header field.
  881. if (cookies != null && cookies.Count > 0)
  882. {
  883. // Room for improvement:
  884. // 2. The user agent SHOULD sort the cookie-list in the following order:
  885. // * Cookies with longer paths are listed before cookies with shorter paths.
  886. // * Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times.
  887. bool first = true;
  888. string cookieStr = string.Empty;
  889. bool isSecureProtocolInUse = HTTPProtocolFactory.IsSecureProtocol(CurrentUri);
  890. foreach (var cookie in cookies)
  891. if (!cookie.IsSecure || (cookie.IsSecure && isSecureProtocolInUse))
  892. {
  893. if (!first)
  894. cookieStr += "; ";
  895. else
  896. first = false;
  897. cookieStr += cookie.ToString();
  898. // 3. Update the last-access-time of each cookie in the cookie-list to the current date and time.
  899. cookie.LastAccess = DateTime.UtcNow;
  900. }
  901. if (!string.IsNullOrEmpty(cookieStr))
  902. SetHeader("Cookie", cookieStr);
  903. }
  904. #endif
  905. if (callBeforeSendCallback && _onBeforeHeaderSend != null)
  906. {
  907. try
  908. {
  909. _onBeforeHeaderSend(this);
  910. }
  911. catch(Exception ex)
  912. {
  913. HTTPManager.Logger.Exception("HTTPRequest", "OnBeforeHeaderSend", ex, this.Context);
  914. }
  915. }
  916. // Write out the headers to the stream
  917. if (callback != null && Headers != null)
  918. foreach (var kvp in Headers)
  919. callback(kvp.Key, kvp.Value);
  920. }
  921. /// <summary>
  922. /// Writes out the Headers to the stream.
  923. /// </summary>
  924. private void SendHeaders(Stream stream)
  925. {
  926. EnumerateHeaders((header, values) =>
  927. {
  928. if (string.IsNullOrEmpty(header) || values == null)
  929. return;
  930. byte[] headerName = string.Concat(header, ": ").GetASCIIBytes();
  931. for (int i = 0; i < values.Count; ++i)
  932. {
  933. if (string.IsNullOrEmpty(values[i]))
  934. {
  935. HTTPManager.Logger.Warning("HTTPRequest", string.Format("Null/empty value for header: {0}", header), this.Context);
  936. continue;
  937. }
  938. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  939. VerboseLogging("Header - '" + header + "': '" + values[i] + "'");
  940. byte[] valueBytes = values[i].GetASCIIBytes();
  941. stream.WriteArray(headerName);
  942. stream.WriteArray(valueBytes);
  943. stream.WriteArray(EOL);
  944. BufferPool.Release(valueBytes);
  945. }
  946. BufferPool.Release(headerName);
  947. }, /*callBeforeSendCallback:*/ true);
  948. }
  949. /// <summary>
  950. /// Returns a string representation of the headers.
  951. /// </summary>
  952. public string DumpHeaders()
  953. {
  954. using (var ms = new BufferPoolMemoryStream(5 * 1024))
  955. {
  956. SendHeaders(ms);
  957. return ms.ToArray().AsciiToString();
  958. }
  959. }
  960. /// <summary>
  961. /// Returns with the bytes that will be sent to the server as the request's payload.
  962. /// </summary>
  963. /// <remarks>Call this only after all form-fields are added!</remarks>
  964. public byte[] GetEntityBody()
  965. {
  966. if (RawData != null)
  967. return RawData;
  968. if (FormImpl != null || (FieldCollector != null && !FieldCollector.IsEmpty))
  969. {
  970. SelectFormImplementation();
  971. if (FormImpl != null)
  972. return FormImpl.GetData();
  973. }
  974. return null;
  975. }
  976. internal struct UploadStreamInfo
  977. {
  978. public readonly Stream Stream;
  979. public readonly long Length;
  980. public UploadStreamInfo(Stream stream, long length)
  981. {
  982. this.Stream = stream;
  983. this.Length = length;
  984. }
  985. }
  986. internal UploadStreamInfo GetUpStream()
  987. {
  988. byte[] data = RawData;
  989. // We are sending forms? Then convert the form to a byte array
  990. if (data == null && FormImpl != null)
  991. data = FormImpl.GetData();
  992. if (data != null || UploadStream != null)
  993. {
  994. // Make a new reference, as we will check the UploadStream property in the HTTPManager
  995. Stream uploadStream = UploadStream;
  996. long UploadLength = 0;
  997. if (uploadStream == null)
  998. {
  999. // Make stream from the data. A BufferPoolMemoryStream could be used here,
  1000. // but because data comes from outside, we don't have control on its lifetime
  1001. // and might be gets reused without our knowledge.
  1002. uploadStream = new MemoryStream(data, 0, data.Length);
  1003. // Initialize progress report variable
  1004. UploadLength = data.Length;
  1005. }
  1006. else
  1007. UploadLength = UseUploadStreamLength ? UploadStreamLength : -1;
  1008. return new UploadStreamInfo(uploadStream, UploadLength);
  1009. }
  1010. return new UploadStreamInfo(null, 0);
  1011. }
  1012. #endregion
  1013. #region Internal Helper Functions
  1014. internal void SendOutTo(Stream stream)
  1015. {
  1016. // Under WEBGL EnumerateHeaders and GetEntityBody are used instead of this function.
  1017. #if !UNITY_WEBGL || UNITY_EDITOR
  1018. string requestPathAndQuery =
  1019. #if !BESTHTTP_DISABLE_PROXY
  1020. HasProxy ? this.Proxy.GetRequestPath(CurrentUri) :
  1021. #endif
  1022. CurrentUri.GetRequestPathAndQueryURL();
  1023. string requestLine = string.Format("{0} {1} HTTP/1.1", MethodNames[(byte)MethodType], requestPathAndQuery);
  1024. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  1025. HTTPManager.Logger.Information("HTTPRequest", string.Format("Sending request: '{0}'", requestLine), this.Context);
  1026. // Create a buffer stream that will not close 'stream' when disposed or closed.
  1027. // buffersize should be larger than UploadChunkSize as it might be used for uploading user data and
  1028. // it should have enough room for UploadChunkSize data and additional chunk information.
  1029. using (WriteOnlyBufferedStream bufferStream = new WriteOnlyBufferedStream(stream, (int)(UploadChunkSize * 1.5f)))
  1030. {
  1031. byte[] requestLineBytes = requestLine.GetASCIIBytes();
  1032. bufferStream.WriteArray(requestLineBytes);
  1033. bufferStream.WriteArray(EOL);
  1034. BufferPool.Release(requestLineBytes);
  1035. // Write headers to the buffer
  1036. SendHeaders(bufferStream);
  1037. bufferStream.WriteArray(EOL);
  1038. // Send remaining data to the wire
  1039. bufferStream.Flush();
  1040. byte[] data = RawData;
  1041. // We are sending forms? Then convert the form to a byte array
  1042. if (data == null && FormImpl != null)
  1043. data = FormImpl.GetData();
  1044. if (data != null || UploadStream != null)
  1045. {
  1046. // Make a new reference, as we will check the UploadStream property in the HTTPManager
  1047. Stream uploadStream = UploadStream;
  1048. long UploadLength = 0;
  1049. if (uploadStream == null)
  1050. {
  1051. // Make stream from the data. A BufferPoolMemoryStream could be used here,
  1052. // but because data comes from outside, we don't have control on it's lifetime
  1053. // and might be gets reused without our knowledge.
  1054. uploadStream = new MemoryStream(data, 0, data.Length);
  1055. // Initialize progress report variable
  1056. UploadLength = data.Length;
  1057. }
  1058. else
  1059. UploadLength = UseUploadStreamLength ? UploadStreamLength : -1;
  1060. // Initialize the progress report variables
  1061. long Uploaded = 0;
  1062. // Upload buffer. First we will read the data into this buffer from the UploadStream, then write this buffer to our outStream
  1063. byte[] buffer = BufferPool.Get(UploadChunkSize, true);
  1064. // How many bytes was read from the UploadStream
  1065. int count = 0;
  1066. while ((count = uploadStream.Read(buffer, 0, buffer.Length)) > 0)
  1067. {
  1068. // If we don't know the size, send as chunked
  1069. if (!UseUploadStreamLength)
  1070. {
  1071. byte[] countBytes = count.ToString("X").GetASCIIBytes();
  1072. bufferStream.WriteArray(countBytes);
  1073. bufferStream.WriteArray(EOL);
  1074. BufferPool.Release(countBytes);
  1075. }
  1076. // write out the buffer to the wire
  1077. bufferStream.Write(buffer, 0, count);
  1078. // chunk trailing EOL
  1079. if (!UseUploadStreamLength)
  1080. bufferStream.WriteArray(EOL);
  1081. // update how many bytes are uploaded
  1082. Uploaded += count;
  1083. // Write to the wire
  1084. bufferStream.Flush();
  1085. if (this.OnUploadProgress != null)
  1086. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, RequestEvents.UploadProgress, Uploaded, UploadLength));
  1087. if (this.IsCancellationRequested)
  1088. return;
  1089. }
  1090. BufferPool.Release(buffer);
  1091. // All data from the stream are sent, write the 'end' chunk if necessary
  1092. if (!UseUploadStreamLength)
  1093. {
  1094. byte[] noMoreChunkBytes = BufferPool.Get(1, true);
  1095. noMoreChunkBytes[0] = (byte)'0';
  1096. bufferStream.Write(noMoreChunkBytes, 0, 1);
  1097. bufferStream.WriteArray(EOL);
  1098. bufferStream.WriteArray(EOL);
  1099. BufferPool.Release(noMoreChunkBytes);
  1100. }
  1101. // Make sure all remaining data will be on the wire
  1102. bufferStream.Flush();
  1103. // Dispose the MemoryStream
  1104. if (UploadStream == null && uploadStream != null)
  1105. uploadStream.Dispose();
  1106. }
  1107. else
  1108. bufferStream.Flush();
  1109. } // bufferStream.Dispose
  1110. HTTPManager.Logger.Information("HTTPRequest", "'" + requestLine + "' sent out", this.Context);
  1111. #endif
  1112. }
  1113. #if !UNITY_WEBGL || UNITY_EDITOR
  1114. internal void UpgradeCallback()
  1115. {
  1116. if (Response == null || !Response.IsUpgraded)
  1117. return;
  1118. try
  1119. {
  1120. if (OnUpgraded != null)
  1121. OnUpgraded(this, Response);
  1122. }
  1123. catch (Exception ex)
  1124. {
  1125. HTTPManager.Logger.Exception("HTTPRequest", "UpgradeCallback", ex, this.Context);
  1126. }
  1127. }
  1128. #endif
  1129. internal bool CallOnBeforeRedirection(Uri redirectUri)
  1130. {
  1131. if (onBeforeRedirection != null)
  1132. return onBeforeRedirection(this, this.Response, redirectUri);
  1133. return true;
  1134. }
  1135. /// <summary>
  1136. /// Called on Unity's main thread just before processing it.
  1137. /// </summary>
  1138. internal void Prepare()
  1139. {
  1140. }
  1141. #endregion
  1142. /// <summary>
  1143. /// Starts processing the request.
  1144. /// </summary>
  1145. public HTTPRequest Send()
  1146. {
  1147. this.IsCancellationRequested = false;
  1148. return HTTPManager.SendRequest(this);
  1149. }
  1150. /// <summary>
  1151. /// Aborts an already established connection, so no further download or upload are done.
  1152. /// </summary>
  1153. public void Abort()
  1154. {
  1155. VerboseLogging("Abort request!");
  1156. lock (this)
  1157. {
  1158. if (this.State >= HTTPRequestStates.Finished)
  1159. return;
  1160. this.IsCancellationRequested = true;
  1161. // If the response is an IProtocol implementation, call the protocol's cancellation.
  1162. IProtocol protocol = this.Response as IProtocol;
  1163. if (protocol != null)
  1164. protocol.CancellationRequested();
  1165. // There's a race-condition here, another thread might set it too.
  1166. this.Response = null;
  1167. // There's a race-condition here too, another thread might set it too.
  1168. // In this case, both state going to be queued up that we have to handle in RequestEvents.cs.
  1169. if (this.IsTimedOut)
  1170. {
  1171. this.State = this.IsConnectTimedOut ? HTTPRequestStates.ConnectionTimedOut : HTTPRequestStates.TimedOut;
  1172. }
  1173. else
  1174. this.State = HTTPRequestStates.Aborted;
  1175. #if !UNITY_WEBGL || UNITY_EDITOR
  1176. if (this.OnCancellationRequested != null)
  1177. {
  1178. try
  1179. {
  1180. this.OnCancellationRequested(this);
  1181. }
  1182. catch { }
  1183. }
  1184. #endif
  1185. }
  1186. }
  1187. /// <summary>
  1188. /// Resets the request for a state where switching MethodType is possible.
  1189. /// </summary>
  1190. public void Clear()
  1191. {
  1192. ClearForm();
  1193. RemoveHeaders();
  1194. this.IsRedirected = false;
  1195. this.RedirectCount = 0;
  1196. }
  1197. private void VerboseLogging(string str)
  1198. {
  1199. HTTPManager.Logger.Verbose("HTTPRequest", str, this.Context);
  1200. }
  1201. #region System.Collections.IEnumerator implementation
  1202. public object Current { get { return null; } }
  1203. public bool MoveNext()
  1204. {
  1205. return this.State < HTTPRequestStates.Finished;
  1206. }
  1207. public void Reset()
  1208. {
  1209. throw new NotImplementedException();
  1210. }
  1211. #endregion
  1212. HTTPRequest IEnumerator<HTTPRequest>.Current
  1213. {
  1214. get { return this; }
  1215. }
  1216. public void Dispose()
  1217. {
  1218. if (UploadStream != null && DisposeUploadStream)
  1219. {
  1220. UploadStream.Dispose();
  1221. UploadStream = null;
  1222. }
  1223. if (Response != null)
  1224. Response.Dispose();
  1225. }
  1226. }
  1227. }