WebUtil.cs 39 KB

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