WebUtil.cs 61 KB

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