WebUtil.cs 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498
  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. request.AllowWriteStreamBuffering = false;
  190. }
  191. catch (Exception ex)
  192. {
  193. errorMessage = ex.Message;
  194. m_log.Debug("[WEB UTIL]: SvcOSD error creating request " + ex.Message);
  195. return ErrorResponseMap(errorMessage);
  196. }
  197. try
  198. {
  199. // If there is some input, write it into the request
  200. if (data != null)
  201. {
  202. byte[] buffer;
  203. if (DebugLevel >= 5)
  204. {
  205. string strBuffer = OSDParser.SerializeJsonString(data);
  206. LogOutgoingDetail(method, reqnum, strBuffer);
  207. buffer = Util.UTF8Getbytes(strBuffer);
  208. }
  209. else
  210. buffer = OSDParser.SerializeJsonToBytes(data);
  211. request.ContentType = rpc ? "application/json-rpc" : "application/json";
  212. if (compressed)
  213. {
  214. request.Headers["X-Content-Encoding"] = "gzip"; // can't set "Content-Encoding" because old OpenSims fail if they get an unrecognized Content-Encoding
  215. using (MemoryStream ms = new MemoryStream())
  216. {
  217. using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress, true))
  218. {
  219. comp.Write(buffer, 0, buffer.Length);
  220. }
  221. buffer = ms.ToArray();
  222. }
  223. }
  224. sendlen = buffer.Length;
  225. request.ContentLength = buffer.Length; //Count bytes to send
  226. using (Stream requestStream = request.GetRequestStream())
  227. requestStream.Write(buffer, 0, buffer.Length); //Send it
  228. buffer = null;
  229. }
  230. using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
  231. {
  232. using (StreamReader reader = new StreamReader(response.GetResponseStream()))
  233. {
  234. string responseStr = reader.ReadToEnd();
  235. if (WebUtil.DebugLevel >= 5)
  236. WebUtil.LogResponseDetail(reqnum, responseStr);
  237. rcvlen = responseStr.Length;
  238. return CanonicalizeResults(responseStr);
  239. }
  240. }
  241. }
  242. catch (WebException we)
  243. {
  244. errorMessage = we.Message;
  245. if (we.Status == WebExceptionStatus.ProtocolError)
  246. {
  247. using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
  248. errorMessage = String.Format("[{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription);
  249. }
  250. }
  251. catch (Exception ex)
  252. {
  253. errorMessage = ex.Message;
  254. m_log.Debug("[WEB UTIL]: Exception making request: " + ex.ToString());
  255. }
  256. finally
  257. {
  258. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  259. if (tickdiff > LongCallTime)
  260. {
  261. m_log.InfoFormat(
  262. "[WEB UTIL]: SvcOSD {0} {1} {2} took {3}ms, {4}/{5}bytes",
  263. reqnum, method, url, tickdiff, sendlen, rcvlen );
  264. }
  265. else if (DebugLevel >= 4)
  266. {
  267. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  268. reqnum, tickdiff);
  269. }
  270. }
  271. m_log.DebugFormat("[LOGHTTP]: JSON 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", StringComparison.OrdinalIgnoreCase))
  291. return result;
  292. if (response.Equals("false", 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 sendlen = 0;
  334. int rcvlen = 0;
  335. HttpWebRequest request = null;
  336. try
  337. {
  338. request = (HttpWebRequest)WebRequest.Create(url);
  339. request.Method = "POST";
  340. request.Timeout = timeout;
  341. request.KeepAlive = false;
  342. request.MaximumAutomaticRedirections = 10;
  343. request.ReadWriteTimeout = timeout / 2;
  344. request.Headers[OSHeaderRequestID] = reqnum.ToString();
  345. request.AllowWriteStreamBuffering = false;
  346. }
  347. catch (Exception ex)
  348. {
  349. return ErrorResponseMap(ex.Message);
  350. }
  351. try
  352. {
  353. if (data != null)
  354. {
  355. string queryString = BuildQueryString(data);
  356. if (DebugLevel >= 5)
  357. LogOutgoingDetail("SEND", reqnum, queryString);
  358. byte[] buffer = System.Text.Encoding.UTF8.GetBytes(queryString);
  359. queryString = null;
  360. request.ContentLength = buffer.Length;
  361. sendlen = buffer.Length;
  362. request.ContentType = "application/x-www-form-urlencoded";
  363. using (Stream requestStream = request.GetRequestStream())
  364. requestStream.Write(buffer, 0, buffer.Length);
  365. buffer = null;
  366. }
  367. using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
  368. {
  369. using (StreamReader reader = new StreamReader(response.GetResponseStream()))
  370. {
  371. string responseStr = reader.ReadToEnd();
  372. rcvlen = responseStr.Length;
  373. if (DebugLevel >= 5)
  374. LogResponseDetail(reqnum, responseStr);
  375. OSD responseOSD = OSDParser.Deserialize(responseStr);
  376. if (responseOSD.Type == OSDType.Map)
  377. return (OSDMap)responseOSD;
  378. }
  379. }
  380. }
  381. catch (WebException we)
  382. {
  383. errorMessage = we.Message;
  384. if (we.Status == WebExceptionStatus.ProtocolError)
  385. {
  386. using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
  387. errorMessage = String.Format("[{0}] {1}",webResponse.StatusCode,webResponse.StatusDescription);
  388. }
  389. }
  390. catch (Exception ex)
  391. {
  392. errorMessage = ex.Message;
  393. }
  394. finally
  395. {
  396. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  397. if (tickdiff > LongCallTime)
  398. {
  399. m_log.InfoFormat(
  400. "[LOGHTTP]: Slow ServiceForm request {0} '{1}' to {2} took {3}ms, {4}/{5}bytes",
  401. reqnum, method, url, tickdiff, sendlen, rcvlen);
  402. }
  403. else if (DebugLevel >= 4)
  404. {
  405. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  406. reqnum, tickdiff);
  407. }
  408. }
  409. m_log.WarnFormat("[LOGHTTP]: ServiceForm request {0} '{1}' to {2} failed: {3}", reqnum, method, url, errorMessage);
  410. return ErrorResponseMap(errorMessage);
  411. }
  412. /// <summary>
  413. /// Create a response map for an error, trying to keep
  414. /// the result formats consistent
  415. /// </summary>
  416. private static OSDMap ErrorResponseMap(string msg)
  417. {
  418. OSDMap result = new OSDMap();
  419. result["Success"] = "False";
  420. result["Message"] = OSD.FromString("Service request failed: " + msg);
  421. return result;
  422. }
  423. #endregion FormRequest
  424. #region Uri
  425. /// <summary>
  426. /// Combines a Uri that can contain both a base Uri and relative path
  427. /// with a second relative path fragment
  428. /// </summary>
  429. /// <param name="uri">Starting (base) Uri</param>
  430. /// <param name="fragment">Relative path fragment to append to the end
  431. /// of the Uri</param>
  432. /// <returns>The combined Uri</returns>
  433. /// <remarks>This is similar to the Uri constructor that takes a base
  434. /// Uri and the relative path, except this method can append a relative
  435. /// path fragment on to an existing relative path</remarks>
  436. public static Uri Combine(this Uri uri, string fragment)
  437. {
  438. string fragment1 = uri.Fragment;
  439. string fragment2 = fragment;
  440. if (!fragment1.EndsWith("/"))
  441. fragment1 = fragment1 + '/';
  442. if (fragment2.StartsWith("/"))
  443. fragment2 = fragment2.Substring(1);
  444. return new Uri(uri, fragment1 + fragment2);
  445. }
  446. /// <summary>
  447. /// Combines a Uri that can contain both a base Uri and relative path
  448. /// with a second relative path fragment. If the fragment is absolute,
  449. /// it will be returned without modification
  450. /// </summary>
  451. /// <param name="uri">Starting (base) Uri</param>
  452. /// <param name="fragment">Relative path fragment to append to the end
  453. /// of the Uri, or an absolute Uri to return unmodified</param>
  454. /// <returns>The combined Uri</returns>
  455. public static Uri Combine(this Uri uri, Uri fragment)
  456. {
  457. if (fragment.IsAbsoluteUri)
  458. return fragment;
  459. string fragment1 = uri.Fragment;
  460. string fragment2 = fragment.ToString();
  461. if (!fragment1.EndsWith("/"))
  462. fragment1 = fragment1 + '/';
  463. if (fragment2.StartsWith("/"))
  464. fragment2 = fragment2.Substring(1);
  465. return new Uri(uri, fragment1 + fragment2);
  466. }
  467. /// <summary>
  468. /// Appends a query string to a Uri that may or may not have existing
  469. /// query parameters
  470. /// </summary>
  471. /// <param name="uri">Uri to append the query to</param>
  472. /// <param name="query">Query string to append. Can either start with ?
  473. /// or just containg key/value pairs</param>
  474. /// <returns>String representation of the Uri with the query string
  475. /// appended</returns>
  476. public static string AppendQuery(this Uri uri, string query)
  477. {
  478. if (String.IsNullOrEmpty(query))
  479. return uri.ToString();
  480. if (query[0] == '?' || query[0] == '&')
  481. query = query.Substring(1);
  482. string uriStr = uri.ToString();
  483. if (uriStr.Contains("?"))
  484. return uriStr + '&' + query;
  485. else
  486. return uriStr + '?' + query;
  487. }
  488. #endregion Uri
  489. #region NameValueCollection
  490. /// <summary>
  491. /// Convert a NameValueCollection into a query string. This is the
  492. /// not exactly the inverse of HttpUtility.ParseQueryString()
  493. /// </summary>
  494. /// <param name="parameters">Collection of key/value pairs to convert</param>
  495. /// <returns>A query string with URL-escaped values</returns>
  496. public static string BuildQueryString(NameValueCollection parameters)
  497. {
  498. if (parameters.Count == 0)
  499. return string.Empty;
  500. StringBuilder sb = new StringBuilder(4096);
  501. foreach (string key in parameters.Keys)
  502. {
  503. string[] values = parameters.GetValues(key);
  504. if (values != null)
  505. {
  506. foreach (string value in values)
  507. {
  508. sb.Append(key);
  509. sb.Append("=");
  510. if(!string.IsNullOrWhiteSpace(value))
  511. sb.Append(HttpUtility.UrlEncode(value));
  512. sb.Append("&");
  513. }
  514. }
  515. }
  516. if(sb.Length > 1)
  517. sb.Length--;
  518. return sb.ToString();
  519. }
  520. /// <summary>
  521. ///
  522. /// </summary>
  523. /// <param name="collection"></param>
  524. /// <param name="key"></param>
  525. /// <returns></returns>
  526. public static string GetOne(this NameValueCollection collection, string key)
  527. {
  528. string[] values = collection.GetValues(key);
  529. if (values != null && values.Length > 0)
  530. return values[0];
  531. return null;
  532. }
  533. #endregion NameValueCollection
  534. #region Stream
  535. /// <summary>
  536. /// Copies the contents of one stream to another, starting at the
  537. /// current position of each stream
  538. /// </summary>
  539. /// <param name="copyFrom">The stream to copy from, at the position
  540. /// where copying should begin</param>
  541. /// <param name="copyTo">The stream to copy to, at the position where
  542. /// bytes should be written</param>
  543. /// <param name="maximumBytesToCopy">The maximum bytes to copy</param>
  544. /// <returns>The total number of bytes copied</returns>
  545. /// <remarks>
  546. /// Copying begins at the streams' current positions. The positions are
  547. /// NOT reset after copying is complete.
  548. /// NOTE!! .NET 4.0 adds the method 'Stream.CopyTo(stream, bufferSize)'.
  549. /// This function could be replaced with that method once we move
  550. /// totally to .NET 4.0. For versions before, this routine exists.
  551. /// This routine used to be named 'CopyTo' but the int parameter has
  552. /// a different meaning so this method was renamed to avoid any confusion.
  553. /// </remarks>
  554. public static int CopyStream(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy)
  555. {
  556. byte[] buffer = new byte[4096];
  557. int readBytes;
  558. int totalCopiedBytes = 0;
  559. while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(4096, maximumBytesToCopy))) > 0)
  560. {
  561. int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
  562. copyTo.Write(buffer, 0, writeBytes);
  563. totalCopiedBytes += writeBytes;
  564. maximumBytesToCopy -= writeBytes;
  565. }
  566. return totalCopiedBytes;
  567. }
  568. #endregion Stream
  569. public class QBasedComparer : IComparer
  570. {
  571. public int Compare(Object x, Object y)
  572. {
  573. float qx = GetQ(x);
  574. float qy = GetQ(y);
  575. return qy.CompareTo(qx); // descending order
  576. }
  577. private float GetQ(Object o)
  578. {
  579. // Example: image/png;q=0.9
  580. float qvalue = 1F;
  581. if (o is String)
  582. {
  583. string mime = (string)o;
  584. string[] parts = mime.Split(';');
  585. if (parts.Length > 1)
  586. {
  587. string[] kvp = parts[1].Split('=');
  588. if (kvp.Length == 2 && kvp[0] == "q")
  589. float.TryParse(kvp[1], NumberStyles.Number, CultureInfo.InvariantCulture, out qvalue);
  590. }
  591. }
  592. return qvalue;
  593. }
  594. }
  595. /// <summary>
  596. /// Takes the value of an Accept header and returns the preferred types
  597. /// ordered by q value (if it exists).
  598. /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
  599. /// Exmaple output: ["jp2", "png", "jpg"]
  600. /// NOTE: This doesn't handle the semantics of *'s...
  601. /// </summary>
  602. /// <param name="accept"></param>
  603. /// <returns></returns>
  604. public static string[] GetPreferredImageTypes(string accept)
  605. {
  606. if (string.IsNullOrEmpty(accept))
  607. return new string[0];
  608. string[] types = accept.Split(new char[] { ',' });
  609. if (types.Length > 0)
  610. {
  611. List<string> list = new List<string>(types);
  612. list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
  613. ArrayList tlist = new ArrayList(list);
  614. tlist.Sort(new QBasedComparer());
  615. string[] result = new string[tlist.Count];
  616. for (int i = 0; i < tlist.Count; i++)
  617. {
  618. string mime = (string)tlist[i];
  619. string[] parts = mime.Split(new char[] { ';' });
  620. string[] pair = parts[0].Split(new char[] { '/' });
  621. if (pair.Length == 2)
  622. result[i] = pair[1].ToLower();
  623. else // oops, we don't know what this is...
  624. result[i] = pair[0];
  625. }
  626. return result;
  627. }
  628. return new string[0];
  629. }
  630. }
  631. public static class AsynchronousRestObjectRequester
  632. {
  633. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  634. /// <summary>
  635. /// Perform an asynchronous REST request.
  636. /// </summary>
  637. /// <param name="verb">GET or POST</param>
  638. /// <param name="requestUrl"></param>
  639. /// <param name="obj"></param>
  640. /// <param name="action"></param>
  641. /// <returns></returns>
  642. ///
  643. /// <exception cref="System.Net.WebException">Thrown if we encounter a
  644. /// network issue while posting the request. You'll want to make
  645. /// sure you deal with this as they're not uncommon</exception>
  646. //
  647. public static void MakeRequest<TRequest, TResponse>(string verb,
  648. string requestUrl, TRequest obj, Action<TResponse> action)
  649. {
  650. MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, 0);
  651. }
  652. public static void MakeRequest<TRequest, TResponse>(string verb,
  653. string requestUrl, TRequest obj, Action<TResponse> action,
  654. int maxConnections)
  655. {
  656. MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, maxConnections, null);
  657. }
  658. /// <summary>
  659. /// Perform a synchronous REST request.
  660. /// </summary>
  661. /// <param name="verb"></param>
  662. /// <param name="requestUrl"></param>
  663. /// <param name="obj"></param>
  664. /// <param name="pTimeout">
  665. /// Request timeout in seconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  666. /// </param>
  667. /// <param name="maxConnections"></param>
  668. /// <returns>
  669. /// The response. If there was an internal exception or the request timed out,
  670. /// then the default(TResponse) is returned.
  671. /// </returns>
  672. public static void MakeRequest<TRequest, TResponse>(string verb,
  673. string requestUrl, TRequest obj, Action<TResponse> action,
  674. int maxConnections, IServiceAuth auth)
  675. {
  676. int reqnum = WebUtil.RequestNumber++;
  677. if (WebUtil.DebugLevel >= 3)
  678. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} AsynchronousRequestObject {1} to {2}",
  679. reqnum, verb, requestUrl);
  680. int tickstart = Util.EnvironmentTickCount();
  681. int tickdata = 0;
  682. int tickdiff = 0;
  683. Type type = typeof(TRequest);
  684. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
  685. if (auth != null)
  686. auth.AddAuthorization(request.Headers);
  687. request.AllowWriteStreamBuffering = false;
  688. if (maxConnections > 0 && request.ServicePoint.ConnectionLimit < maxConnections)
  689. request.ServicePoint.ConnectionLimit = maxConnections;
  690. TResponse deserial = default(TResponse);
  691. request.Method = verb;
  692. byte[] data = null;
  693. try
  694. {
  695. if (verb == "POST")
  696. {
  697. request.ContentType = "text/xml";
  698. XmlWriterSettings settings = new XmlWriterSettings();
  699. settings.Encoding = Encoding.UTF8;
  700. using (MemoryStream buffer = new MemoryStream())
  701. using (XmlWriter writer = XmlWriter.Create(buffer, settings))
  702. {
  703. XmlSerializer serializer = new XmlSerializer(type);
  704. serializer.Serialize(writer, obj);
  705. writer.Flush();
  706. data = buffer.ToArray();
  707. }
  708. int length = data.Length;
  709. request.ContentLength = length;
  710. if (WebUtil.DebugLevel >= 5)
  711. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  712. request.BeginGetRequestStream(delegate(IAsyncResult res)
  713. {
  714. using (Stream requestStream = request.EndGetRequestStream(res))
  715. requestStream.Write(data, 0, length);
  716. // capture how much time was spent writing
  717. tickdata = Util.EnvironmentTickCountSubtract(tickstart);
  718. request.BeginGetResponse(delegate(IAsyncResult ar)
  719. {
  720. using (WebResponse response = request.EndGetResponse(ar))
  721. {
  722. try
  723. {
  724. using (Stream respStream = response.GetResponseStream())
  725. {
  726. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  727. reqnum, respStream, response.ContentLength);
  728. }
  729. }
  730. catch (System.InvalidOperationException)
  731. {
  732. }
  733. }
  734. action(deserial);
  735. }, null);
  736. }, null);
  737. }
  738. else
  739. {
  740. request.BeginGetResponse(delegate(IAsyncResult res2)
  741. {
  742. try
  743. {
  744. // If the server returns a 404, this appears to trigger a System.Net.WebException even though that isn't
  745. // documented in MSDN
  746. using (WebResponse response = request.EndGetResponse(res2))
  747. {
  748. try
  749. {
  750. using (Stream respStream = response.GetResponseStream())
  751. {
  752. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  753. reqnum, respStream, response.ContentLength);
  754. }
  755. }
  756. catch (System.InvalidOperationException)
  757. {
  758. }
  759. }
  760. }
  761. catch (WebException e)
  762. {
  763. if (e.Status == WebExceptionStatus.ProtocolError)
  764. {
  765. if (e.Response is HttpWebResponse)
  766. {
  767. using (HttpWebResponse httpResponse = (HttpWebResponse)e.Response)
  768. {
  769. if (httpResponse.StatusCode != HttpStatusCode.NotFound)
  770. {
  771. // We don't appear to be handling any other status codes, so log these feailures to that
  772. // people don't spend unnecessary hours hunting phantom bugs.
  773. m_log.DebugFormat(
  774. "[ASYNC REQUEST]: Request {0} {1} failed with unexpected status code {2}",
  775. verb, requestUrl, httpResponse.StatusCode);
  776. }
  777. }
  778. }
  779. }
  780. else
  781. {
  782. m_log.ErrorFormat(
  783. "[ASYNC REQUEST]: Request {0} {1} failed with status {2} and message {3}",
  784. verb, requestUrl, e.Status, e.Message);
  785. }
  786. }
  787. catch (Exception e)
  788. {
  789. m_log.ErrorFormat(
  790. "[ASYNC REQUEST]: Request {0} {1} failed with exception {2}{3}",
  791. verb, requestUrl, e.Message, e.StackTrace);
  792. }
  793. // m_log.DebugFormat("[ASYNC REQUEST]: Received {0}", deserial.ToString());
  794. try
  795. {
  796. action(deserial);
  797. }
  798. catch (Exception e)
  799. {
  800. m_log.ErrorFormat(
  801. "[ASYNC REQUEST]: Request {0} {1} callback failed with exception {2}{3}",
  802. verb, requestUrl, e.Message, e.StackTrace);
  803. }
  804. }, null);
  805. }
  806. tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  807. if (tickdiff > WebUtil.LongCallTime)
  808. {
  809. string originalRequest = null;
  810. if (data != null)
  811. {
  812. originalRequest = Encoding.UTF8.GetString(data);
  813. if (originalRequest.Length > WebUtil.MaxRequestDiagLength)
  814. originalRequest = originalRequest.Remove(WebUtil.MaxRequestDiagLength);
  815. }
  816. m_log.InfoFormat(
  817. "[LOGHTTP]: Slow AsynchronousRequestObject request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
  818. reqnum, verb, requestUrl, tickdiff, tickdata,
  819. originalRequest);
  820. }
  821. else if (WebUtil.DebugLevel >= 4)
  822. {
  823. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
  824. reqnum, tickdiff, tickdata);
  825. }
  826. }
  827. catch { }
  828. }
  829. }
  830. public static class SynchronousRestFormsRequester
  831. {
  832. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  833. /// <summary>
  834. /// Perform a synchronous REST request.
  835. /// </summary>
  836. /// <param name="verb"></param>
  837. /// <param name="requestUrl"></param>
  838. /// <param name="obj"> </param>
  839. /// <param name="timeoutsecs"> </param>
  840. /// <returns></returns>
  841. ///
  842. /// <exception cref="System.Net.WebException">Thrown if we encounter a network issue while posting
  843. /// the request. You'll want to make sure you deal with this as they're not uncommon</exception>
  844. public static string MakeRequest(string verb, string requestUrl, string obj, int timeoutsecs = -1,
  845. IServiceAuth auth = null, bool keepalive = true)
  846. {
  847. int reqnum = WebUtil.RequestNumber++;
  848. if (WebUtil.DebugLevel >= 3)
  849. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestForms {1} to {2}",
  850. reqnum, verb, requestUrl);
  851. int tickstart = Util.EnvironmentTickCount();
  852. HttpWebRequest request = null;
  853. try
  854. {
  855. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  856. request.Method = verb;
  857. if (timeoutsecs > 0)
  858. request.Timeout = timeoutsecs * 1000;
  859. if(!keepalive)
  860. request.KeepAlive = false;
  861. if (auth != null)
  862. auth.AddAuthorization(request.Headers);
  863. request.AllowWriteStreamBuffering = false;
  864. request.ContentType = "application/x-www-form-urlencoded";
  865. }
  866. catch (Exception e)
  867. {
  868. m_log.InfoFormat("[FORMS]: Error creating {0} request to : {1}. Request: {2}", verb, requestUrl, e.Message);
  869. throw e;
  870. }
  871. int sendlen = 0;
  872. if (obj.Length > 0 && (verb == "POST") || (verb == "PUT"))
  873. {
  874. byte[] data = Util.UTF8NBGetbytes(obj);
  875. sendlen = data.Length;
  876. request.ContentLength = sendlen;
  877. if (WebUtil.DebugLevel >= 5)
  878. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  879. try
  880. {
  881. using(Stream requestStream = request.GetRequestStream())
  882. requestStream.Write(data, 0, sendlen);
  883. data = null;
  884. }
  885. catch (Exception e)
  886. {
  887. m_log.InfoFormat("[FORMS]: Error sending {0} request to: {1}. {2}", verb,requestUrl, e.Message);
  888. throw e;
  889. }
  890. }
  891. int rcvlen = 0;
  892. string respstring = String.Empty;
  893. try
  894. {
  895. using (WebResponse resp = request.GetResponse())
  896. {
  897. if (resp.ContentLength != 0)
  898. {
  899. using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
  900. respstring = reader.ReadToEnd();
  901. rcvlen = respstring.Length;
  902. }
  903. }
  904. }
  905. catch (Exception e)
  906. {
  907. m_log.InfoFormat("[FORMS]: Error receiving response from {0}: {1}.", requestUrl, e.Message);
  908. throw e;
  909. }
  910. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  911. if (tickdiff > WebUtil.LongCallTime)
  912. {
  913. m_log.InfoFormat("[FORMS]: request {0} {1} {2} took {3}ms, {4}/{5}bytes",
  914. reqnum, verb, requestUrl, tickdiff, sendlen, rcvlen);
  915. }
  916. else if (WebUtil.DebugLevel >= 4)
  917. {
  918. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  919. reqnum, tickdiff);
  920. if (WebUtil.DebugLevel >= 5)
  921. WebUtil.LogResponseDetail(reqnum, respstring);
  922. }
  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. HttpWebRequest request = null;
  938. try
  939. {
  940. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  941. request.Method = "POST";
  942. if (timeoutsecs > 0)
  943. request.Timeout = timeoutsecs * 1000;
  944. if (!keepalive)
  945. request.KeepAlive = false;
  946. if (auth != null)
  947. auth.AddAuthorization(request.Headers);
  948. request.AllowWriteStreamBuffering = false;
  949. request.ContentType = "application/x-www-form-urlencoded";
  950. }
  951. catch (Exception e)
  952. {
  953. m_log.InfoFormat("[FORMS]: Error creating POST request to {0}: {1}", requestUrl, e.Message);
  954. throw e;
  955. }
  956. byte[] data = Util.UTF8NBGetbytes(obj);
  957. int sendlen = data.Length;
  958. request.ContentLength = sendlen;
  959. if (WebUtil.DebugLevel >= 5)
  960. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  961. try
  962. {
  963. using (Stream requestStream = request.GetRequestStream())
  964. requestStream.Write(data, 0, sendlen);
  965. data = null;
  966. }
  967. catch (Exception e)
  968. {
  969. m_log.InfoFormat("[FORMS]: Error sending POST request to {0}: {1}", requestUrl, e.Message);
  970. throw e;
  971. }
  972. string respstring = String.Empty;
  973. int rcvlen = 0;
  974. try
  975. {
  976. using (WebResponse resp = request.GetResponse())
  977. {
  978. if (resp.ContentLength != 0)
  979. {
  980. using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
  981. respstring = reader.ReadToEnd();
  982. }
  983. }
  984. }
  985. catch (Exception e)
  986. {
  987. m_log.InfoFormat("[FORMS]: Error receiving response from {0}: {1}", requestUrl, e.Message);
  988. throw e;
  989. }
  990. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  991. if (tickdiff > WebUtil.LongCallTime)
  992. {
  993. m_log.InfoFormat("[FORMS]: request {0} POST {1} took {2}ms {3}/{4}bytes",
  994. reqnum, requestUrl, tickdiff, sendlen, rcvlen);
  995. }
  996. else if (WebUtil.DebugLevel >= 4)
  997. {
  998. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  999. reqnum, tickdiff);
  1000. if (WebUtil.DebugLevel >= 5)
  1001. WebUtil.LogResponseDetail(reqnum, respstring);
  1002. }
  1003. return respstring;
  1004. }
  1005. }
  1006. public class SynchronousRestObjectRequester
  1007. {
  1008. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  1009. /// <summary>
  1010. /// Perform a synchronous REST request.
  1011. /// </summary>
  1012. /// <param name="verb"></param>
  1013. /// <param name="requestUrl"></param>
  1014. /// <param name="obj"></param>
  1015. /// <returns>
  1016. /// The response. If there was an internal exception, then the default(TResponse) is returned.
  1017. /// </returns>
  1018. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj)
  1019. {
  1020. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0);
  1021. }
  1022. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, IServiceAuth auth)
  1023. {
  1024. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0, auth);
  1025. }
  1026. /// <summary>
  1027. /// Perform a synchronous REST request.
  1028. /// </summary>
  1029. /// <param name="verb"></param>
  1030. /// <param name="requestUrl"></param>
  1031. /// <param name="obj"></param>
  1032. /// <param name="pTimeout">
  1033. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1034. /// </param>
  1035. /// <returns>
  1036. /// The response. If there was an internal exception or the request timed out,
  1037. /// then the default(TResponse) is returned.
  1038. /// </returns>
  1039. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout)
  1040. {
  1041. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0);
  1042. }
  1043. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, IServiceAuth auth)
  1044. {
  1045. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0, auth);
  1046. }
  1047. /// Perform a synchronous REST request.
  1048. /// </summary>
  1049. /// <param name="verb"></param>
  1050. /// <param name="requestUrl"></param>
  1051. /// <param name="obj"></param>
  1052. /// <param name="pTimeout">
  1053. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1054. /// </param>
  1055. /// <param name="maxConnections"></param>
  1056. /// <returns>
  1057. /// The response. If there was an internal exception or the request timed out,
  1058. /// then the default(TResponse) is returned.
  1059. /// </returns>
  1060. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections)
  1061. {
  1062. return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, maxConnections, null);
  1063. }
  1064. /// <summary>
  1065. /// Perform a synchronous REST request.
  1066. /// </summary>
  1067. /// <param name="verb"></param>
  1068. /// <param name="requestUrl"></param>
  1069. /// <param name="obj"></param>
  1070. /// <param name="pTimeout">
  1071. /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
  1072. /// </param>
  1073. /// <param name="maxConnections"></param>
  1074. /// <returns>
  1075. /// The response. If there was an internal exception or the request timed out,
  1076. /// then the default(TResponse) is returned.
  1077. /// </returns>
  1078. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections, IServiceAuth auth)
  1079. {
  1080. int reqnum = WebUtil.RequestNumber++;
  1081. if (WebUtil.DebugLevel >= 3)
  1082. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SRestObjectRequest {1} {2}",
  1083. reqnum, verb, requestUrl);
  1084. int tickstart = Util.EnvironmentTickCount();
  1085. TResponse deserial = default(TResponse);
  1086. HttpWebRequest request = null;
  1087. try
  1088. {
  1089. request = (HttpWebRequest)WebRequest.Create(requestUrl);
  1090. if (auth != null)
  1091. auth.AddAuthorization(request.Headers);
  1092. request.AllowWriteStreamBuffering = false;
  1093. if (pTimeout != 0)
  1094. request.Timeout = pTimeout;
  1095. request.Method = verb;
  1096. }
  1097. catch (Exception e)
  1098. {
  1099. m_log.DebugFormat("[SRestObjectRequest]: Exception in creating request {0} {1}: {2}{3}",
  1100. verb, requestUrl, e.Message, e.StackTrace);
  1101. return deserial;
  1102. }
  1103. try
  1104. {
  1105. if ((verb == "POST") || (verb == "PUT"))
  1106. {
  1107. request.ContentType = "text/xml";
  1108. byte[] data;
  1109. XmlWriterSettings settings = new XmlWriterSettings(){Encoding = Util.UTF8};
  1110. using (MemoryStream ms = new MemoryStream())
  1111. using (XmlWriter writer = XmlWriter.Create(ms, settings))
  1112. {
  1113. XmlSerializer serializer = new XmlSerializer(typeof(TRequest));
  1114. serializer.Serialize(writer, obj);
  1115. writer.Flush();
  1116. data = ms.ToArray();
  1117. }
  1118. int sendlen = data.Length;
  1119. request.ContentLength = sendlen;
  1120. if (WebUtil.DebugLevel >= 5)
  1121. WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
  1122. using (Stream requestStream = request.GetRequestStream())
  1123. requestStream.Write(data, 0, sendlen);
  1124. data = null;
  1125. }
  1126. }
  1127. catch (Exception e)
  1128. {
  1129. m_log.DebugFormat(
  1130. "[SRestObjectRequest]: Exception in making request {0} {1}: {2}{3}",
  1131. verb, requestUrl, e.Message, e.StackTrace);
  1132. return deserial;
  1133. }
  1134. int rcvlen = 0;
  1135. try
  1136. {
  1137. using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
  1138. {
  1139. if (resp.ContentLength != 0)
  1140. {
  1141. rcvlen = (int)resp.ContentLength;
  1142. using (Stream respStream = resp.GetResponseStream())
  1143. {
  1144. deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
  1145. reqnum, respStream, resp.ContentLength);
  1146. }
  1147. }
  1148. else
  1149. {
  1150. m_log.DebugFormat("[SRestObjectRequest]: Oops! no content found in response stream from {0} {1}",
  1151. verb, requestUrl);
  1152. }
  1153. }
  1154. }
  1155. catch (WebException e)
  1156. {
  1157. using (HttpWebResponse hwr = (HttpWebResponse)e.Response)
  1158. {
  1159. if (hwr != null)
  1160. {
  1161. if (hwr.StatusCode == HttpStatusCode.Unauthorized)
  1162. {
  1163. m_log.ErrorFormat("[SRestObjectRequest]: {0} requires authentication",
  1164. requestUrl);
  1165. }
  1166. else if(hwr.StatusCode != HttpStatusCode.NotFound)
  1167. {
  1168. m_log.WarnFormat("[SRestObjectRequest]: {0} returned error: {1}",
  1169. requestUrl, hwr.StatusCode);
  1170. }
  1171. }
  1172. else
  1173. m_log.ErrorFormat(
  1174. "[SRestObjectRequest]: WebException for {0} {1} {2} {3}",
  1175. verb, requestUrl, typeof(TResponse).ToString(), e.Message);
  1176. }
  1177. }
  1178. catch (System.InvalidOperationException)
  1179. {
  1180. // This is what happens when there is invalid XML
  1181. m_log.DebugFormat("[SRestObjectRequest]: Invalid XML from {0} {1} {2}",
  1182. verb, requestUrl, typeof(TResponse).ToString());
  1183. }
  1184. catch (Exception e)
  1185. {
  1186. m_log.DebugFormat("[SRestObjectRequest]: Exception on response from {0} {1}: {2}",
  1187. verb, requestUrl, e.Message);
  1188. }
  1189. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  1190. if (tickdiff > WebUtil.LongCallTime)
  1191. {
  1192. m_log.InfoFormat("[LOGHTTP]: Slow SRestObjectRequest {0} {1} {2} took {3}ms, {4}bytes",
  1193. reqnum, verb, requestUrl, tickdiff, rcvlen);
  1194. }
  1195. else if (WebUtil.DebugLevel >= 4)
  1196. {
  1197. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms",
  1198. reqnum, tickdiff);
  1199. }
  1200. return deserial;
  1201. }
  1202. }
  1203. public static class XMLResponseHelper
  1204. {
  1205. public static TResponse LogAndDeserialize<TRequest, TResponse>(int reqnum, Stream respStream, long contentLength)
  1206. {
  1207. XmlSerializer deserializer = new XmlSerializer(typeof(TResponse));
  1208. if (WebUtil.DebugLevel >= 5)
  1209. {
  1210. const int blockLength = 4096;
  1211. byte[] dataBuffer = new byte[blockLength];
  1212. int curcount;
  1213. using (MemoryStream ms = new MemoryStream(4 * blockLength))
  1214. {
  1215. if(contentLength == -1)
  1216. {
  1217. while (true)
  1218. {
  1219. curcount = respStream.Read(dataBuffer, 0, blockLength);
  1220. if (curcount <= 0)
  1221. break;
  1222. ms.Write(dataBuffer, 0, curcount);
  1223. }
  1224. }
  1225. else
  1226. {
  1227. int remaining = (int)contentLength;
  1228. while (remaining > 0)
  1229. {
  1230. curcount = respStream.Read(dataBuffer, 0, remaining);
  1231. if (curcount <= 0)
  1232. throw new EndOfStreamException(String.Format("End of stream reached with {0} bytes left to read", remaining));
  1233. ms.Write(dataBuffer, 0, curcount);
  1234. remaining -= curcount;
  1235. }
  1236. }
  1237. dataBuffer = ms.ToArray();
  1238. WebUtil.LogResponseDetail(reqnum, System.Text.Encoding.UTF8.GetString(dataBuffer));
  1239. ms.Position = 0;
  1240. return (TResponse)deserializer.Deserialize(ms);
  1241. }
  1242. }
  1243. else
  1244. {
  1245. return (TResponse)deserializer.Deserialize(respStream);
  1246. }
  1247. }
  1248. }
  1249. public static class XMLRPCRequester
  1250. {
  1251. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  1252. public static Hashtable SendRequest(Hashtable ReqParams, string method, string url)
  1253. {
  1254. int reqnum = WebUtil.RequestNumber++;
  1255. if (WebUtil.DebugLevel >= 3)
  1256. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} XML-RPC '{1}' to {2}",
  1257. reqnum, method, url);
  1258. int tickstart = Util.EnvironmentTickCount();
  1259. string responseStr = null;
  1260. try
  1261. {
  1262. ArrayList SendParams = new ArrayList();
  1263. SendParams.Add(ReqParams);
  1264. XmlRpcRequest Req = new XmlRpcRequest(method, SendParams);
  1265. if (WebUtil.DebugLevel >= 5)
  1266. {
  1267. string str = Req.ToString();
  1268. str = XElement.Parse(str).ToString(SaveOptions.DisableFormatting);
  1269. WebUtil.LogOutgoingDetail("SEND", reqnum, str);
  1270. }
  1271. XmlRpcResponse Resp = Req.Send(url, 30000);
  1272. try
  1273. {
  1274. if (WebUtil.DebugLevel >= 5)
  1275. {
  1276. responseStr = Resp.ToString();
  1277. responseStr = XElement.Parse(responseStr).ToString(SaveOptions.DisableFormatting);
  1278. WebUtil.LogResponseDetail(reqnum, responseStr);
  1279. }
  1280. }
  1281. catch (Exception e)
  1282. {
  1283. m_log.Error("Error parsing XML-RPC response", e);
  1284. }
  1285. if (Resp.IsFault)
  1286. {
  1287. m_log.DebugFormat(
  1288. "[LOGHTTP]: XML-RPC request {0} '{1}' to {2} FAILED: FaultCode={3}, FaultMessage={4}",
  1289. reqnum, method, url, Resp.FaultCode, Resp.FaultString);
  1290. return null;
  1291. }
  1292. Hashtable RespData = (Hashtable)Resp.Value;
  1293. return RespData;
  1294. }
  1295. finally
  1296. {
  1297. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  1298. if (tickdiff > WebUtil.LongCallTime)
  1299. {
  1300. m_log.InfoFormat(
  1301. "[LOGHTTP]: Slow XML-RPC request {0} '{1}' to {2} took {3}ms, {4}",
  1302. reqnum, method, url, tickdiff,
  1303. responseStr != null
  1304. ? (responseStr.Length > WebUtil.MaxRequestDiagLength ? responseStr.Remove(WebUtil.MaxRequestDiagLength) : responseStr)
  1305. : "");
  1306. }
  1307. else if (WebUtil.DebugLevel >= 4)
  1308. {
  1309. m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms", reqnum, tickdiff);
  1310. }
  1311. }
  1312. }
  1313. }
  1314. }