1
0

WebUtil.cs 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Collections.Specialized;
  31. using System.Globalization;
  32. using System.IO;
  33. using System.IO.Compression;
  34. using System.Net;
  35. using System.Net.Security;
  36. using System.Security.Cryptography.X509Certificates;
  37. using System.Reflection;
  38. using System.Text;
  39. using System.Web;
  40. using System.Xml;
  41. using System.Xml.Serialization;
  42. using System.Xml.Linq;
  43. using log4net;
  44. using Nwc.XmlRpc;
  45. using OpenMetaverse.StructuredData;
  46. using OpenSim.Framework;
  47. using OpenSim.Framework.ServiceAuth;
  48. namespace OpenSim.Framework
  49. {
  50. /// <summary>
  51. /// Miscellaneous static methods and extension methods related to the web
  52. /// </summary>
  53. ///
  54. public static class WebUtil
  55. {
  56. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  57. public static ExpiringKey<string> GlobalExpiringBadURLs = new ExpiringKey<string>(30000);
  58. /// <summary>
  59. /// Control the printing of certain debug messages.
  60. /// </summary>
  61. /// <remarks>
  62. /// If DebugLevel >= 3 then short notices about outgoing HTTP requests are logged.
  63. /// </remarks>
  64. public static int DebugLevel { get; set; }
  65. /// <summary>
  66. /// Request number for diagnostic purposes.
  67. /// </summary>
  68. public static int RequestNumber { get; set; }
  69. /// <summary>
  70. /// this is the header field used to communicate the local request id
  71. /// used for performance and debugging
  72. /// </summary>
  73. public const string OSHeaderRequestID = "opensim-request-id";
  74. /// <summary>
  75. /// Number of milliseconds a call can take before it is considered
  76. /// a "long" call for warning & debugging purposes
  77. /// </summary>
  78. public const int LongCallTime = 3000;
  79. /// <summary>
  80. /// The maximum length of any data logged because of a long request time.
  81. /// </summary>
  82. /// <remarks>
  83. /// This is to truncate any really large post data, such as an asset. In theory, the first section should
  84. /// give us useful information about the call (which agent it relates to if applicable, etc.).
  85. /// This is also used to truncate messages when using DebugLevel 5.
  86. /// </remarks>
  87. public const int MaxRequestDiagLength = 200;
  88. public static bool ValidateServerCertificateNoChecks(
  89. object sender,
  90. X509Certificate certificate,
  91. X509Chain chain,
  92. SslPolicyErrors sslPolicyErrors)
  93. {
  94. sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
  95. sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch;
  96. if (sslPolicyErrors == SslPolicyErrors.None)
  97. return true;
  98. return false;
  99. }
  100. #region JSONRequest
  101. /// <summary>
  102. /// PUT JSON-encoded data to a web service that returns LLSD or
  103. /// JSON data
  104. /// </summary>
  105. public static OSDMap PutToServiceCompressed(string url, OSDMap data, int timeout)
  106. {
  107. return ServiceOSDRequest(url,data, "PUT", timeout, true, false);
  108. }
  109. public static OSDMap PutToService(string url, OSDMap data, int timeout)
  110. {
  111. return ServiceOSDRequest(url,data, "PUT", timeout, false, false);
  112. }
  113. public static OSDMap PostToService(string url, OSDMap data, int timeout, bool rpc)
  114. {
  115. return ServiceOSDRequest(url, data, "POST", timeout, false, rpc);
  116. }
  117. public static OSDMap PostToServiceCompressed(string url, OSDMap data, int timeout)
  118. {
  119. return ServiceOSDRequest(url, data, "POST", timeout, true, false);
  120. }
  121. public static OSDMap GetFromService(string url, int timeout)
  122. {
  123. return ServiceOSDRequest(url, null, "GET", timeout, false, false);
  124. }
  125. public static void LogOutgoingDetail(Stream outputStream)
  126. {
  127. LogOutgoingDetail("", outputStream);
  128. }
  129. public static void LogOutgoingDetail(string context, Stream outputStream)
  130. {
  131. using (Stream stream = Util.Copy(outputStream))
  132. using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
  133. {
  134. string output;
  135. if (DebugLevel == 5)
  136. {
  137. char[] chars = new char[WebUtil.MaxRequestDiagLength + 1]; // +1 so we know to add "..." only if needed
  138. int len = reader.Read(chars, 0, WebUtil.MaxRequestDiagLength + 1);
  139. output = new string(chars, 0, len);
  140. }
  141. else
  142. {
  143. output = reader.ReadToEnd();
  144. }
  145. LogOutgoingDetail(context, output);
  146. }
  147. }
  148. public static void LogOutgoingDetail(string type, int reqnum, string output)
  149. {
  150. LogOutgoingDetail(string.Format("{0} {1}: ", type, reqnum), output);
  151. }
  152. public static void LogOutgoingDetail(string context, string output)
  153. {
  154. if (DebugLevel == 5)
  155. {
  156. if (output.Length > MaxRequestDiagLength)
  157. output = output.Substring(0, MaxRequestDiagLength) + "...";
  158. }
  159. m_log.DebugFormat("[LOGHTTP]: {0}{1}", context, Util.BinaryToASCII(output));
  160. }
  161. public static void LogResponseDetail(int reqnum, Stream inputStream)
  162. {
  163. LogOutgoingDetail(string.Format("RESPONSE {0}: ", reqnum), inputStream);
  164. }
  165. public static void LogResponseDetail(int reqnum, string input)
  166. {
  167. LogOutgoingDetail(string.Format("RESPONSE {0}: ", reqnum), input);
  168. }
  169. public static OSDMap ServiceOSDRequest(string url, OSDMap data, string method, int timeout, bool compressed, bool rpc, bool keepalive = false)
  170. {
  171. int reqnum = RequestNumber++;
  172. if (DebugLevel >= 3)
  173. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} JSON-RPC {1} to {2}",
  174. reqnum, method, url);
  175. string errorMessage = "unknown error";
  176. int tickstart = Util.EnvironmentTickCount();
  177. int sendlen = 0;
  178. int rcvlen = 0;
  179. HttpWebRequest request = null;
  180. try
  181. {
  182. request = (HttpWebRequest)WebRequest.Create(url);
  183. request.Method = method;
  184. request.Timeout = timeout;
  185. request.KeepAlive = keepalive;
  186. request.MaximumAutomaticRedirections = 10;
  187. request.ReadWriteTimeout = timeout / 2;
  188. request.Headers[OSHeaderRequestID] = reqnum.ToString();
  189. }
  190. catch (Exception ex)
  191. {
  192. errorMessage = ex.Message;
  193. m_log.Debug("[WEB UTIL]: ServiceOSD error creating request " + ex.Message);
  194. return ErrorResponseMap(errorMessage);
  195. }
  196. try
  197. {
  198. // If there is some input, write it into the request
  199. if (data != null)
  200. {
  201. byte[] buffer;
  202. if (DebugLevel >= 5)
  203. {
  204. string strBuffer = OSDParser.SerializeJsonString(data);
  205. LogOutgoingDetail(method, reqnum, strBuffer);
  206. buffer = Util.UTF8Getbytes(strBuffer);
  207. }
  208. else
  209. buffer = OSDParser.SerializeJsonToBytes(data);
  210. request.ContentType = rpc ? "application/json-rpc" : "application/json";
  211. if (compressed)
  212. {
  213. request.Headers["X-Content-Encoding"] = "gzip"; // can't set "Content-Encoding" because old OpenSims fail if they get an unrecognized Content-Encoding
  214. using (MemoryStream ms = new MemoryStream())
  215. {
  216. using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress, true))
  217. {
  218. comp.Write(buffer, 0, buffer.Length);
  219. }
  220. buffer = ms.ToArray();
  221. }
  222. }
  223. sendlen = buffer.Length;
  224. request.ContentLength = buffer.Length; //Count bytes to send
  225. using (Stream requestStream = request.GetRequestStream())
  226. requestStream.Write(buffer, 0, buffer.Length); //Send it
  227. buffer = null;
  228. }
  229. using (WebResponse response = request.GetResponse())
  230. {
  231. using (StreamReader reader = new StreamReader(response.GetResponseStream()))
  232. {
  233. string responseStr = reader.ReadToEnd();
  234. if (WebUtil.DebugLevel >= 5)
  235. WebUtil.LogResponseDetail(reqnum, responseStr);
  236. rcvlen = responseStr.Length;
  237. return CanonicalizeResults(responseStr);
  238. }
  239. }
  240. }
  241. catch (WebException we)
  242. {
  243. errorMessage = we.Message;
  244. if (we.Status == WebExceptionStatus.ProtocolError)
  245. {
  246. using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
  247. errorMessage = String.Format("[{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription);
  248. }
  249. }
  250. catch (Exception ex)
  251. {
  252. errorMessage = ex.Message;
  253. m_log.Debug("[WEB UTIL]: Exception making request: " + ex.ToString());
  254. }
  255. finally
  256. {
  257. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  258. if (tickdiff > LongCallTime)
  259. {
  260. m_log.InfoFormat(
  261. "[WEB UTIL]: ServiceOSD request {0} {1} {2} took {3}ms, {4}/{5}bytes",
  262. reqnum, method, url, tickdiff, sendlen, rcvlen );
  263. }
  264. else if (DebugLevel >= 4)
  265. {
  266. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  267. reqnum, tickdiff);
  268. }
  269. }
  270. m_log.DebugFormat("[LOGHTTP]: JSON request {0} {1} to {2} FAILED: {3}", reqnum, method, url, errorMessage);
  271. return ErrorResponseMap(errorMessage);
  272. }
  273. /// <summary>
  274. /// Since there are no consistencies in the way web requests are
  275. /// formed, we need to do a little guessing about the result format.
  276. /// Keys:
  277. /// Success|success == the success fail of the request
  278. /// _RawResult == the raw string that came back
  279. /// _Result == the OSD unpacked string
  280. /// </summary>
  281. private static OSDMap CanonicalizeResults(string response)
  282. {
  283. OSDMap result = new OSDMap();
  284. // Default values
  285. result["Success"] = OSD.FromBoolean(true);
  286. result["success"] = OSD.FromBoolean(true);
  287. result["_RawResult"] = OSD.FromString(response);
  288. result["_Result"] = new OSDMap();
  289. if (response.Equals("true",System.StringComparison.OrdinalIgnoreCase))
  290. return result;
  291. if (response.Equals("false",System.StringComparison.OrdinalIgnoreCase))
  292. {
  293. result["Success"] = OSD.FromBoolean(false);
  294. result["success"] = OSD.FromBoolean(false);
  295. return result;
  296. }
  297. try
  298. {
  299. OSD responseOSD = OSDParser.Deserialize(response);
  300. if (responseOSD.Type == OSDType.Map)
  301. {
  302. result["_Result"] = (OSDMap)responseOSD;
  303. return result;
  304. }
  305. }
  306. catch
  307. {
  308. // don't need to treat this as an error... we're just guessing anyway
  309. // m_log.DebugFormat("[WEB UTIL] couldn't decode <{0}>: {1}",response,e.Message);
  310. }
  311. return result;
  312. }
  313. #endregion JSONRequest
  314. #region FormRequest
  315. /// <summary>
  316. /// POST URL-encoded form data to a web service that returns LLSD or
  317. /// JSON data
  318. /// </summary>
  319. public static OSDMap PostToService(string url, NameValueCollection data)
  320. {
  321. return ServiceFormRequest(url,data, 30000);
  322. }
  323. public static OSDMap ServiceFormRequest(string url, NameValueCollection data, int timeout)
  324. {
  325. int reqnum = RequestNumber++;
  326. string method = (data != null && data["RequestMethod"] != null) ? data["RequestMethod"] : "unknown";
  327. if (DebugLevel >= 3)
  328. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} ServiceForm '{1}' to {2}",
  329. reqnum, method, url);
  330. string errorMessage = "unknown error";
  331. int tickstart = Util.EnvironmentTickCount();
  332. int sendlen = 0;
  333. int rcvlen = 0;
  334. HttpWebRequest request = null;
  335. try
  336. {
  337. request = (HttpWebRequest)HttpWebRequest.Create(url);
  338. request.Method = "POST";
  339. request.Timeout = timeout;
  340. request.KeepAlive = false;
  341. request.MaximumAutomaticRedirections = 10;
  342. request.ReadWriteTimeout = timeout / 2;
  343. request.Headers[OSHeaderRequestID] = reqnum.ToString();
  344. }
  345. catch (Exception ex)
  346. {
  347. return ErrorResponseMap(ex.Message);
  348. }
  349. try
  350. {
  351. if (data != null)
  352. {
  353. string queryString = BuildQueryString(data);
  354. if (DebugLevel >= 5)
  355. LogOutgoingDetail("SEND", reqnum, queryString);
  356. byte[] buffer = System.Text.Encoding.UTF8.GetBytes(queryString);
  357. queryString = null;
  358. request.ContentLength = buffer.Length;
  359. sendlen = buffer.Length;
  360. request.ContentType = "application/x-www-form-urlencoded";
  361. using (Stream requestStream = request.GetRequestStream())
  362. requestStream.Write(buffer, 0, buffer.Length);
  363. buffer = null;
  364. }
  365. using (WebResponse response = request.GetResponse())
  366. {
  367. using (StreamReader reader = new StreamReader(response.GetResponseStream()))
  368. {
  369. string responseStr = reader.ReadToEnd();
  370. rcvlen = responseStr.Length;
  371. if (WebUtil.DebugLevel >= 5)
  372. WebUtil.LogResponseDetail(reqnum, responseStr);
  373. OSD responseOSD = OSDParser.Deserialize(responseStr);
  374. if (responseOSD.Type == OSDType.Map)
  375. return (OSDMap)responseOSD;
  376. }
  377. }
  378. }
  379. catch (WebException we)
  380. {
  381. errorMessage = we.Message;
  382. if (we.Status == WebExceptionStatus.ProtocolError)
  383. {
  384. using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
  385. errorMessage = String.Format("[{0}] {1}",webResponse.StatusCode,webResponse.StatusDescription);
  386. }
  387. }
  388. catch (Exception ex)
  389. {
  390. errorMessage = ex.Message;
  391. }
  392. finally
  393. {
  394. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  395. if (tickdiff > LongCallTime)
  396. {
  397. m_log.InfoFormat(
  398. "[LOGHTTP]: Slow ServiceForm request {0} '{1}' to {2} took {3}ms, {4}/{5}bytes",
  399. reqnum, method, url, tickdiff, sendlen, rcvlen);
  400. }
  401. else if (DebugLevel >= 4)
  402. {
  403. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  404. reqnum, tickdiff);
  405. }
  406. }
  407. m_log.WarnFormat("[LOGHTTP]: ServiceForm request {0} '{1}' to {2} failed: {3}", reqnum, method, url, errorMessage);
  408. return ErrorResponseMap(errorMessage);
  409. }
  410. /// <summary>
  411. /// Create a response map for an error, trying to keep
  412. /// the result formats consistent
  413. /// </summary>
  414. private static OSDMap ErrorResponseMap(string msg)
  415. {
  416. OSDMap result = new OSDMap();
  417. result["Success"] = "False";
  418. result["Message"] = OSD.FromString("Service request failed: " + msg);
  419. return result;
  420. }
  421. #endregion FormRequest
  422. #region Uri
  423. /// <summary>
  424. /// Combines a Uri that can contain both a base Uri and relative path
  425. /// with a second relative path fragment
  426. /// </summary>
  427. /// <param name="uri">Starting (base) Uri</param>
  428. /// <param name="fragment">Relative path fragment to append to the end
  429. /// of the Uri</param>
  430. /// <returns>The combined Uri</returns>
  431. /// <remarks>This is similar to the Uri constructor that takes a base
  432. /// Uri and the relative path, except this method can append a relative
  433. /// path fragment on to an existing relative path</remarks>
  434. public static Uri Combine(this Uri uri, string fragment)
  435. {
  436. string fragment1 = uri.Fragment;
  437. string fragment2 = fragment;
  438. if (!fragment1.EndsWith("/"))
  439. fragment1 = fragment1 + '/';
  440. if (fragment2.StartsWith("/"))
  441. fragment2 = fragment2.Substring(1);
  442. return new Uri(uri, fragment1 + fragment2);
  443. }
  444. /// <summary>
  445. /// Combines a Uri that can contain both a base Uri and relative path
  446. /// with a second relative path fragment. If the fragment is absolute,
  447. /// it will be returned without modification
  448. /// </summary>
  449. /// <param name="uri">Starting (base) Uri</param>
  450. /// <param name="fragment">Relative path fragment to append to the end
  451. /// of the Uri, or an absolute Uri to return unmodified</param>
  452. /// <returns>The combined Uri</returns>
  453. public static Uri Combine(this Uri uri, Uri fragment)
  454. {
  455. if (fragment.IsAbsoluteUri)
  456. return fragment;
  457. string fragment1 = uri.Fragment;
  458. string fragment2 = fragment.ToString();
  459. if (!fragment1.EndsWith("/"))
  460. fragment1 = fragment1 + '/';
  461. if (fragment2.StartsWith("/"))
  462. fragment2 = fragment2.Substring(1);
  463. return new Uri(uri, fragment1 + fragment2);
  464. }
  465. /// <summary>
  466. /// Appends a query string to a Uri that may or may not have existing
  467. /// query parameters
  468. /// </summary>
  469. /// <param name="uri">Uri to append the query to</param>
  470. /// <param name="query">Query string to append. Can either start with ?
  471. /// or just containg key/value pairs</param>
  472. /// <returns>String representation of the Uri with the query string
  473. /// appended</returns>
  474. public static string AppendQuery(this Uri uri, string query)
  475. {
  476. if (String.IsNullOrEmpty(query))
  477. return uri.ToString();
  478. if (query[0] == '?' || query[0] == '&')
  479. query = query.Substring(1);
  480. string uriStr = uri.ToString();
  481. if (uriStr.Contains("?"))
  482. return uriStr + '&' + query;
  483. else
  484. return uriStr + '?' + query;
  485. }
  486. #endregion Uri
  487. #region NameValueCollection
  488. /// <summary>
  489. /// Convert a NameValueCollection into a query string. This is the
  490. /// not exactly the inverse of HttpUtility.ParseQueryString()
  491. /// </summary>
  492. /// <param name="parameters">Collection of key/value pairs to convert</param>
  493. /// <returns>A query string with URL-escaped values</returns>
  494. public static string BuildQueryString(NameValueCollection parameters)
  495. {
  496. if (parameters.Count == 0)
  497. return string.Empty;
  498. StringBuilder sb = new StringBuilder(4096);
  499. foreach (string key in parameters.Keys)
  500. {
  501. string[] values = parameters.GetValues(key);
  502. if (values != null)
  503. {
  504. foreach (string value in values)
  505. {
  506. sb.Append(key);
  507. sb.Append("=");
  508. if(!string.IsNullOrWhiteSpace(value))
  509. sb.Append(HttpUtility.UrlEncode(value));
  510. sb.Append("&");
  511. }
  512. }
  513. }
  514. if(sb.Length > 1)
  515. sb.Length--;
  516. return sb.ToString();
  517. }
  518. /// <summary>
  519. ///
  520. /// </summary>
  521. /// <param name="collection"></param>
  522. /// <param name="key"></param>
  523. /// <returns></returns>
  524. public static string GetOne(this NameValueCollection collection, string key)
  525. {
  526. string[] values = collection.GetValues(key);
  527. if (values != null && values.Length > 0)
  528. return values[0];
  529. return null;
  530. }
  531. #endregion NameValueCollection
  532. #region Stream
  533. /// <summary>
  534. /// Copies the contents of one stream to another, starting at the
  535. /// current position of each stream
  536. /// </summary>
  537. /// <param name="copyFrom">The stream to copy from, at the position
  538. /// where copying should begin</param>
  539. /// <param name="copyTo">The stream to copy to, at the position where
  540. /// bytes should be written</param>
  541. /// <param name="maximumBytesToCopy">The maximum bytes to copy</param>
  542. /// <returns>The total number of bytes copied</returns>
  543. /// <remarks>
  544. /// Copying begins at the streams' current positions. The positions are
  545. /// NOT reset after copying is complete.
  546. /// NOTE!! .NET 4.0 adds the method 'Stream.CopyTo(stream, bufferSize)'.
  547. /// This function could be replaced with that method once we move
  548. /// totally to .NET 4.0. For versions before, this routine exists.
  549. /// This routine used to be named 'CopyTo' but the int parameter has
  550. /// a different meaning so this method was renamed to avoid any confusion.
  551. /// </remarks>
  552. public static int CopyStream(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy)
  553. {
  554. byte[] buffer = new byte[4096];
  555. int readBytes;
  556. int totalCopiedBytes = 0;
  557. while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(4096, maximumBytesToCopy))) > 0)
  558. {
  559. int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
  560. copyTo.Write(buffer, 0, writeBytes);
  561. totalCopiedBytes += writeBytes;
  562. maximumBytesToCopy -= writeBytes;
  563. }
  564. return totalCopiedBytes;
  565. }
  566. #endregion Stream
  567. public class QBasedComparer : IComparer
  568. {
  569. public int Compare(Object x, Object y)
  570. {
  571. float qx = GetQ(x);
  572. float qy = GetQ(y);
  573. return qy.CompareTo(qx); // descending order
  574. }
  575. private float GetQ(Object o)
  576. {
  577. // Example: image/png;q=0.9
  578. float qvalue = 1F;
  579. if (o is String)
  580. {
  581. string mime = (string)o;
  582. string[] parts = mime.Split(';');
  583. if (parts.Length > 1)
  584. {
  585. string[] kvp = parts[1].Split('=');
  586. if (kvp.Length == 2 && kvp[0] == "q")
  587. float.TryParse(kvp[1], NumberStyles.Number, CultureInfo.InvariantCulture, out qvalue);
  588. }
  589. }
  590. return qvalue;
  591. }
  592. }
  593. /// <summary>
  594. /// Takes the value of an Accept header and returns the preferred types
  595. /// ordered by q value (if it exists).
  596. /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
  597. /// Exmaple output: ["jp2", "png", "jpg"]
  598. /// NOTE: This doesn't handle the semantics of *'s...
  599. /// </summary>
  600. /// <param name="accept"></param>
  601. /// <returns></returns>
  602. public static string[] GetPreferredImageTypes(string accept)
  603. {
  604. if (string.IsNullOrEmpty(accept))
  605. return new string[0];
  606. string[] types = accept.Split(new char[] { ',' });
  607. if (types.Length > 0)
  608. {
  609. List<string> list = new List<string>(types);
  610. list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
  611. ArrayList tlist = new ArrayList(list);
  612. tlist.Sort(new QBasedComparer());
  613. string[] result = new string[tlist.Count];
  614. for (int i = 0; i < tlist.Count; i++)
  615. {
  616. string mime = (string)tlist[i];
  617. string[] parts = mime.Split(new char[] { ';' });
  618. string[] pair = parts[0].Split(new char[] { '/' });
  619. if (pair.Length == 2)
  620. result[i] = pair[1].ToLower();
  621. else // oops, we don't know what this is...
  622. result[i] = pair[0];
  623. }
  624. return result;
  625. }
  626. return new string[0];
  627. }
  628. }
  629. public static class AsynchronousRestObjectRequester
  630. {
  631. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  632. /// <summary>
  633. /// Perform an asynchronous REST request.
  634. /// </summary>
  635. /// <param name="verb">GET or POST</param>
  636. /// <param name="requestUrl"></param>
  637. /// <param name="obj"></param>
  638. /// <param name="action"></param>
  639. /// <returns></returns>
  640. ///
  641. /// <exception cref="System.Net.WebException">Thrown if we encounter a
  642. /// network issue while posting the request. You'll want to make
  643. /// sure you deal with this as they're not uncommon</exception>
  644. //
  645. public static void MakeRequest<TRequest, TResponse>(string verb,
  646. string requestUrl, TRequest obj, Action<TResponse> action)
  647. {
  648. MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, 0);
  649. }
  650. public static void MakeRequest<TRequest, TResponse>(string verb,
  651. string requestUrl, TRequest obj, Action<TResponse> action,
  652. int maxConnections)
  653. {
  654. MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, maxConnections, null);
  655. }
  656. /// <summary>
  657. /// Perform a synchronous REST request.
  658. /// </summary>
  659. /// <param name="verb"></param>
  660. /// <param name="requestUrl"></param>
  661. /// <param name="obj"></param>
  662. /// <param name="pTimeout">
  663. /// Request timeout in seconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  664. /// </param>
  665. /// <param name="maxConnections"></param>
  666. /// <returns>
  667. /// The response. If there was an internal exception or the request timed out,
  668. /// then the default(TResponse) is returned.
  669. /// </returns>
  670. public static void MakeRequest<TRequest, TResponse>(string verb,
  671. string requestUrl, TRequest obj, Action<TResponse> action,
  672. int maxConnections, IServiceAuth auth)
  673. {
  674. int reqnum = WebUtil.RequestNumber++;
  675. if (WebUtil.DebugLevel >= 3)
  676. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} AsynchronousRequestObject {1} to {2}",
  677. reqnum, verb, requestUrl);
  678. int tickstart = Util.EnvironmentTickCount();
  679. int tickdata = 0;
  680. int tickdiff = 0;
  681. Type type = typeof(TRequest);
  682. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
  683. if (auth != null)
  684. auth.AddAuthorization(request.Headers);
  685. if (maxConnections > 0 && request.ServicePoint.ConnectionLimit < maxConnections)
  686. request.ServicePoint.ConnectionLimit = maxConnections;
  687. TResponse deserial = default(TResponse);
  688. request.Method = verb;
  689. byte[] data = null;
  690. try
  691. {
  692. if (verb == "POST")
  693. {
  694. request.ContentType = "text/xml";
  695. XmlWriterSettings settings = new XmlWriterSettings();
  696. settings.Encoding = Encoding.UTF8;
  697. using (MemoryStream buffer = new MemoryStream())
  698. using (XmlWriter writer = XmlWriter.Create(buffer, settings))
  699. {
  700. XmlSerializer serializer = new XmlSerializer(type);
  701. serializer.Serialize(writer, obj);
  702. writer.Flush();
  703. data = buffer.ToArray();
  704. }
  705. int length = data.Length;
  706. request.ContentLength = length;
  707. if (WebUtil.DebugLevel >= 5)
  708. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  709. request.BeginGetRequestStream(delegate(IAsyncResult res)
  710. {
  711. using (Stream requestStream = request.EndGetRequestStream(res))
  712. requestStream.Write(data, 0, length);
  713. // capture how much time was spent writing
  714. tickdata = Util.EnvironmentTickCountSubtract(tickstart);
  715. request.BeginGetResponse(delegate(IAsyncResult ar)
  716. {
  717. using (WebResponse response = request.EndGetResponse(ar))
  718. {
  719. try
  720. {
  721. using (Stream respStream = response.GetResponseStream())
  722. {
  723. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  724. reqnum, respStream, response.ContentLength);
  725. }
  726. }
  727. catch (System.InvalidOperationException)
  728. {
  729. }
  730. }
  731. action(deserial);
  732. }, null);
  733. }, null);
  734. }
  735. else
  736. {
  737. request.BeginGetResponse(delegate(IAsyncResult res2)
  738. {
  739. try
  740. {
  741. // If the server returns a 404, this appears to trigger a System.Net.WebException even though that isn't
  742. // documented in MSDN
  743. using (WebResponse response = request.EndGetResponse(res2))
  744. {
  745. try
  746. {
  747. using (Stream respStream = response.GetResponseStream())
  748. {
  749. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  750. reqnum, respStream, response.ContentLength);
  751. }
  752. }
  753. catch (System.InvalidOperationException)
  754. {
  755. }
  756. }
  757. }
  758. catch (WebException e)
  759. {
  760. if (e.Status == WebExceptionStatus.ProtocolError)
  761. {
  762. if (e.Response is HttpWebResponse)
  763. {
  764. using (HttpWebResponse httpResponse = (HttpWebResponse)e.Response)
  765. {
  766. if (httpResponse.StatusCode != HttpStatusCode.NotFound)
  767. {
  768. // We don't appear to be handling any other status codes, so log these feailures to that
  769. // people don't spend unnecessary hours hunting phantom bugs.
  770. m_log.DebugFormat(
  771. "[ASYNC REQUEST]: Request {0} {1} failed with unexpected status code {2}",
  772. verb, requestUrl, httpResponse.StatusCode);
  773. }
  774. }
  775. }
  776. }
  777. else
  778. {
  779. m_log.ErrorFormat(
  780. "[ASYNC REQUEST]: Request {0} {1} failed with status {2} and message {3}",
  781. verb, requestUrl, e.Status, e.Message);
  782. }
  783. }
  784. catch (Exception e)
  785. {
  786. m_log.ErrorFormat(
  787. "[ASYNC REQUEST]: Request {0} {1} failed with exception {2}{3}",
  788. verb, requestUrl, e.Message, e.StackTrace);
  789. }
  790. // m_log.DebugFormat("[ASYNC REQUEST]: Received {0}", deserial.ToString());
  791. try
  792. {
  793. action(deserial);
  794. }
  795. catch (Exception e)
  796. {
  797. m_log.ErrorFormat(
  798. "[ASYNC REQUEST]: Request {0} {1} callback failed with exception {2}{3}",
  799. verb, requestUrl, e.Message, e.StackTrace);
  800. }
  801. }, null);
  802. }
  803. tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  804. if (tickdiff > WebUtil.LongCallTime)
  805. {
  806. string originalRequest = null;
  807. if (data != null)
  808. {
  809. originalRequest = Encoding.UTF8.GetString(data);
  810. if (originalRequest.Length > WebUtil.MaxRequestDiagLength)
  811. originalRequest = originalRequest.Remove(WebUtil.MaxRequestDiagLength);
  812. }
  813. m_log.InfoFormat(
  814. "[LOGHTTP]: Slow AsynchronousRequestObject request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
  815. reqnum, verb, requestUrl, tickdiff, tickdata,
  816. originalRequest);
  817. }
  818. else if (WebUtil.DebugLevel >= 4)
  819. {
  820. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
  821. reqnum, tickdiff, tickdata);
  822. }
  823. }
  824. catch { }
  825. }
  826. }
  827. public static class SynchronousRestFormsRequester
  828. {
  829. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  830. /// <summary>
  831. /// Perform a synchronous REST request.
  832. /// </summary>
  833. /// <param name="verb"></param>
  834. /// <param name="requestUrl"></param>
  835. /// <param name="obj"> </param>
  836. /// <param name="timeoutsecs"> </param>
  837. /// <returns></returns>
  838. ///
  839. /// <exception cref="System.Net.WebException">Thrown if we encounter a network issue while posting
  840. /// the request. You'll want to make sure you deal with this as they're not uncommon</exception>
  841. public static string MakeRequest(string verb, string requestUrl, string obj, int timeoutsecs = -1,
  842. IServiceAuth auth = null, bool keepalive = true)
  843. {
  844. int reqnum = WebUtil.RequestNumber++;
  845. if (WebUtil.DebugLevel >= 3)
  846. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestForms {1} to {2}",
  847. reqnum, verb, requestUrl);
  848. int tickstart = Util.EnvironmentTickCount();
  849. HttpWebRequest request = null;
  850. try
  851. {
  852. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  853. request.Method = verb;
  854. request.AllowWriteStreamBuffering = false;
  855. if (timeoutsecs > 0)
  856. request.Timeout = timeoutsecs * 1000;
  857. if(!keepalive)
  858. request.KeepAlive = false;
  859. if (auth != null)
  860. auth.AddAuthorization(request.Headers);
  861. request.ContentType = "application/x-www-form-urlencoded";
  862. }
  863. catch (Exception e)
  864. {
  865. m_log.InfoFormat("[FORMS]: Error creating {0} request to : {1}. Request: {2}", verb, requestUrl, e.Message);
  866. throw e;
  867. }
  868. int sendlen = 0;
  869. if (obj.Length > 0 && (verb == "POST") || (verb == "PUT"))
  870. {
  871. byte[] data = Util.UTF8NBGetbytes(obj);
  872. sendlen = data.Length;
  873. request.ContentLength = sendlen;
  874. if (WebUtil.DebugLevel >= 5)
  875. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  876. try
  877. {
  878. using(Stream requestStream = request.GetRequestStream())
  879. requestStream.Write(data, 0, sendlen);
  880. data = null;
  881. }
  882. catch (Exception e)
  883. {
  884. m_log.InfoFormat("[FORMS]: Error sending {0} request to: {1}. {2}", verb,requestUrl, e.Message);
  885. throw e;
  886. }
  887. }
  888. int rcvlen = 0;
  889. string respstring = String.Empty;
  890. try
  891. {
  892. using (WebResponse resp = request.GetResponse())
  893. {
  894. if (resp.ContentLength != 0)
  895. {
  896. using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
  897. respstring = reader.ReadToEnd();
  898. rcvlen = respstring.Length;
  899. }
  900. }
  901. }
  902. catch (Exception e)
  903. {
  904. m_log.InfoFormat("[FORMS]: Error receiving response from {0}: {1}.", requestUrl, e.Message);
  905. throw e;
  906. }
  907. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  908. if (tickdiff > WebUtil.LongCallTime)
  909. {
  910. m_log.InfoFormat("[FORMS]: request {0} {1} {2} took {3}ms, {4}/{5}bytes",
  911. reqnum, verb, requestUrl, tickdiff, sendlen, rcvlen);
  912. }
  913. else if (WebUtil.DebugLevel >= 4)
  914. {
  915. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  916. reqnum, tickdiff);
  917. if (WebUtil.DebugLevel >= 5)
  918. WebUtil.LogResponseDetail(reqnum, respstring);
  919. }
  920. return respstring;
  921. }
  922. public static string MakeRequest(string verb, string requestUrl, string obj, IServiceAuth auth)
  923. {
  924. return MakeRequest(verb, requestUrl, obj, -1, auth);
  925. }
  926. public static string MakePostRequest(string requestUrl, string obj,
  927. IServiceAuth auth = null, int timeoutsecs = -1, bool keepalive = true)
  928. {
  929. int reqnum = WebUtil.RequestNumber++;
  930. if (WebUtil.DebugLevel >= 3)
  931. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestForms POST to {1}",
  932. reqnum, requestUrl);
  933. int tickstart = Util.EnvironmentTickCount();
  934. HttpWebRequest request = null;
  935. try
  936. {
  937. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  938. request.Method = "POST";
  939. if (timeoutsecs > 0)
  940. request.Timeout = timeoutsecs * 1000;
  941. if (!keepalive)
  942. request.KeepAlive = false;
  943. if (auth != null)
  944. auth.AddAuthorization(request.Headers);
  945. request.ContentType = "application/x-www-form-urlencoded";
  946. }
  947. catch (Exception e)
  948. {
  949. m_log.InfoFormat("[FORMS]: Error creating POST request to {0}: {1}", requestUrl, e.Message);
  950. throw e;
  951. }
  952. byte[] data = Util.UTF8NBGetbytes(obj);
  953. int sendlen = data.Length;
  954. request.ContentLength = sendlen;
  955. if (WebUtil.DebugLevel >= 5)
  956. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  957. try
  958. {
  959. using (Stream requestStream = request.GetRequestStream())
  960. requestStream.Write(data, 0, sendlen);
  961. data = null;
  962. }
  963. catch (Exception e)
  964. {
  965. m_log.InfoFormat("[FORMS]: Error sending POST request to {0}: {1}", requestUrl, e.Message);
  966. throw e;
  967. }
  968. string respstring = String.Empty;
  969. int rcvlen = 0;
  970. try
  971. {
  972. using (WebResponse resp = request.GetResponse())
  973. {
  974. if (resp.ContentLength != 0)
  975. {
  976. using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
  977. respstring = reader.ReadToEnd();
  978. }
  979. }
  980. }
  981. catch (Exception e)
  982. {
  983. m_log.InfoFormat("[FORMS]: Error receiving response from {0}: {1}", requestUrl, e.Message);
  984. throw e;
  985. }
  986. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  987. if (tickdiff > WebUtil.LongCallTime)
  988. {
  989. m_log.InfoFormat("[FORMS]: request {0} POST {1} took {2}ms {3}/{4}bytes",
  990. reqnum, requestUrl, tickdiff, sendlen, rcvlen);
  991. }
  992. else if (WebUtil.DebugLevel >= 4)
  993. {
  994. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  995. reqnum, tickdiff);
  996. if (WebUtil.DebugLevel >= 5)
  997. WebUtil.LogResponseDetail(reqnum, respstring);
  998. }
  999. return respstring;
  1000. }
  1001. }
  1002. public class SynchronousRestObjectRequester
  1003. {
  1004. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  1005. /// <summary>
  1006. /// Perform a synchronous REST request.
  1007. /// </summary>
  1008. /// <param name="verb"></param>
  1009. /// <param name="requestUrl"></param>
  1010. /// <param name="obj"></param>
  1011. /// <returns>
  1012. /// The response. If there was an internal exception, then the default(TResponse) is returned.
  1013. /// </returns>
  1014. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj)
  1015. {
  1016. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0);
  1017. }
  1018. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, IServiceAuth auth)
  1019. {
  1020. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0, auth);
  1021. }
  1022. /// <summary>
  1023. /// Perform a synchronous REST request.
  1024. /// </summary>
  1025. /// <param name="verb"></param>
  1026. /// <param name="requestUrl"></param>
  1027. /// <param name="obj"></param>
  1028. /// <param name="pTimeout">
  1029. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1030. /// </param>
  1031. /// <returns>
  1032. /// The response. If there was an internal exception or the request timed out,
  1033. /// then the default(TResponse) is returned.
  1034. /// </returns>
  1035. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout)
  1036. {
  1037. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0);
  1038. }
  1039. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, IServiceAuth auth)
  1040. {
  1041. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0, auth);
  1042. }
  1043. /// Perform a synchronous REST request.
  1044. /// </summary>
  1045. /// <param name="verb"></param>
  1046. /// <param name="requestUrl"></param>
  1047. /// <param name="obj"></param>
  1048. /// <param name="pTimeout">
  1049. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1050. /// </param>
  1051. /// <param name="maxConnections"></param>
  1052. /// <returns>
  1053. /// The response. If there was an internal exception or the request timed out,
  1054. /// then the default(TResponse) is returned.
  1055. /// </returns>
  1056. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections)
  1057. {
  1058. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, maxConnections, null);
  1059. }
  1060. /// <summary>
  1061. /// Perform a synchronous REST request.
  1062. /// </summary>
  1063. /// <param name="verb"></param>
  1064. /// <param name="requestUrl"></param>
  1065. /// <param name="obj"></param>
  1066. /// <param name="pTimeout">
  1067. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1068. /// </param>
  1069. /// <param name="maxConnections"></param>
  1070. /// <returns>
  1071. /// The response. If there was an internal exception or the request timed out,
  1072. /// then the default(TResponse) is returned.
  1073. /// </returns>
  1074. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections, IServiceAuth auth)
  1075. {
  1076. int reqnum = WebUtil.RequestNumber++;
  1077. if (WebUtil.DebugLevel >= 3)
  1078. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestObject {1} to {2}",
  1079. reqnum, verb, requestUrl);
  1080. int tickstart = Util.EnvironmentTickCount();
  1081. TResponse deserial = default(TResponse);
  1082. HttpWebRequest request = null;
  1083. try
  1084. {
  1085. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  1086. if (auth != null)
  1087. auth.AddAuthorization(request.Headers);
  1088. if (pTimeout != 0)
  1089. request.Timeout = pTimeout;
  1090. if (maxConnections > 0 && request.ServicePoint.ConnectionLimit < maxConnections)
  1091. request.ServicePoint.ConnectionLimit = maxConnections;
  1092. request.AllowWriteStreamBuffering = false;
  1093. request.Method = verb;
  1094. }
  1095. catch (Exception e)
  1096. {
  1097. m_log.DebugFormat("[SynchronousRestObjectRequester]: Exception in creating request {0} {1}: {2}{3}",
  1098. verb, requestUrl, e.Message, e.StackTrace);
  1099. return deserial;
  1100. }
  1101. int sendlen = 0;
  1102. try
  1103. {
  1104. if ((verb == "POST") || (verb == "PUT"))
  1105. {
  1106. request.ContentType = "text/xml";
  1107. byte[] data;
  1108. XmlWriterSettings settings = new XmlWriterSettings(){Encoding = Util.UTF8};
  1109. using (MemoryStream ms = new MemoryStream())
  1110. using (XmlWriter writer = XmlWriter.Create(ms, settings))
  1111. {
  1112. XmlSerializer serializer = new XmlSerializer(typeof(TRequest));
  1113. serializer.Serialize(writer, obj);
  1114. writer.Flush();
  1115. data = ms.ToArray();
  1116. }
  1117. sendlen = data.Length;
  1118. request.ContentLength = sendlen;
  1119. if (WebUtil.DebugLevel >= 5)
  1120. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  1121. using (Stream requestStream = request.GetRequestStream())
  1122. requestStream.Write(data, 0, sendlen);
  1123. data = null;
  1124. }
  1125. }
  1126. catch (Exception e)
  1127. {
  1128. m_log.DebugFormat(
  1129. "[SynchronousRestObjectRequester]: Exception in making request {0} {1}: {2}{3}",
  1130. verb, requestUrl, e.Message, e.StackTrace);
  1131. return deserial;
  1132. }
  1133. int rcvlen = 0;
  1134. try
  1135. {
  1136. using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
  1137. {
  1138. if (resp.ContentLength != 0)
  1139. {
  1140. rcvlen = (int)resp.ContentLength;
  1141. using (Stream respStream = resp.GetResponseStream())
  1142. {
  1143. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  1144. reqnum, respStream, resp.ContentLength);
  1145. }
  1146. }
  1147. else
  1148. {
  1149. m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}",
  1150. verb, requestUrl);
  1151. }
  1152. }
  1153. }
  1154. catch (WebException e)
  1155. {
  1156. using (HttpWebResponse hwr = (HttpWebResponse)e.Response)
  1157. {
  1158. if (hwr != null)
  1159. {
  1160. if (hwr.StatusCode == HttpStatusCode.Unauthorized)
  1161. {
  1162. m_log.ErrorFormat("[SynchronousRestObjectRequester]: Web request {0} requires authentication",
  1163. requestUrl);
  1164. }
  1165. else if(hwr.StatusCode != HttpStatusCode.NotFound)
  1166. {
  1167. m_log.WarnFormat("[SynchronousRestObjectRequester]: Web request {0} returned error: {1}",
  1168. requestUrl, hwr.StatusCode);
  1169. }
  1170. }
  1171. else
  1172. m_log.ErrorFormat(
  1173. "[SynchronousRestObjectRequester]: WebException for {0} {1} {2} {3}",
  1174. verb, requestUrl, typeof(TResponse).ToString(), e.Message);
  1175. }
  1176. }
  1177. catch (System.InvalidOperationException)
  1178. {
  1179. // This is what happens when there is invalid XML
  1180. m_log.DebugFormat("[SynchronousRestObjectRequester]: Invalid XML from {0} {1} {2}",
  1181. verb, requestUrl, typeof(TResponse).ToString());
  1182. }
  1183. catch (Exception e)
  1184. {
  1185. m_log.DebugFormat("[SynchronousRestObjectRequester]: Exception on response from {0} {1}: {2}",
  1186. verb, requestUrl, e.Message);
  1187. }
  1188. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  1189. if (tickdiff > WebUtil.LongCallTime)
  1190. {
  1191. m_log.InfoFormat("[LOGHTTP]: Slow SynchronousRestObject request {0} {1} to {2} took {3}ms, {4}/{5}bytes",
  1192. reqnum, verb, requestUrl, tickdiff, sendlen, rcvlen);
  1193. }
  1194. else if (WebUtil.DebugLevel >= 4)
  1195. {
  1196. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  1197. reqnum, tickdiff);
  1198. }
  1199. return deserial;
  1200. }
  1201. }
  1202. public static class XMLResponseHelper
  1203. {
  1204. public static TResponse LogAndDeserialize<TRequest, TResponse>(int reqnum, Stream respStream, long contentLength)
  1205. {
  1206. XmlSerializer deserializer = new XmlSerializer(typeof(TResponse));
  1207. if (WebUtil.DebugLevel >= 5)
  1208. {
  1209. const int blockLength = 4096;
  1210. byte[] dataBuffer = new byte[blockLength];
  1211. int curcount;
  1212. using (MemoryStream ms = new MemoryStream(4 * blockLength))
  1213. {
  1214. if(contentLength == -1)
  1215. {
  1216. while (true)
  1217. {
  1218. curcount = respStream.Read(dataBuffer, 0, blockLength);
  1219. if (curcount <= 0)
  1220. break;
  1221. ms.Write(dataBuffer, 0, curcount);
  1222. }
  1223. }
  1224. else
  1225. {
  1226. int remaining = (int)contentLength;
  1227. while (remaining > 0)
  1228. {
  1229. curcount = respStream.Read(dataBuffer, 0, remaining);
  1230. if (curcount <= 0)
  1231. throw new EndOfStreamException(String.Format("End of stream reached with {0} bytes left to read", remaining));
  1232. ms.Write(dataBuffer, 0, curcount);
  1233. remaining -= curcount;
  1234. }
  1235. }
  1236. dataBuffer = ms.ToArray();
  1237. WebUtil.LogResponseDetail(reqnum, System.Text.Encoding.UTF8.GetString(dataBuffer));
  1238. ms.Position = 0;
  1239. return (TResponse)deserializer.Deserialize(ms);
  1240. }
  1241. }
  1242. else
  1243. {
  1244. return (TResponse)deserializer.Deserialize(respStream);
  1245. }
  1246. }
  1247. }
  1248. public static class XMLRPCRequester
  1249. {
  1250. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  1251. public static Hashtable SendRequest(Hashtable ReqParams, string method, string url)
  1252. {
  1253. int reqnum = WebUtil.RequestNumber++;
  1254. if (WebUtil.DebugLevel >= 3)
  1255. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} XML-RPC '{1}' to {2}",
  1256. reqnum, method, url);
  1257. int tickstart = Util.EnvironmentTickCount();
  1258. string responseStr = null;
  1259. try
  1260. {
  1261. ArrayList SendParams = new ArrayList();
  1262. SendParams.Add(ReqParams);
  1263. XmlRpcRequest Req = new XmlRpcRequest(method, SendParams);
  1264. if (WebUtil.DebugLevel >= 5)
  1265. {
  1266. string str = Req.ToString();
  1267. str = XElement.Parse(str).ToString(SaveOptions.DisableFormatting);
  1268. WebUtil.LogOutgoingDetail("SEND", reqnum, str);
  1269. }
  1270. XmlRpcResponse Resp = Req.Send(url, 30000);
  1271. try
  1272. {
  1273. if (WebUtil.DebugLevel >= 5)
  1274. {
  1275. responseStr = Resp.ToString();
  1276. responseStr = XElement.Parse(responseStr).ToString(SaveOptions.DisableFormatting);
  1277. WebUtil.LogResponseDetail(reqnum, responseStr);
  1278. }
  1279. }
  1280. catch (Exception e)
  1281. {
  1282. m_log.Error("Error parsing XML-RPC response", e);
  1283. }
  1284. if (Resp.IsFault)
  1285. {
  1286. m_log.DebugFormat(
  1287. "[LOGHTTP]: XML-RPC request {0} '{1}' to {2} FAILED: FaultCode={3}, FaultMessage={4}",
  1288. reqnum, method, url, Resp.FaultCode, Resp.FaultString);
  1289. return null;
  1290. }
  1291. Hashtable RespData = (Hashtable)Resp.Value;
  1292. return RespData;
  1293. }
  1294. finally
  1295. {
  1296. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  1297. if (tickdiff > WebUtil.LongCallTime)
  1298. {
  1299. m_log.InfoFormat(
  1300. "[LOGHTTP]: Slow XML-RPC request {0} '{1}' to {2} took {3}ms, {4}",
  1301. reqnum, method, url, tickdiff,
  1302. responseStr != null
  1303. ? (responseStr.Length > WebUtil.MaxRequestDiagLength ? responseStr.Remove(WebUtil.MaxRequestDiagLength) : responseStr)
  1304. : "");
  1305. }
  1306. else if (WebUtil.DebugLevel >= 4)
  1307. {
  1308. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms", reqnum, tickdiff);
  1309. }
  1310. }
  1311. }
  1312. }
  1313. }