opensim – Blame information for rev 1

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