WebUtil.cs 39 KB

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