WebUtil.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  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.Reflection;
  37. using System.Text;
  38. using System.Web;
  39. using System.Xml;
  40. using System.Xml.Serialization;
  41. using log4net;
  42. using OpenMetaverse.StructuredData;
  43. namespace OpenSim.Framework
  44. {
  45. /// <summary>
  46. /// Miscellaneous static methods and extension methods related to the web
  47. /// </summary>
  48. public static class WebUtil
  49. {
  50. private static readonly ILog m_log =
  51. LogManager.GetLogger(
  52. MethodBase.GetCurrentMethod().DeclaringType);
  53. private static int m_requestNumber = 0;
  54. // this is the header field used to communicate the local request id
  55. // used for performance and debugging
  56. public const string OSHeaderRequestID = "opensim-request-id";
  57. // number of milliseconds a call can take before it is considered
  58. // a "long" call for warning & debugging purposes
  59. public const int LongCallTime = 500;
  60. #region JSONRequest
  61. /// <summary>
  62. /// PUT JSON-encoded data to a web service that returns LLSD or
  63. /// JSON data
  64. /// </summary>
  65. public static OSDMap PutToServiceCompressed(string url, OSDMap data, int timeout)
  66. {
  67. return ServiceOSDRequest(url,data, "PUT", timeout, true);
  68. }
  69. public static OSDMap PutToService(string url, OSDMap data, int timeout)
  70. {
  71. return ServiceOSDRequest(url,data, "PUT", timeout, false);
  72. }
  73. public static OSDMap PostToService(string url, OSDMap data, int timeout)
  74. {
  75. return ServiceOSDRequest(url, data, "POST", timeout, false);
  76. }
  77. public static OSDMap PostToServiceCompressed(string url, OSDMap data, int timeout)
  78. {
  79. return ServiceOSDRequest(url, data, "POST", timeout, true);
  80. }
  81. public static OSDMap GetFromService(string url, int timeout)
  82. {
  83. return ServiceOSDRequest(url, null, "GET", timeout, false);
  84. }
  85. public static OSDMap ServiceOSDRequest(string url, OSDMap data, string method, int timeout, bool compressed)
  86. {
  87. int reqnum = m_requestNumber++;
  88. // m_log.DebugFormat("[WEB UTIL]: <{0}> start osd request for {1}, method {2}",reqnum,url,method);
  89. string errorMessage = "unknown error";
  90. int tickstart = Util.EnvironmentTickCount();
  91. int tickdata = 0;
  92. try
  93. {
  94. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
  95. request.Method = method;
  96. request.Timeout = timeout;
  97. request.KeepAlive = false;
  98. request.MaximumAutomaticRedirections = 10;
  99. request.ReadWriteTimeout = timeout / 4;
  100. request.Headers[OSHeaderRequestID] = reqnum.ToString();
  101. // If there is some input, write it into the request
  102. if (data != null)
  103. {
  104. string strBuffer = OSDParser.SerializeJsonString(data);
  105. byte[] buffer = System.Text.Encoding.UTF8.GetBytes(strBuffer);
  106. if (compressed)
  107. {
  108. request.ContentType = "application/x-gzip";
  109. using (MemoryStream ms = new MemoryStream())
  110. {
  111. using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress))
  112. {
  113. comp.Write(buffer, 0, buffer.Length);
  114. // We need to close the gzip stream before we write it anywhere
  115. // because apparently something important related to gzip compression
  116. // gets written on the strteam upon Dispose()
  117. }
  118. byte[] buf = ms.ToArray();
  119. request.ContentLength = buf.Length; //Count bytes to send
  120. using (Stream requestStream = request.GetRequestStream())
  121. requestStream.Write(buf, 0, (int)buf.Length);
  122. }
  123. }
  124. else
  125. {
  126. request.ContentType = "application/json";
  127. request.ContentLength = buffer.Length; //Count bytes to send
  128. using (Stream requestStream = request.GetRequestStream())
  129. requestStream.Write(buffer, 0, buffer.Length); //Send it
  130. }
  131. }
  132. // capture how much time was spent writing, this may seem silly
  133. // but with the number concurrent requests, this often blocks
  134. tickdata = Util.EnvironmentTickCountSubtract(tickstart);
  135. using (WebResponse response = request.GetResponse())
  136. {
  137. using (Stream responseStream = response.GetResponseStream())
  138. {
  139. string responseStr = null;
  140. responseStr = responseStream.GetStreamString();
  141. // m_log.DebugFormat("[WEB UTIL]: <{0}> response is <{1}>",reqnum,responseStr);
  142. return CanonicalizeResults(responseStr);
  143. }
  144. }
  145. }
  146. catch (WebException we)
  147. {
  148. errorMessage = we.Message;
  149. if (we.Status == WebExceptionStatus.ProtocolError)
  150. {
  151. HttpWebResponse webResponse = (HttpWebResponse)we.Response;
  152. errorMessage = String.Format("[{0}] {1}",webResponse.StatusCode,webResponse.StatusDescription);
  153. }
  154. }
  155. catch (Exception ex)
  156. {
  157. errorMessage = ex.Message;
  158. }
  159. finally
  160. {
  161. // This just dumps a warning for any operation that takes more than 100 ms
  162. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  163. if (tickdiff > LongCallTime)
  164. m_log.DebugFormat("[WEB UTIL]: osd request <{0}> (URI:{1}, METHOD:{2}) took {3}ms overall, {4}ms writing",
  165. reqnum,url,method,tickdiff,tickdata);
  166. }
  167. m_log.DebugFormat("[WEB UTIL]: <{0}> osd request for {1}, method {2} FAILED: {3}", reqnum, url, method, errorMessage);
  168. return ErrorResponseMap(errorMessage);
  169. }
  170. /// <summary>
  171. /// Since there are no consistencies in the way web requests are
  172. /// formed, we need to do a little guessing about the result format.
  173. /// Keys:
  174. /// Success|success == the success fail of the request
  175. /// _RawResult == the raw string that came back
  176. /// _Result == the OSD unpacked string
  177. /// </summary>
  178. private static OSDMap CanonicalizeResults(string response)
  179. {
  180. OSDMap result = new OSDMap();
  181. // Default values
  182. result["Success"] = OSD.FromBoolean(true);
  183. result["success"] = OSD.FromBoolean(true);
  184. result["_RawResult"] = OSD.FromString(response);
  185. result["_Result"] = new OSDMap();
  186. if (response.Equals("true",System.StringComparison.OrdinalIgnoreCase))
  187. return result;
  188. if (response.Equals("false",System.StringComparison.OrdinalIgnoreCase))
  189. {
  190. result["Success"] = OSD.FromBoolean(false);
  191. result["success"] = OSD.FromBoolean(false);
  192. return result;
  193. }
  194. try
  195. {
  196. OSD responseOSD = OSDParser.Deserialize(response);
  197. if (responseOSD.Type == OSDType.Map)
  198. {
  199. result["_Result"] = (OSDMap)responseOSD;
  200. return result;
  201. }
  202. }
  203. catch
  204. {
  205. // don't need to treat this as an error... we're just guessing anyway
  206. // m_log.DebugFormat("[WEB UTIL] couldn't decode <{0}>: {1}",response,e.Message);
  207. }
  208. return result;
  209. }
  210. #endregion JSONRequest
  211. #region FormRequest
  212. /// <summary>
  213. /// POST URL-encoded form data to a web service that returns LLSD or
  214. /// JSON data
  215. /// </summary>
  216. public static OSDMap PostToService(string url, NameValueCollection data)
  217. {
  218. return ServiceFormRequest(url,data,10000);
  219. }
  220. public static OSDMap ServiceFormRequest(string url, NameValueCollection data, int timeout)
  221. {
  222. int reqnum = m_requestNumber++;
  223. string method = (data != null && data["RequestMethod"] != null) ? data["RequestMethod"] : "unknown";
  224. // m_log.DebugFormat("[WEB UTIL]: <{0}> start form request for {1}, method {2}",reqnum,url,method);
  225. string errorMessage = "unknown error";
  226. int tickstart = Util.EnvironmentTickCount();
  227. int tickdata = 0;
  228. try
  229. {
  230. HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
  231. request.Method = "POST";
  232. request.Timeout = timeout;
  233. request.KeepAlive = false;
  234. request.MaximumAutomaticRedirections = 10;
  235. request.ReadWriteTimeout = timeout / 4;
  236. request.Headers[OSHeaderRequestID] = reqnum.ToString();
  237. if (data != null)
  238. {
  239. string queryString = BuildQueryString(data);
  240. byte[] buffer = System.Text.Encoding.UTF8.GetBytes(queryString);
  241. request.ContentLength = buffer.Length;
  242. request.ContentType = "application/x-www-form-urlencoded";
  243. using (Stream requestStream = request.GetRequestStream())
  244. requestStream.Write(buffer, 0, buffer.Length);
  245. }
  246. // capture how much time was spent writing, this may seem silly
  247. // but with the number concurrent requests, this often blocks
  248. tickdata = Util.EnvironmentTickCountSubtract(tickstart);
  249. using (WebResponse response = request.GetResponse())
  250. {
  251. using (Stream responseStream = response.GetResponseStream())
  252. {
  253. string responseStr = null;
  254. responseStr = responseStream.GetStreamString();
  255. OSD responseOSD = OSDParser.Deserialize(responseStr);
  256. if (responseOSD.Type == OSDType.Map)
  257. return (OSDMap)responseOSD;
  258. }
  259. }
  260. }
  261. catch (WebException we)
  262. {
  263. errorMessage = we.Message;
  264. if (we.Status == WebExceptionStatus.ProtocolError)
  265. {
  266. HttpWebResponse webResponse = (HttpWebResponse)we.Response;
  267. errorMessage = String.Format("[{0}] {1}",webResponse.StatusCode,webResponse.StatusDescription);
  268. }
  269. }
  270. catch (Exception ex)
  271. {
  272. errorMessage = ex.Message;
  273. }
  274. finally
  275. {
  276. int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
  277. if (tickdiff > LongCallTime)
  278. m_log.InfoFormat("[WEB UTIL]: form request <{0}> (URI:{1}, METHOD:{2}) took {3}ms overall, {4}ms writing",
  279. reqnum,url,method,tickdiff,tickdata);
  280. }
  281. m_log.WarnFormat("[WEB UTIL]: <{0}> form request failed: {1}",reqnum,errorMessage);
  282. return ErrorResponseMap(errorMessage);
  283. }
  284. /// <summary>
  285. /// Create a response map for an error, trying to keep
  286. /// the result formats consistent
  287. /// </summary>
  288. private static OSDMap ErrorResponseMap(string msg)
  289. {
  290. OSDMap result = new OSDMap();
  291. result["Success"] = "False";
  292. result["Message"] = OSD.FromString("Service request failed: " + msg);
  293. return result;
  294. }
  295. #endregion FormRequest
  296. #region Uri
  297. /// <summary>
  298. /// Combines a Uri that can contain both a base Uri and relative path
  299. /// with a second relative path fragment
  300. /// </summary>
  301. /// <param name="uri">Starting (base) Uri</param>
  302. /// <param name="fragment">Relative path fragment to append to the end
  303. /// of the Uri</param>
  304. /// <returns>The combined Uri</returns>
  305. /// <remarks>This is similar to the Uri constructor that takes a base
  306. /// Uri and the relative path, except this method can append a relative
  307. /// path fragment on to an existing relative path</remarks>
  308. public static Uri Combine(this Uri uri, string fragment)
  309. {
  310. string fragment1 = uri.Fragment;
  311. string fragment2 = fragment;
  312. if (!fragment1.EndsWith("/"))
  313. fragment1 = fragment1 + '/';
  314. if (fragment2.StartsWith("/"))
  315. fragment2 = fragment2.Substring(1);
  316. return new Uri(uri, fragment1 + fragment2);
  317. }
  318. /// <summary>
  319. /// Combines a Uri that can contain both a base Uri and relative path
  320. /// with a second relative path fragment. If the fragment is absolute,
  321. /// it will be returned without modification
  322. /// </summary>
  323. /// <param name="uri">Starting (base) Uri</param>
  324. /// <param name="fragment">Relative path fragment to append to the end
  325. /// of the Uri, or an absolute Uri to return unmodified</param>
  326. /// <returns>The combined Uri</returns>
  327. public static Uri Combine(this Uri uri, Uri fragment)
  328. {
  329. if (fragment.IsAbsoluteUri)
  330. return fragment;
  331. string fragment1 = uri.Fragment;
  332. string fragment2 = fragment.ToString();
  333. if (!fragment1.EndsWith("/"))
  334. fragment1 = fragment1 + '/';
  335. if (fragment2.StartsWith("/"))
  336. fragment2 = fragment2.Substring(1);
  337. return new Uri(uri, fragment1 + fragment2);
  338. }
  339. /// <summary>
  340. /// Appends a query string to a Uri that may or may not have existing
  341. /// query parameters
  342. /// </summary>
  343. /// <param name="uri">Uri to append the query to</param>
  344. /// <param name="query">Query string to append. Can either start with ?
  345. /// or just containg key/value pairs</param>
  346. /// <returns>String representation of the Uri with the query string
  347. /// appended</returns>
  348. public static string AppendQuery(this Uri uri, string query)
  349. {
  350. if (String.IsNullOrEmpty(query))
  351. return uri.ToString();
  352. if (query[0] == '?' || query[0] == '&')
  353. query = query.Substring(1);
  354. string uriStr = uri.ToString();
  355. if (uriStr.Contains("?"))
  356. return uriStr + '&' + query;
  357. else
  358. return uriStr + '?' + query;
  359. }
  360. #endregion Uri
  361. #region NameValueCollection
  362. /// <summary>
  363. /// Convert a NameValueCollection into a query string. This is the
  364. /// inverse of HttpUtility.ParseQueryString()
  365. /// </summary>
  366. /// <param name="parameters">Collection of key/value pairs to convert</param>
  367. /// <returns>A query string with URL-escaped values</returns>
  368. public static string BuildQueryString(NameValueCollection parameters)
  369. {
  370. List<string> items = new List<string>(parameters.Count);
  371. foreach (string key in parameters.Keys)
  372. {
  373. string[] values = parameters.GetValues(key);
  374. if (values != null)
  375. {
  376. foreach (string value in values)
  377. items.Add(String.Concat(key, "=", HttpUtility.UrlEncode(value ?? String.Empty)));
  378. }
  379. }
  380. return String.Join("&", items.ToArray());
  381. }
  382. /// <summary>
  383. ///
  384. /// </summary>
  385. /// <param name="collection"></param>
  386. /// <param name="key"></param>
  387. /// <returns></returns>
  388. public static string GetOne(this NameValueCollection collection, string key)
  389. {
  390. string[] values = collection.GetValues(key);
  391. if (values != null && values.Length > 0)
  392. return values[0];
  393. return null;
  394. }
  395. #endregion NameValueCollection
  396. #region Stream
  397. /// <summary>
  398. /// Copies the contents of one stream to another, starting at the
  399. /// current position of each stream
  400. /// </summary>
  401. /// <param name="copyFrom">The stream to copy from, at the position
  402. /// where copying should begin</param>
  403. /// <param name="copyTo">The stream to copy to, at the position where
  404. /// bytes should be written</param>
  405. /// <param name="maximumBytesToCopy">The maximum bytes to copy</param>
  406. /// <returns>The total number of bytes copied</returns>
  407. /// <remarks>
  408. /// Copying begins at the streams' current positions. The positions are
  409. /// NOT reset after copying is complete.
  410. /// </remarks>
  411. public static int CopyTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy)
  412. {
  413. byte[] buffer = new byte[4096];
  414. int readBytes;
  415. int totalCopiedBytes = 0;
  416. while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(4096, maximumBytesToCopy))) > 0)
  417. {
  418. int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
  419. copyTo.Write(buffer, 0, writeBytes);
  420. totalCopiedBytes += writeBytes;
  421. maximumBytesToCopy -= writeBytes;
  422. }
  423. return totalCopiedBytes;
  424. }
  425. /// <summary>
  426. /// Converts an entire stream to a string, regardless of current stream
  427. /// position
  428. /// </summary>
  429. /// <param name="stream">The stream to convert to a string</param>
  430. /// <returns></returns>
  431. /// <remarks>When this method is done, the stream position will be
  432. /// reset to its previous position before this method was called</remarks>
  433. public static string GetStreamString(this Stream stream)
  434. {
  435. string value = null;
  436. if (stream != null && stream.CanRead)
  437. {
  438. long rewindPos = -1;
  439. if (stream.CanSeek)
  440. {
  441. rewindPos = stream.Position;
  442. stream.Seek(0, SeekOrigin.Begin);
  443. }
  444. StreamReader reader = new StreamReader(stream);
  445. value = reader.ReadToEnd();
  446. if (rewindPos >= 0)
  447. stream.Seek(rewindPos, SeekOrigin.Begin);
  448. }
  449. return value;
  450. }
  451. #endregion Stream
  452. public class QBasedComparer : IComparer
  453. {
  454. public int Compare(Object x, Object y)
  455. {
  456. float qx = GetQ(x);
  457. float qy = GetQ(y);
  458. return qy.CompareTo(qx); // descending order
  459. }
  460. private float GetQ(Object o)
  461. {
  462. // Example: image/png;q=0.9
  463. float qvalue = 1F;
  464. if (o is String)
  465. {
  466. string mime = (string)o;
  467. string[] parts = mime.Split(';');
  468. if (parts.Length > 1)
  469. {
  470. string[] kvp = parts[1].Split('=');
  471. if (kvp.Length == 2 && kvp[0] == "q")
  472. float.TryParse(kvp[1], NumberStyles.Number, CultureInfo.InvariantCulture, out qvalue);
  473. }
  474. }
  475. return qvalue;
  476. }
  477. }
  478. /// <summary>
  479. /// Takes the value of an Accept header and returns the preferred types
  480. /// ordered by q value (if it exists).
  481. /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
  482. /// Exmaple output: ["jp2", "png", "jpg"]
  483. /// NOTE: This doesn't handle the semantics of *'s...
  484. /// </summary>
  485. /// <param name="accept"></param>
  486. /// <returns></returns>
  487. public static string[] GetPreferredImageTypes(string accept)
  488. {
  489. if (accept == null || accept == string.Empty)
  490. return new string[0];
  491. string[] types = accept.Split(new char[] { ',' });
  492. if (types.Length > 0)
  493. {
  494. List<string> list = new List<string>(types);
  495. list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
  496. ArrayList tlist = new ArrayList(list);
  497. tlist.Sort(new QBasedComparer());
  498. string[] result = new string[tlist.Count];
  499. for (int i = 0; i < tlist.Count; i++)
  500. {
  501. string mime = (string)tlist[i];
  502. string[] parts = mime.Split(new char[] { ';' });
  503. string[] pair = parts[0].Split(new char[] { '/' });
  504. if (pair.Length == 2)
  505. result[i] = pair[1].ToLower();
  506. else // oops, we don't know what this is...
  507. result[i] = pair[0];
  508. }
  509. return result;
  510. }
  511. return new string[0];
  512. }
  513. }
  514. public static class AsynchronousRestObjectRequester
  515. {
  516. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  517. /// <summary>
  518. /// Perform an asynchronous REST request.
  519. /// </summary>
  520. /// <param name="verb">GET or POST</param>
  521. /// <param name="requestUrl"></param>
  522. /// <param name="obj"></param>
  523. /// <param name="action"></param>
  524. /// <returns></returns>
  525. ///
  526. /// <exception cref="System.Net.WebException">Thrown if we encounter a
  527. /// network issue while posting the request. You'll want to make
  528. /// sure you deal with this as they're not uncommon</exception>
  529. //
  530. public static void MakeRequest<TRequest, TResponse>(string verb,
  531. string requestUrl, TRequest obj, Action<TResponse> action)
  532. {
  533. // m_log.DebugFormat("[ASYNC REQUEST]: Starting {0} {1}", verb, requestUrl);
  534. Type type = typeof(TRequest);
  535. WebRequest request = WebRequest.Create(requestUrl);
  536. WebResponse response = null;
  537. TResponse deserial = default(TResponse);
  538. XmlSerializer deserializer = new XmlSerializer(typeof(TResponse));
  539. request.Method = verb;
  540. if (verb == "POST")
  541. {
  542. request.ContentType = "text/xml";
  543. MemoryStream buffer = new MemoryStream();
  544. XmlWriterSettings settings = new XmlWriterSettings();
  545. settings.Encoding = Encoding.UTF8;
  546. using (XmlWriter writer = XmlWriter.Create(buffer, settings))
  547. {
  548. XmlSerializer serializer = new XmlSerializer(type);
  549. serializer.Serialize(writer, obj);
  550. writer.Flush();
  551. }
  552. int length = (int)buffer.Length;
  553. request.ContentLength = length;
  554. request.BeginGetRequestStream(delegate(IAsyncResult res)
  555. {
  556. Stream requestStream = request.EndGetRequestStream(res);
  557. requestStream.Write(buffer.ToArray(), 0, length);
  558. requestStream.Close();
  559. request.BeginGetResponse(delegate(IAsyncResult ar)
  560. {
  561. response = request.EndGetResponse(ar);
  562. Stream respStream = null;
  563. try
  564. {
  565. respStream = response.GetResponseStream();
  566. deserial = (TResponse)deserializer.Deserialize(
  567. respStream);
  568. }
  569. catch (System.InvalidOperationException)
  570. {
  571. }
  572. finally
  573. {
  574. // Let's not close this
  575. //buffer.Close();
  576. respStream.Close();
  577. response.Close();
  578. }
  579. action(deserial);
  580. }, null);
  581. }, null);
  582. return;
  583. }
  584. request.BeginGetResponse(delegate(IAsyncResult res2)
  585. {
  586. try
  587. {
  588. // If the server returns a 404, this appears to trigger a System.Net.WebException even though that isn't
  589. // documented in MSDN
  590. response = request.EndGetResponse(res2);
  591. Stream respStream = null;
  592. try
  593. {
  594. respStream = response.GetResponseStream();
  595. deserial = (TResponse)deserializer.Deserialize(respStream);
  596. }
  597. catch (System.InvalidOperationException)
  598. {
  599. }
  600. finally
  601. {
  602. respStream.Close();
  603. response.Close();
  604. }
  605. }
  606. catch (WebException e)
  607. {
  608. if (e.Status == WebExceptionStatus.ProtocolError)
  609. {
  610. if (e.Response is HttpWebResponse)
  611. {
  612. HttpWebResponse httpResponse = (HttpWebResponse)e.Response;
  613. if (httpResponse.StatusCode != HttpStatusCode.NotFound)
  614. {
  615. // We don't appear to be handling any other status codes, so log these feailures to that
  616. // people don't spend unnecessary hours hunting phantom bugs.
  617. m_log.DebugFormat(
  618. "[ASYNC REQUEST]: Request {0} {1} failed with unexpected status code {2}",
  619. verb, requestUrl, httpResponse.StatusCode);
  620. }
  621. }
  622. }
  623. else
  624. {
  625. m_log.ErrorFormat("[ASYNC REQUEST]: Request {0} {1} failed with status {2} and message {3}", verb, requestUrl, e.Status, e.Message);
  626. }
  627. }
  628. catch (Exception e)
  629. {
  630. m_log.ErrorFormat("[ASYNC REQUEST]: Request {0} {1} failed with exception {2}", verb, requestUrl, e);
  631. }
  632. // m_log.DebugFormat("[ASYNC REQUEST]: Received {0}", deserial.ToString());
  633. try
  634. {
  635. action(deserial);
  636. }
  637. catch (Exception e)
  638. {
  639. m_log.ErrorFormat(
  640. "[ASYNC REQUEST]: Request {0} {1} callback failed with exception {2}", verb, requestUrl, e);
  641. }
  642. }, null);
  643. }
  644. }
  645. public static class SynchronousRestFormsRequester
  646. {
  647. private static readonly ILog m_log =
  648. LogManager.GetLogger(
  649. MethodBase.GetCurrentMethod().DeclaringType);
  650. /// <summary>
  651. /// Perform a synchronous REST request.
  652. /// </summary>
  653. /// <param name="verb"></param>
  654. /// <param name="requestUrl"></param>
  655. /// <param name="obj"> </param>
  656. /// <returns></returns>
  657. ///
  658. /// <exception cref="System.Net.WebException">Thrown if we encounter a network issue while posting
  659. /// the request. You'll want to make sure you deal with this as they're not uncommon</exception>
  660. public static string MakeRequest(string verb, string requestUrl, string obj)
  661. {
  662. WebRequest request = WebRequest.Create(requestUrl);
  663. request.Method = verb;
  664. string respstring = String.Empty;
  665. using (MemoryStream buffer = new MemoryStream())
  666. {
  667. if ((verb == "POST") || (verb == "PUT"))
  668. {
  669. request.ContentType = "application/x-www-form-urlencoded";
  670. int length = 0;
  671. using (StreamWriter writer = new StreamWriter(buffer))
  672. {
  673. writer.Write(obj);
  674. writer.Flush();
  675. }
  676. length = (int)obj.Length;
  677. request.ContentLength = length;
  678. Stream requestStream = null;
  679. try
  680. {
  681. requestStream = request.GetRequestStream();
  682. requestStream.Write(buffer.ToArray(), 0, length);
  683. }
  684. catch (Exception e)
  685. {
  686. m_log.DebugFormat("[FORMS]: exception occured on sending request to {0}: " + e.ToString(), requestUrl);
  687. }
  688. finally
  689. {
  690. if (requestStream != null)
  691. requestStream.Close();
  692. }
  693. }
  694. try
  695. {
  696. using (WebResponse resp = request.GetResponse())
  697. {
  698. if (resp.ContentLength != 0)
  699. {
  700. Stream respStream = null;
  701. try
  702. {
  703. respStream = resp.GetResponseStream();
  704. using (StreamReader reader = new StreamReader(respStream))
  705. {
  706. respstring = reader.ReadToEnd();
  707. }
  708. }
  709. catch (Exception e)
  710. {
  711. m_log.DebugFormat("[FORMS]: exception occured on receiving reply " + e.ToString());
  712. }
  713. finally
  714. {
  715. if (respStream != null)
  716. respStream.Close();
  717. }
  718. }
  719. }
  720. }
  721. catch (System.InvalidOperationException)
  722. {
  723. // This is what happens when there is invalid XML
  724. m_log.DebugFormat("[FORMS]: InvalidOperationException on receiving request");
  725. }
  726. }
  727. return respstring;
  728. }
  729. }
  730. public class SynchronousRestObjectRequester
  731. {
  732. private static readonly ILog m_log =
  733. LogManager.GetLogger(
  734. MethodBase.GetCurrentMethod().DeclaringType);
  735. /// <summary>
  736. /// Perform a synchronous REST request.
  737. /// </summary>
  738. /// <param name="verb"></param>
  739. /// <param name="requestUrl"></param>
  740. /// <param name="obj"> </param>
  741. /// <returns></returns>
  742. ///
  743. /// <exception cref="System.Net.WebException">Thrown if we encounter a network issue while posting
  744. /// the request. You'll want to make sure you deal with this as they're not uncommon</exception>
  745. public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj)
  746. {
  747. Type type = typeof(TRequest);
  748. TResponse deserial = default(TResponse);
  749. WebRequest request = WebRequest.Create(requestUrl);
  750. request.Method = verb;
  751. if ((verb == "POST") || (verb == "PUT"))
  752. {
  753. request.ContentType = "text/xml";
  754. MemoryStream buffer = new MemoryStream();
  755. XmlWriterSettings settings = new XmlWriterSettings();
  756. settings.Encoding = Encoding.UTF8;
  757. using (XmlWriter writer = XmlWriter.Create(buffer, settings))
  758. {
  759. XmlSerializer serializer = new XmlSerializer(type);
  760. serializer.Serialize(writer, obj);
  761. writer.Flush();
  762. }
  763. int length = (int)buffer.Length;
  764. request.ContentLength = length;
  765. Stream requestStream = null;
  766. try
  767. {
  768. requestStream = request.GetRequestStream();
  769. requestStream.Write(buffer.ToArray(), 0, length);
  770. }
  771. catch (Exception e)
  772. {
  773. m_log.DebugFormat("[SynchronousRestObjectRequester]: exception in sending data to {0}: {1}", requestUrl, e);
  774. return deserial;
  775. }
  776. finally
  777. {
  778. if (requestStream != null)
  779. requestStream.Close();
  780. }
  781. }
  782. try
  783. {
  784. using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
  785. {
  786. if (resp.ContentLength != 0)
  787. {
  788. Stream respStream = resp.GetResponseStream();
  789. XmlSerializer deserializer = new XmlSerializer(typeof(TResponse));
  790. deserial = (TResponse)deserializer.Deserialize(respStream);
  791. respStream.Close();
  792. }
  793. else
  794. m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}", requestUrl, verb);
  795. }
  796. }
  797. catch (WebException e)
  798. {
  799. HttpWebResponse hwr = (HttpWebResponse)e.Response;
  800. if (hwr != null && hwr.StatusCode == HttpStatusCode.NotFound)
  801. return deserial;
  802. else
  803. m_log.ErrorFormat(
  804. "[SynchronousRestObjectRequester]: WebException {0} {1} {2} {3}",
  805. requestUrl, typeof(TResponse).ToString(), e.Message, e.StackTrace);
  806. }
  807. catch (System.InvalidOperationException)
  808. {
  809. // This is what happens when there is invalid XML
  810. m_log.DebugFormat("[SynchronousRestObjectRequester]: Invalid XML {0} {1}", requestUrl, typeof(TResponse).ToString());
  811. }
  812. catch (Exception e)
  813. {
  814. m_log.DebugFormat("[SynchronousRestObjectRequester]: Exception on response from {0} {1}", requestUrl, e);
  815. }
  816. return deserial;
  817. }
  818. }
  819. }