clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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  
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.Globalization;
33 using System.IO;
34 using System.IO.Compression;
35 using System.Net;
36 using System.Net.Security;
37 using System.Reflection;
38 using System.Text;
39 using System.Web;
40 using System.Xml;
41 using System.Xml.Serialization;
42 using System.Xml.Linq;
43 using log4net;
44 using Nwc.XmlRpc;
45 using OpenMetaverse.StructuredData;
46 using XMLResponseHelper = OpenSim.Framework.SynchronousRestObjectRequester.XMLResponseHelper;
47  
48 using OpenSim.Framework.ServiceAuth;
49  
50 namespace OpenSim.Framework
51 {
52 /// <summary>
53 /// Miscellaneous static methods and extension methods related to the web
54 /// </summary>
55 public static class WebUtil
56 {
57 private static readonly ILog m_log =
58 LogManager.GetLogger(
59 MethodBase.GetCurrentMethod().DeclaringType);
60  
61 /// <summary>
62 /// Control the printing of certain debug messages.
63 /// </summary>
64 /// <remarks>
65 /// If DebugLevel >= 3 then short notices about outgoing HTTP requests are logged.
66 /// </remarks>
67 public static int DebugLevel { get; set; }
68  
69 /// <summary>
70 /// Request number for diagnostic purposes.
71 /// </summary>
72 public static int RequestNumber { get; set; }
73  
74 /// <summary>
75 /// Control where OSD requests should be serialized per endpoint.
76 /// </summary>
77 public static bool SerializeOSDRequestsPerEndpoint { get; set; }
78  
79 /// <summary>
80 /// this is the header field used to communicate the local request id
81 /// used for performance and debugging
82 /// </summary>
83 public const string OSHeaderRequestID = "opensim-request-id";
84  
85 /// <summary>
86 /// Number of milliseconds a call can take before it is considered
87 /// a "long" call for warning & debugging purposes
88 /// </summary>
89 public const int LongCallTime = 3000;
90  
91 /// <summary>
92 /// The maximum length of any data logged because of a long request time.
93 /// </summary>
94 /// <remarks>
95 /// This is to truncate any really large post data, such as an asset. In theory, the first section should
96 /// give us useful information about the call (which agent it relates to if applicable, etc.).
97 /// This is also used to truncate messages when using DebugLevel 5.
98 /// </remarks>
99 public const int MaxRequestDiagLength = 200;
100  
101 /// <summary>
102 /// Dictionary of end points
103 /// </summary>
104 private static Dictionary<string,object> m_endpointSerializer = new Dictionary<string,object>();
105  
106 private static object EndPointLock(string url)
107 {
108 System.Uri uri = new System.Uri(url);
109 string endpoint = string.Format("{0}:{1}",uri.Host,uri.Port);
110  
111 lock (m_endpointSerializer)
112 {
113 object eplock = null;
114  
115 if (! m_endpointSerializer.TryGetValue(endpoint,out eplock))
116 {
117 eplock = new object();
118 m_endpointSerializer.Add(endpoint,eplock);
119 // m_log.WarnFormat("[WEB UTIL] add a new host to end point serializer {0}",endpoint);
120 }
121  
122 return eplock;
123 }
124 }
125  
126 #region JSONRequest
127  
128 /// <summary>
129 /// PUT JSON-encoded data to a web service that returns LLSD or
130 /// JSON data
131 /// </summary>
132 public static OSDMap PutToServiceCompressed(string url, OSDMap data, int timeout)
133 {
134 return ServiceOSDRequest(url,data, "PUT", timeout, true, false);
135 }
136  
137 public static OSDMap PutToService(string url, OSDMap data, int timeout)
138 {
139 return ServiceOSDRequest(url,data, "PUT", timeout, false, false);
140 }
141  
142 public static OSDMap PostToService(string url, OSDMap data, int timeout, bool rpc)
143 {
144 return ServiceOSDRequest(url, data, "POST", timeout, false, rpc);
145 }
146  
147 public static OSDMap PostToServiceCompressed(string url, OSDMap data, int timeout)
148 {
149 return ServiceOSDRequest(url, data, "POST", timeout, true, false);
150 }
151  
152 public static OSDMap GetFromService(string url, int timeout)
153 {
154 return ServiceOSDRequest(url, null, "GET", timeout, false, false);
155 }
156  
157 public static OSDMap ServiceOSDRequest(string url, OSDMap data, string method, int timeout, bool compressed, bool rpc)
158 {
159 if (SerializeOSDRequestsPerEndpoint)
160 {
161 lock (EndPointLock(url))
162 {
163 return ServiceOSDRequestWorker(url, data, method, timeout, compressed, rpc);
164 }
165 }
166 else
167 {
168 return ServiceOSDRequestWorker(url, data, method, timeout, compressed, rpc);
169 }
170 }
171  
172 public static void LogOutgoingDetail(Stream outputStream)
173 {
174 LogOutgoingDetail("", outputStream);
175 }
176  
177 public static void LogOutgoingDetail(string context, Stream outputStream)
178 {
179 using (Stream stream = Util.Copy(outputStream))
180 using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
181 {
182 string output;
183  
184 if (DebugLevel == 5)
185 {
186 char[] chars = new char[WebUtil.MaxRequestDiagLength + 1]; // +1 so we know to add "..." only if needed
187 int len = reader.Read(chars, 0, WebUtil.MaxRequestDiagLength + 1);
188 output = new string(chars, 0, len);
189 }
190 else
191 {
192 output = reader.ReadToEnd();
193 }
194  
195 LogOutgoingDetail(context, output);
196 }
197 }
198  
199 public static void LogOutgoingDetail(string type, int reqnum, string output)
200 {
201 LogOutgoingDetail(string.Format("{0} {1}: ", type, reqnum), output);
202 }
203  
204 public static void LogOutgoingDetail(string context, string output)
205 {
206 if (DebugLevel == 5)
207 {
208 if (output.Length > MaxRequestDiagLength)
209 output = output.Substring(0, MaxRequestDiagLength) + "...";
210 }
211  
212 m_log.DebugFormat("[LOGHTTP]: {0}{1}", context, Util.BinaryToASCII(output));
213 }
214  
215 public static void LogResponseDetail(int reqnum, Stream inputStream)
216 {
217 LogOutgoingDetail(string.Format("RESPONSE {0}: ", reqnum), inputStream);
218 }
219  
220 public static void LogResponseDetail(int reqnum, string input)
221 {
222 LogOutgoingDetail(string.Format("RESPONSE {0}: ", reqnum), input);
223 }
224  
225 private static OSDMap ServiceOSDRequestWorker(string url, OSDMap data, string method, int timeout, bool compressed, bool rpc)
226 {
227 int reqnum = RequestNumber++;
228  
229 if (DebugLevel >= 3)
230 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} JSON-RPC {1} to {2}",
231 reqnum, method, url);
232  
233 string errorMessage = "unknown error";
234 int tickstart = Util.EnvironmentTickCount();
235 int tickdata = 0;
236 string strBuffer = null;
237  
238 try
239 {
240 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
241 request.Method = method;
242 request.Timeout = timeout;
243 request.KeepAlive = false;
244 request.MaximumAutomaticRedirections = 10;
245 request.ReadWriteTimeout = timeout / 4;
246 request.Headers[OSHeaderRequestID] = reqnum.ToString();
247  
248 // If there is some input, write it into the request
249 if (data != null)
250 {
251 strBuffer = OSDParser.SerializeJsonString(data);
252  
253 if (DebugLevel >= 5)
254 LogOutgoingDetail("SEND", reqnum, strBuffer);
255  
256 byte[] buffer = System.Text.Encoding.UTF8.GetBytes(strBuffer);
257  
258 request.ContentType = rpc ? "application/json-rpc" : "application/json";
259  
260 if (compressed)
261 {
262 request.Headers["X-Content-Encoding"] = "gzip"; // can't set "Content-Encoding" because old OpenSims fail if they get an unrecognized Content-Encoding
263  
264 using (MemoryStream ms = new MemoryStream())
265 {
266 using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress, true))
267 {
268 comp.Write(buffer, 0, buffer.Length);
269 // We need to close the gzip stream before we write it anywhere
270 // because apparently something important related to gzip compression
271 // gets written on the stream upon Dispose()
272 }
273 byte[] buf = ms.ToArray();
274 request.ContentLength = buf.Length; //Count bytes to send
275 using (Stream requestStream = request.GetRequestStream())
276 requestStream.Write(buf, 0, (int)buf.Length);
277 }
278 }
279 else
280 {
281 request.ContentLength = buffer.Length; //Count bytes to send
282 using (Stream requestStream = request.GetRequestStream())
283 requestStream.Write(buffer, 0, buffer.Length); //Send it
284 }
285 }
286  
287 // capture how much time was spent writing, this may seem silly
288 // but with the number concurrent requests, this often blocks
289 tickdata = Util.EnvironmentTickCountSubtract(tickstart);
290  
291 using (WebResponse response = request.GetResponse())
292 {
293 using (Stream responseStream = response.GetResponseStream())
294 {
295 using (StreamReader reader = new StreamReader(responseStream))
296 {
297 string responseStr = reader.ReadToEnd();
298 if (WebUtil.DebugLevel >= 5)
299 WebUtil.LogResponseDetail(reqnum, responseStr);
300 return CanonicalizeResults(responseStr);
301 }
302 }
303 }
304 }
305 catch (WebException we)
306 {
307 errorMessage = we.Message;
308 if (we.Status == WebExceptionStatus.ProtocolError)
309 {
310 using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
311 errorMessage = String.Format("[{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription);
312 }
313 }
314 catch (Exception ex)
315 {
316 errorMessage = ex.Message;
317 }
318 finally
319 {
320 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
321 if (tickdiff > LongCallTime)
322 {
323 m_log.InfoFormat(
324 "[LOGHTTP]: Slow JSON-RPC request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
325 reqnum, method, url, tickdiff, tickdata,
326 strBuffer != null
327 ? (strBuffer.Length > MaxRequestDiagLength ? strBuffer.Remove(MaxRequestDiagLength) : strBuffer)
328 : "");
329 }
330 else if (DebugLevel >= 4)
331 {
332 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
333 reqnum, tickdiff, tickdata);
334 }
335 }
336  
337 m_log.DebugFormat(
338 "[LOGHTTP]: JSON-RPC request {0} {1} to {2} FAILED: {3}", reqnum, method, url, errorMessage);
339  
340 return ErrorResponseMap(errorMessage);
341 }
342  
343 /// <summary>
344 /// Since there are no consistencies in the way web requests are
345 /// formed, we need to do a little guessing about the result format.
346 /// Keys:
347 /// Success|success == the success fail of the request
348 /// _RawResult == the raw string that came back
349 /// _Result == the OSD unpacked string
350 /// </summary>
351 private static OSDMap CanonicalizeResults(string response)
352 {
353 OSDMap result = new OSDMap();
354  
355 // Default values
356 result["Success"] = OSD.FromBoolean(true);
357 result["success"] = OSD.FromBoolean(true);
358 result["_RawResult"] = OSD.FromString(response);
359 result["_Result"] = new OSDMap();
360  
361 if (response.Equals("true",System.StringComparison.OrdinalIgnoreCase))
362 return result;
363  
364 if (response.Equals("false",System.StringComparison.OrdinalIgnoreCase))
365 {
366 result["Success"] = OSD.FromBoolean(false);
367 result["success"] = OSD.FromBoolean(false);
368 return result;
369 }
370  
371 try
372 {
373 OSD responseOSD = OSDParser.Deserialize(response);
374 if (responseOSD.Type == OSDType.Map)
375 {
376 result["_Result"] = (OSDMap)responseOSD;
377 return result;
378 }
379 }
380 catch
381 {
382 // don't need to treat this as an error... we're just guessing anyway
383 // m_log.DebugFormat("[WEB UTIL] couldn't decode <{0}>: {1}",response,e.Message);
384 }
385  
386 return result;
387 }
388  
389 #endregion JSONRequest
390  
391 #region FormRequest
392  
393 /// <summary>
394 /// POST URL-encoded form data to a web service that returns LLSD or
395 /// JSON data
396 /// </summary>
397 public static OSDMap PostToService(string url, NameValueCollection data)
398 {
399 return ServiceFormRequest(url,data,10000);
400 }
401  
402 public static OSDMap ServiceFormRequest(string url, NameValueCollection data, int timeout)
403 {
404 lock (EndPointLock(url))
405 {
406 return ServiceFormRequestWorker(url,data,timeout);
407 }
408 }
409  
410 private static OSDMap ServiceFormRequestWorker(string url, NameValueCollection data, int timeout)
411 {
412 int reqnum = RequestNumber++;
413 string method = (data != null && data["RequestMethod"] != null) ? data["RequestMethod"] : "unknown";
414  
415 if (DebugLevel >= 3)
416 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} ServiceForm '{1}' to {2}",
417 reqnum, method, url);
418  
419 string errorMessage = "unknown error";
420 int tickstart = Util.EnvironmentTickCount();
421 int tickdata = 0;
422 string queryString = null;
423  
424 try
425 {
426 HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
427 request.Method = "POST";
428 request.Timeout = timeout;
429 request.KeepAlive = false;
430 request.MaximumAutomaticRedirections = 10;
431 request.ReadWriteTimeout = timeout / 4;
432 request.Headers[OSHeaderRequestID] = reqnum.ToString();
433  
434 if (data != null)
435 {
436 queryString = BuildQueryString(data);
437  
438 if (DebugLevel >= 5)
439 LogOutgoingDetail("SEND", reqnum, queryString);
440  
441 byte[] buffer = System.Text.Encoding.UTF8.GetBytes(queryString);
442  
443 request.ContentLength = buffer.Length;
444 request.ContentType = "application/x-www-form-urlencoded";
445 using (Stream requestStream = request.GetRequestStream())
446 requestStream.Write(buffer, 0, buffer.Length);
447 }
448  
449 // capture how much time was spent writing, this may seem silly
450 // but with the number concurrent requests, this often blocks
451 tickdata = Util.EnvironmentTickCountSubtract(tickstart);
452  
453 using (WebResponse response = request.GetResponse())
454 {
455 using (Stream responseStream = response.GetResponseStream())
456 {
457 using (StreamReader reader = new StreamReader(responseStream))
458 {
459 string responseStr = reader.ReadToEnd();
460 if (WebUtil.DebugLevel >= 5)
461 WebUtil.LogResponseDetail(reqnum, responseStr);
462 OSD responseOSD = OSDParser.Deserialize(responseStr);
463  
464 if (responseOSD.Type == OSDType.Map)
465 return (OSDMap)responseOSD;
466 }
467 }
468 }
469 }
470 catch (WebException we)
471 {
472 errorMessage = we.Message;
473 if (we.Status == WebExceptionStatus.ProtocolError)
474 {
475 using (HttpWebResponse webResponse = (HttpWebResponse)we.Response)
476 errorMessage = String.Format("[{0}] {1}",webResponse.StatusCode,webResponse.StatusDescription);
477 }
478 }
479 catch (Exception ex)
480 {
481 errorMessage = ex.Message;
482 }
483 finally
484 {
485 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
486 if (tickdiff > LongCallTime)
487 {
488 m_log.InfoFormat(
489 "[LOGHTTP]: Slow ServiceForm request {0} '{1}' to {2} took {3}ms, {4}ms writing, {5}",
490 reqnum, method, url, tickdiff, tickdata,
491 queryString != null
492 ? (queryString.Length > MaxRequestDiagLength) ? queryString.Remove(MaxRequestDiagLength) : queryString
493 : "");
494 }
495 else if (DebugLevel >= 4)
496 {
497 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
498 reqnum, tickdiff, tickdata);
499 }
500 }
501  
502 m_log.WarnFormat("[LOGHTTP]: ServiceForm request {0} '{1}' to {2} failed: {3}", reqnum, method, url, errorMessage);
503  
504 return ErrorResponseMap(errorMessage);
505 }
506  
507 /// <summary>
508 /// Create a response map for an error, trying to keep
509 /// the result formats consistent
510 /// </summary>
511 private static OSDMap ErrorResponseMap(string msg)
512 {
513 OSDMap result = new OSDMap();
514 result["Success"] = "False";
515 result["Message"] = OSD.FromString("Service request failed: " + msg);
516 return result;
517 }
518  
519 #endregion FormRequest
520  
521 #region Uri
522  
523 /// <summary>
524 /// Combines a Uri that can contain both a base Uri and relative path
525 /// with a second relative path fragment
526 /// </summary>
527 /// <param name="uri">Starting (base) Uri</param>
528 /// <param name="fragment">Relative path fragment to append to the end
529 /// of the Uri</param>
530 /// <returns>The combined Uri</returns>
531 /// <remarks>This is similar to the Uri constructor that takes a base
532 /// Uri and the relative path, except this method can append a relative
533 /// path fragment on to an existing relative path</remarks>
534 public static Uri Combine(this Uri uri, string fragment)
535 {
536 string fragment1 = uri.Fragment;
537 string fragment2 = fragment;
538  
539 if (!fragment1.EndsWith("/"))
540 fragment1 = fragment1 + '/';
541 if (fragment2.StartsWith("/"))
542 fragment2 = fragment2.Substring(1);
543  
544 return new Uri(uri, fragment1 + fragment2);
545 }
546  
547 /// <summary>
548 /// Combines a Uri that can contain both a base Uri and relative path
549 /// with a second relative path fragment. If the fragment is absolute,
550 /// it will be returned without modification
551 /// </summary>
552 /// <param name="uri">Starting (base) Uri</param>
553 /// <param name="fragment">Relative path fragment to append to the end
554 /// of the Uri, or an absolute Uri to return unmodified</param>
555 /// <returns>The combined Uri</returns>
556 public static Uri Combine(this Uri uri, Uri fragment)
557 {
558 if (fragment.IsAbsoluteUri)
559 return fragment;
560  
561 string fragment1 = uri.Fragment;
562 string fragment2 = fragment.ToString();
563  
564 if (!fragment1.EndsWith("/"))
565 fragment1 = fragment1 + '/';
566 if (fragment2.StartsWith("/"))
567 fragment2 = fragment2.Substring(1);
568  
569 return new Uri(uri, fragment1 + fragment2);
570 }
571  
572 /// <summary>
573 /// Appends a query string to a Uri that may or may not have existing
574 /// query parameters
575 /// </summary>
576 /// <param name="uri">Uri to append the query to</param>
577 /// <param name="query">Query string to append. Can either start with ?
578 /// or just containg key/value pairs</param>
579 /// <returns>String representation of the Uri with the query string
580 /// appended</returns>
581 public static string AppendQuery(this Uri uri, string query)
582 {
583 if (String.IsNullOrEmpty(query))
584 return uri.ToString();
585  
586 if (query[0] == '?' || query[0] == '&')
587 query = query.Substring(1);
588  
589 string uriStr = uri.ToString();
590  
591 if (uriStr.Contains("?"))
592 return uriStr + '&' + query;
593 else
594 return uriStr + '?' + query;
595 }
596  
597 #endregion Uri
598  
599 #region NameValueCollection
600  
601 /// <summary>
602 /// Convert a NameValueCollection into a query string. This is the
603 /// inverse of HttpUtility.ParseQueryString()
604 /// </summary>
605 /// <param name="parameters">Collection of key/value pairs to convert</param>
606 /// <returns>A query string with URL-escaped values</returns>
607 public static string BuildQueryString(NameValueCollection parameters)
608 {
609 List<string> items = new List<string>(parameters.Count);
610  
611 foreach (string key in parameters.Keys)
612 {
613 string[] values = parameters.GetValues(key);
614 if (values != null)
615 {
616 foreach (string value in values)
617 items.Add(String.Concat(key, "=", HttpUtility.UrlEncode(value ?? String.Empty)));
618 }
619 }
620  
621 return String.Join("&", items.ToArray());
622 }
623  
624 /// <summary>
625 ///
626 /// </summary>
627 /// <param name="collection"></param>
628 /// <param name="key"></param>
629 /// <returns></returns>
630 public static string GetOne(this NameValueCollection collection, string key)
631 {
632 string[] values = collection.GetValues(key);
633 if (values != null && values.Length > 0)
634 return values[0];
635  
636 return null;
637 }
638  
639 #endregion NameValueCollection
640  
641 #region Stream
642  
643 /// <summary>
644 /// Copies the contents of one stream to another, starting at the
645 /// current position of each stream
646 /// </summary>
647 /// <param name="copyFrom">The stream to copy from, at the position
648 /// where copying should begin</param>
649 /// <param name="copyTo">The stream to copy to, at the position where
650 /// bytes should be written</param>
651 /// <param name="maximumBytesToCopy">The maximum bytes to copy</param>
652 /// <returns>The total number of bytes copied</returns>
653 /// <remarks>
654 /// Copying begins at the streams' current positions. The positions are
655 /// NOT reset after copying is complete.
656 /// NOTE!! .NET 4.0 adds the method 'Stream.CopyTo(stream, bufferSize)'.
657 /// This function could be replaced with that method once we move
658 /// totally to .NET 4.0. For versions before, this routine exists.
659 /// This routine used to be named 'CopyTo' but the int parameter has
660 /// a different meaning so this method was renamed to avoid any confusion.
661 /// </remarks>
662 public static int CopyStream(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy)
663 {
664 byte[] buffer = new byte[4096];
665 int readBytes;
666 int totalCopiedBytes = 0;
667  
668 while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(4096, maximumBytesToCopy))) > 0)
669 {
670 int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
671 copyTo.Write(buffer, 0, writeBytes);
672 totalCopiedBytes += writeBytes;
673 maximumBytesToCopy -= writeBytes;
674 }
675  
676 return totalCopiedBytes;
677 }
678  
679 #endregion Stream
680  
681 public class QBasedComparer : IComparer
682 {
683 public int Compare(Object x, Object y)
684 {
685 float qx = GetQ(x);
686 float qy = GetQ(y);
687 return qy.CompareTo(qx); // descending order
688 }
689  
690 private float GetQ(Object o)
691 {
692 // Example: image/png;q=0.9
693  
694 float qvalue = 1F;
695 if (o is String)
696 {
697 string mime = (string)o;
698 string[] parts = mime.Split(';');
699 if (parts.Length > 1)
700 {
701 string[] kvp = parts[1].Split('=');
702 if (kvp.Length == 2 && kvp[0] == "q")
703 float.TryParse(kvp[1], NumberStyles.Number, CultureInfo.InvariantCulture, out qvalue);
704 }
705 }
706  
707 return qvalue;
708 }
709 }
710  
711 /// <summary>
712 /// Takes the value of an Accept header and returns the preferred types
713 /// ordered by q value (if it exists).
714 /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
715 /// Exmaple output: ["jp2", "png", "jpg"]
716 /// NOTE: This doesn't handle the semantics of *'s...
717 /// </summary>
718 /// <param name="accept"></param>
719 /// <returns></returns>
720 public static string[] GetPreferredImageTypes(string accept)
721 {
722 if (string.IsNullOrEmpty(accept))
723 return new string[0];
724  
725 string[] types = accept.Split(new char[] { ',' });
726 if (types.Length > 0)
727 {
728 List<string> list = new List<string>(types);
729 list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
730 ArrayList tlist = new ArrayList(list);
731 tlist.Sort(new QBasedComparer());
732  
733 string[] result = new string[tlist.Count];
734 for (int i = 0; i < tlist.Count; i++)
735 {
736 string mime = (string)tlist[i];
737 string[] parts = mime.Split(new char[] { ';' });
738 string[] pair = parts[0].Split(new char[] { '/' });
739 if (pair.Length == 2)
740 result[i] = pair[1].ToLower();
741 else // oops, we don't know what this is...
742 result[i] = pair[0];
743 }
744  
745 return result;
746 }
747  
748 return new string[0];
749 }
750 }
751  
752 public static class AsynchronousRestObjectRequester
753 {
754 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
755  
756 /// <summary>
757 /// Perform an asynchronous REST request.
758 /// </summary>
759 /// <param name="verb">GET or POST</param>
760 /// <param name="requestUrl"></param>
761 /// <param name="obj"></param>
762 /// <param name="action"></param>
763 /// <returns></returns>
764 ///
765 /// <exception cref="System.Net.WebException">Thrown if we encounter a
766 /// network issue while posting the request. You'll want to make
767 /// sure you deal with this as they're not uncommon</exception>
768 //
769 public static void MakeRequest<TRequest, TResponse>(string verb,
770 string requestUrl, TRequest obj, Action<TResponse> action)
771 {
772 MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, 0);
773 }
774  
775 public static void MakeRequest<TRequest, TResponse>(string verb,
776 string requestUrl, TRequest obj, Action<TResponse> action,
777 int maxConnections)
778 {
779 MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, action, maxConnections, null);
780 }
781  
782 public static void MakeRequest<TRequest, TResponse>(string verb,
783 string requestUrl, TRequest obj, Action<TResponse> action,
784 int maxConnections, IServiceAuth auth)
785 {
786 int reqnum = WebUtil.RequestNumber++;
787  
788 if (WebUtil.DebugLevel >= 3)
789 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} AsynchronousRequestObject {1} to {2}",
790 reqnum, verb, requestUrl);
791  
792 int tickstart = Util.EnvironmentTickCount();
793 int tickdata = 0;
794  
795 Type type = typeof(TRequest);
796  
797 WebRequest request = WebRequest.Create(requestUrl);
798 HttpWebRequest ht = (HttpWebRequest)request;
799  
800 if (auth != null)
801 auth.AddAuthorization(ht.Headers);
802  
803 if (maxConnections > 0 && ht.ServicePoint.ConnectionLimit < maxConnections)
804 ht.ServicePoint.ConnectionLimit = maxConnections;
805  
806 TResponse deserial = default(TResponse);
807  
808 request.Method = verb;
809  
810 MemoryStream buffer = null;
811  
812 try
813 {
814 if (verb == "POST")
815 {
816 request.ContentType = "text/xml";
817  
818 buffer = new MemoryStream();
819  
820 XmlWriterSettings settings = new XmlWriterSettings();
821 settings.Encoding = Encoding.UTF8;
822  
823 using (XmlWriter writer = XmlWriter.Create(buffer, settings))
824 {
825 XmlSerializer serializer = new XmlSerializer(type);
826 serializer.Serialize(writer, obj);
827 writer.Flush();
828 }
829  
830 int length = (int)buffer.Length;
831 request.ContentLength = length;
832 byte[] data = buffer.ToArray();
833  
834 if (WebUtil.DebugLevel >= 5)
835 WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
836  
837 request.BeginGetRequestStream(delegate(IAsyncResult res)
838 {
839 using (Stream requestStream = request.EndGetRequestStream(res))
840 requestStream.Write(data, 0, length);
841  
842 // capture how much time was spent writing
843 tickdata = Util.EnvironmentTickCountSubtract(tickstart);
844  
845 request.BeginGetResponse(delegate(IAsyncResult ar)
846 {
847 using (WebResponse response = request.EndGetResponse(ar))
848 {
849 try
850 {
851 using (Stream respStream = response.GetResponseStream())
852 {
853 deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
854 reqnum, respStream, response.ContentLength);
855 }
856 }
857 catch (System.InvalidOperationException)
858 {
859 }
860 }
861  
862 action(deserial);
863  
864 }, null);
865 }, null);
866 }
867 else
868 {
869 request.BeginGetResponse(delegate(IAsyncResult res2)
870 {
871 try
872 {
873 // If the server returns a 404, this appears to trigger a System.Net.WebException even though that isn't
874 // documented in MSDN
875 using (WebResponse response = request.EndGetResponse(res2))
876 {
877 try
878 {
879 using (Stream respStream = response.GetResponseStream())
880 {
881 deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
882 reqnum, respStream, response.ContentLength);
883 }
884 }
885 catch (System.InvalidOperationException)
886 {
887 }
888 }
889 }
890 catch (WebException e)
891 {
892 if (e.Status == WebExceptionStatus.ProtocolError)
893 {
894 if (e.Response is HttpWebResponse)
895 {
896 using (HttpWebResponse httpResponse = (HttpWebResponse)e.Response)
897 {
898 if (httpResponse.StatusCode != HttpStatusCode.NotFound)
899 {
900 // We don't appear to be handling any other status codes, so log these feailures to that
901 // people don't spend unnecessary hours hunting phantom bugs.
902 m_log.DebugFormat(
903 "[ASYNC REQUEST]: Request {0} {1} failed with unexpected status code {2}",
904 verb, requestUrl, httpResponse.StatusCode);
905 }
906 }
907 }
908 }
909 else
910 {
911 m_log.ErrorFormat(
912 "[ASYNC REQUEST]: Request {0} {1} failed with status {2} and message {3}",
913 verb, requestUrl, e.Status, e.Message);
914 }
915 }
916 catch (Exception e)
917 {
918 m_log.ErrorFormat(
919 "[ASYNC REQUEST]: Request {0} {1} failed with exception {2}{3}",
920 verb, requestUrl, e.Message, e.StackTrace);
921 }
922  
923 // m_log.DebugFormat("[ASYNC REQUEST]: Received {0}", deserial.ToString());
924  
925 try
926 {
927 action(deserial);
928 }
929 catch (Exception e)
930 {
931 m_log.ErrorFormat(
932 "[ASYNC REQUEST]: Request {0} {1} callback failed with exception {2}{3}",
933 verb, requestUrl, e.Message, e.StackTrace);
934 }
935  
936 }, null);
937 }
938  
939 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
940 if (tickdiff > WebUtil.LongCallTime)
941 {
942 string originalRequest = null;
943  
944 if (buffer != null)
945 {
946 originalRequest = Encoding.UTF8.GetString(buffer.ToArray());
947  
948 if (originalRequest.Length > WebUtil.MaxRequestDiagLength)
949 originalRequest = originalRequest.Remove(WebUtil.MaxRequestDiagLength);
950 }
951  
952 m_log.InfoFormat(
953 "[LOGHTTP]: Slow AsynchronousRequestObject request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
954 reqnum, verb, requestUrl, tickdiff, tickdata,
955 originalRequest);
956 }
957 else if (WebUtil.DebugLevel >= 4)
958 {
959 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
960 reqnum, tickdiff, tickdata);
961 }
962 }
963 finally
964 {
965 if (buffer != null)
966 buffer.Dispose();
967 }
968 }
969 }
970  
971 public static class SynchronousRestFormsRequester
972 {
973 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
974  
975 /// <summary>
976 /// Perform a synchronous REST request.
977 /// </summary>
978 /// <param name="verb"></param>
979 /// <param name="requestUrl"></param>
980 /// <param name="obj"> </param>
981 /// <param name="timeoutsecs"> </param>
982 /// <returns></returns>
983 ///
984 /// <exception cref="System.Net.WebException">Thrown if we encounter a network issue while posting
985 /// the request. You'll want to make sure you deal with this as they're not uncommon</exception>
986 public static string MakeRequest(string verb, string requestUrl, string obj, int timeoutsecs, IServiceAuth auth)
987 {
988 int reqnum = WebUtil.RequestNumber++;
989  
990 if (WebUtil.DebugLevel >= 3)
991 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestForms {1} to {2}",
992 reqnum, verb, requestUrl);
993  
994 int tickstart = Util.EnvironmentTickCount();
995 int tickdata = 0;
996  
997 WebRequest request = WebRequest.Create(requestUrl);
998 request.Method = verb;
999 if (timeoutsecs > 0)
1000 request.Timeout = timeoutsecs * 1000;
1001  
1002 if (auth != null)
1003 auth.AddAuthorization(request.Headers);
1004  
1005 string respstring = String.Empty;
1006  
1007 using (MemoryStream buffer = new MemoryStream())
1008 {
1009 if ((verb == "POST") || (verb == "PUT"))
1010 {
1011 request.ContentType = "application/x-www-form-urlencoded";
1012  
1013 int length = 0;
1014 using (StreamWriter writer = new StreamWriter(buffer))
1015 {
1016 writer.Write(obj);
1017 writer.Flush();
1018 }
1019  
1020 length = (int)obj.Length;
1021 request.ContentLength = length;
1022 byte[] data = buffer.ToArray();
1023  
1024 if (WebUtil.DebugLevel >= 5)
1025 WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
1026  
1027 Stream requestStream = null;
1028 try
1029 {
1030 requestStream = request.GetRequestStream();
1031 requestStream.Write(data, 0, length);
1032 }
1033 catch (Exception e)
1034 {
1035 m_log.InfoFormat("[FORMS]: Error sending request to {0}: {1}. Request: {2}", requestUrl, e.Message,
1036 obj.Length > WebUtil.MaxRequestDiagLength ? obj.Remove(WebUtil.MaxRequestDiagLength) : obj);
1037 throw e;
1038 }
1039 finally
1040 {
1041 if (requestStream != null)
1042 requestStream.Dispose();
1043  
1044 // capture how much time was spent writing
1045 tickdata = Util.EnvironmentTickCountSubtract(tickstart);
1046 }
1047 }
1048  
1049 try
1050 {
1051 using (WebResponse resp = request.GetResponse())
1052 {
1053 if (resp.ContentLength != 0)
1054 {
1055 using (Stream respStream = resp.GetResponseStream())
1056 using (StreamReader reader = new StreamReader(respStream))
1057 respstring = reader.ReadToEnd();
1058 }
1059 }
1060 }
1061 catch (Exception e)
1062 {
1063 m_log.InfoFormat("[FORMS]: Error receiving response from {0}: {1}. Request: {2}", requestUrl, e.Message,
1064 obj.Length > WebUtil.MaxRequestDiagLength ? obj.Remove(WebUtil.MaxRequestDiagLength) : obj);
1065 throw e;
1066 }
1067 }
1068  
1069 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
1070 if (tickdiff > WebUtil.LongCallTime)
1071 {
1072 m_log.InfoFormat(
1073 "[LOGHTTP]: Slow SynchronousRestForms request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
1074 reqnum, verb, requestUrl, tickdiff, tickdata,
1075 obj.Length > WebUtil.MaxRequestDiagLength ? obj.Remove(WebUtil.MaxRequestDiagLength) : obj);
1076 }
1077 else if (WebUtil.DebugLevel >= 4)
1078 {
1079 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
1080 reqnum, tickdiff, tickdata);
1081 }
1082  
1083 if (WebUtil.DebugLevel >= 5)
1084 WebUtil.LogResponseDetail(reqnum, respstring);
1085  
1086 return respstring;
1087 }
1088  
1089 public static string MakeRequest(string verb, string requestUrl, string obj, int timeoutsecs)
1090 {
1091 return MakeRequest(verb, requestUrl, obj, timeoutsecs, null);
1092 }
1093  
1094 public static string MakeRequest(string verb, string requestUrl, string obj)
1095 {
1096 return MakeRequest(verb, requestUrl, obj, -1);
1097 }
1098  
1099 public static string MakeRequest(string verb, string requestUrl, string obj, IServiceAuth auth)
1100 {
1101 return MakeRequest(verb, requestUrl, obj, -1, auth);
1102 }
1103 }
1104  
1105 public class SynchronousRestObjectRequester
1106 {
1107 private static readonly ILog m_log =
1108 LogManager.GetLogger(
1109 MethodBase.GetCurrentMethod().DeclaringType);
1110  
1111 /// <summary>
1112 /// Perform a synchronous REST request.
1113 /// </summary>
1114 /// <param name="verb"></param>
1115 /// <param name="requestUrl"></param>
1116 /// <param name="obj"></param>
1117 /// <returns>
1118 /// The response. If there was an internal exception, then the default(TResponse) is returned.
1119 /// </returns>
1120 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj)
1121 {
1122 return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0);
1123 }
1124  
1125 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, IServiceAuth auth)
1126 {
1127 return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, 0, auth);
1128 }
1129 /// <summary>
1130 /// Perform a synchronous REST request.
1131 /// </summary>
1132 /// <param name="verb"></param>
1133 /// <param name="requestUrl"></param>
1134 /// <param name="obj"></param>
1135 /// <param name="pTimeout">
1136 /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
1137 /// </param>
1138 /// <returns>
1139 /// The response. If there was an internal exception or the request timed out,
1140 /// then the default(TResponse) is returned.
1141 /// </returns>
1142 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout)
1143 {
1144 return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0);
1145 }
1146  
1147 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, IServiceAuth auth)
1148 {
1149 return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, 0, auth);
1150 }
1151  
1152 /// Perform a synchronous REST request.
1153 /// </summary>
1154 /// <param name="verb"></param>
1155 /// <param name="requestUrl"></param>
1156 /// <param name="obj"></param>
1157 /// <param name="pTimeout">
1158 /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
1159 /// </param>
1160 /// <param name="maxConnections"></param>
1161 /// <returns>
1162 /// The response. If there was an internal exception or the request timed out,
1163 /// then the default(TResponse) is returned.
1164 /// </returns>
1165 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections)
1166 {
1167 return MakeRequest<TRequest, TResponse>(verb, requestUrl, obj, pTimeout, maxConnections, null);
1168 }
1169  
1170 /// <summary>
1171 /// Perform a synchronous REST request.
1172 /// </summary>
1173 /// <param name="verb"></param>
1174 /// <param name="requestUrl"></param>
1175 /// <param name="obj"></param>
1176 /// <param name="pTimeout">
1177 /// Request timeout in milliseconds. Timeout.Infinite indicates no timeout. If 0 is passed then the default HttpWebRequest timeout is used (100 seconds)
1178 /// </param>
1179 /// <param name="maxConnections"></param>
1180 /// <returns>
1181 /// The response. If there was an internal exception or the request timed out,
1182 /// then the default(TResponse) is returned.
1183 /// </returns>
1184 public static TResponse MakeRequest<TRequest, TResponse>(string verb, string requestUrl, TRequest obj, int pTimeout, int maxConnections, IServiceAuth auth)
1185 {
1186 int reqnum = WebUtil.RequestNumber++;
1187  
1188 if (WebUtil.DebugLevel >= 3)
1189 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} SynchronousRestObject {1} to {2}",
1190 reqnum, verb, requestUrl);
1191  
1192 int tickstart = Util.EnvironmentTickCount();
1193 int tickdata = 0;
1194  
1195 Type type = typeof(TRequest);
1196 TResponse deserial = default(TResponse);
1197  
1198 WebRequest request = WebRequest.Create(requestUrl);
1199 HttpWebRequest ht = (HttpWebRequest)request;
1200  
1201 if (auth != null)
1202 auth.AddAuthorization(ht.Headers);
1203  
1204 if (pTimeout != 0)
1205 ht.Timeout = pTimeout;
1206  
1207 if (maxConnections > 0 && ht.ServicePoint.ConnectionLimit < maxConnections)
1208 ht.ServicePoint.ConnectionLimit = maxConnections;
1209  
1210 request.Method = verb;
1211 MemoryStream buffer = null;
1212  
1213 try
1214 {
1215 if ((verb == "POST") || (verb == "PUT"))
1216 {
1217 request.ContentType = "text/xml";
1218  
1219 buffer = new MemoryStream();
1220  
1221 XmlWriterSettings settings = new XmlWriterSettings();
1222 settings.Encoding = Encoding.UTF8;
1223  
1224 using (XmlWriter writer = XmlWriter.Create(buffer, settings))
1225 {
1226 XmlSerializer serializer = new XmlSerializer(type);
1227 serializer.Serialize(writer, obj);
1228 writer.Flush();
1229 }
1230  
1231 int length = (int)buffer.Length;
1232 request.ContentLength = length;
1233 byte[] data = buffer.ToArray();
1234  
1235 if (WebUtil.DebugLevel >= 5)
1236 WebUtil.LogOutgoingDetail("SEND", reqnum, System.Text.Encoding.UTF8.GetString(data));
1237  
1238 try
1239 {
1240 using (Stream requestStream = request.GetRequestStream())
1241 requestStream.Write(data, 0, length);
1242 }
1243 catch (Exception e)
1244 {
1245 m_log.DebugFormat(
1246 "[SynchronousRestObjectRequester]: Exception in making request {0} {1}: {2}{3}",
1247 verb, requestUrl, e.Message, e.StackTrace);
1248  
1249 return deserial;
1250 }
1251 finally
1252 {
1253 // capture how much time was spent writing
1254 tickdata = Util.EnvironmentTickCountSubtract(tickstart);
1255 }
1256 }
1257  
1258 try
1259 {
1260 using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
1261 {
1262 if (resp.ContentLength != 0)
1263 {
1264 using (Stream respStream = resp.GetResponseStream())
1265 {
1266 deserial = XMLResponseHelper.LogAndDeserialize<TRequest, TResponse>(
1267 reqnum, respStream, resp.ContentLength);
1268 }
1269 }
1270 else
1271 {
1272 m_log.DebugFormat(
1273 "[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}",
1274 verb, requestUrl);
1275 }
1276 }
1277 }
1278 catch (WebException e)
1279 {
1280 using (HttpWebResponse hwr = (HttpWebResponse)e.Response)
1281 {
1282 if (hwr != null)
1283 {
1284 if (hwr.StatusCode == HttpStatusCode.NotFound)
1285 return deserial;
1286 if (hwr.StatusCode == HttpStatusCode.Unauthorized)
1287 {
1288 m_log.Error(string.Format(
1289 "[SynchronousRestObjectRequester]: Web request {0} requires authentication ",
1290 requestUrl));
1291 return deserial;
1292 }
1293 }
1294 else
1295 m_log.Error(string.Format(
1296 "[SynchronousRestObjectRequester]: WebException for {0} {1} {2} ",
1297 verb, requestUrl, typeof(TResponse).ToString()), e);
1298 }
1299 }
1300 catch (System.InvalidOperationException)
1301 {
1302 // This is what happens when there is invalid XML
1303 m_log.DebugFormat(
1304 "[SynchronousRestObjectRequester]: Invalid XML from {0} {1} {2}",
1305 verb, requestUrl, typeof(TResponse).ToString());
1306 }
1307 catch (Exception e)
1308 {
1309 m_log.Debug(string.Format(
1310 "[SynchronousRestObjectRequester]: Exception on response from {0} {1} ",
1311 verb, requestUrl), e);
1312 }
1313  
1314 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
1315 if (tickdiff > WebUtil.LongCallTime)
1316 {
1317 string originalRequest = null;
1318  
1319 if (buffer != null)
1320 {
1321 originalRequest = Encoding.UTF8.GetString(buffer.ToArray());
1322  
1323 if (originalRequest.Length > WebUtil.MaxRequestDiagLength)
1324 originalRequest = originalRequest.Remove(WebUtil.MaxRequestDiagLength);
1325 }
1326  
1327 m_log.InfoFormat(
1328 "[LOGHTTP]: Slow SynchronousRestObject request {0} {1} to {2} took {3}ms, {4}ms writing, {5}",
1329 reqnum, verb, requestUrl, tickdiff, tickdata,
1330 originalRequest);
1331 }
1332 else if (WebUtil.DebugLevel >= 4)
1333 {
1334 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms, {2}ms writing",
1335 reqnum, tickdiff, tickdata);
1336 }
1337 }
1338 finally
1339 {
1340 if (buffer != null)
1341 buffer.Dispose();
1342 }
1343  
1344 return deserial;
1345 }
1346  
1347  
1348 public static class XMLResponseHelper
1349 {
1350 public static TResponse LogAndDeserialize<TRequest, TResponse>(int reqnum, Stream respStream, long contentLength)
1351 {
1352 XmlSerializer deserializer = new XmlSerializer(typeof(TResponse));
1353  
1354 if (WebUtil.DebugLevel >= 5)
1355 {
1356 byte[] data = new byte[contentLength];
1357 Util.ReadStream(respStream, data);
1358  
1359 WebUtil.LogResponseDetail(reqnum, System.Text.Encoding.UTF8.GetString(data));
1360  
1361 using (MemoryStream temp = new MemoryStream(data))
1362 return (TResponse)deserializer.Deserialize(temp);
1363 }
1364 else
1365 {
1366 return (TResponse)deserializer.Deserialize(respStream);
1367 }
1368 }
1369 }
1370 }
1371  
1372  
1373 public static class XMLRPCRequester
1374 {
1375 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
1376  
1377 public static Hashtable SendRequest(Hashtable ReqParams, string method, string url)
1378 {
1379 int reqnum = WebUtil.RequestNumber++;
1380  
1381 if (WebUtil.DebugLevel >= 3)
1382 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} XML-RPC '{1}' to {2}",
1383 reqnum, method, url);
1384  
1385 int tickstart = Util.EnvironmentTickCount();
1386 string responseStr = null;
1387  
1388 try
1389 {
1390 ArrayList SendParams = new ArrayList();
1391 SendParams.Add(ReqParams);
1392  
1393 XmlRpcRequest Req = new XmlRpcRequest(method, SendParams);
1394  
1395 if (WebUtil.DebugLevel >= 5)
1396 {
1397 string str = Req.ToString();
1398 str = XElement.Parse(str).ToString(SaveOptions.DisableFormatting);
1399 WebUtil.LogOutgoingDetail("SEND", reqnum, str);
1400 }
1401  
1402 XmlRpcResponse Resp = Req.Send(url, 30000);
1403  
1404 try
1405 {
1406 responseStr = Resp.ToString();
1407 responseStr = XElement.Parse(responseStr).ToString(SaveOptions.DisableFormatting);
1408  
1409 if (WebUtil.DebugLevel >= 5)
1410 WebUtil.LogResponseDetail(reqnum, responseStr);
1411 }
1412 catch (Exception e)
1413 {
1414 m_log.Error("Error parsing XML-RPC response", e);
1415 }
1416  
1417 if (Resp.IsFault)
1418 {
1419 m_log.DebugFormat(
1420 "[LOGHTTP]: XML-RPC request {0} '{1}' to {2} FAILED: FaultCode={3}, FaultMessage={4}",
1421 reqnum, method, url, Resp.FaultCode, Resp.FaultString);
1422 return null;
1423 }
1424  
1425 Hashtable RespData = (Hashtable)Resp.Value;
1426 return RespData;
1427 }
1428 finally
1429 {
1430 int tickdiff = Util.EnvironmentTickCountSubtract(tickstart);
1431 if (tickdiff > WebUtil.LongCallTime)
1432 {
1433 m_log.InfoFormat(
1434 "[LOGHTTP]: Slow XML-RPC request {0} '{1}' to {2} took {3}ms, {4}",
1435 reqnum, method, url, tickdiff,
1436 responseStr != null
1437 ? (responseStr.Length > WebUtil.MaxRequestDiagLength ? responseStr.Remove(WebUtil.MaxRequestDiagLength) : responseStr)
1438 : "");
1439 }
1440 else if (WebUtil.DebugLevel >= 4)
1441 {
1442 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} took {1}ms", reqnum, tickdiff);
1443 }
1444 }
1445 }
1446  
1447 }
1448 }