HTTPRequest.cs 54 KB

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