WebUtil.cs 47 KB

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