WebUtil.cs 38 KB

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