aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/RestClient.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/RestClient.cs')
-rw-r--r--OpenSim/Framework/RestClient.cs676
1 files changed, 676 insertions, 0 deletions
diff --git a/OpenSim/Framework/RestClient.cs b/OpenSim/Framework/RestClient.cs
new file mode 100644
index 0000000..7080ca5
--- /dev/null
+++ b/OpenSim/Framework/RestClient.cs
@@ -0,0 +1,676 @@
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
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Net;
32using System.Reflection;
33using System.Text;
34using System.Threading;
35using System.Web;
36using log4net;
37
38using OpenSim.Framework.ServiceAuth;
39
40namespace OpenSim.Framework
41{
42 /// <summary>
43 /// Implementation of a generic REST client
44 /// </summary>
45 /// <remarks>
46 /// This class is a generic implementation of a REST (Representational State Transfer) web service. This
47 /// class is designed to execute both synchronously and asynchronously.
48 ///
49 /// Internally the implementation works as a two stage asynchronous web-client.
50 /// When the request is initiated, RestClient will query asynchronously for for a web-response,
51 /// sleeping until the initial response is returned by the server. Once the initial response is retrieved
52 /// the second stage of asynchronous requests will be triggered, in an attempt to read of the response
53 /// object into a memorystream as a sequence of asynchronous reads.
54 ///
55 /// The asynchronisity of RestClient is designed to move as much processing into the back-ground, allowing
56 /// other threads to execute, while it waits for a response from the web-service. RestClient itself can be
57 /// invoked by the caller in either synchronous mode or asynchronous modes.
58 /// </remarks>
59 public class RestClient : IDisposable
60 {
61 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
62
63 // private string realuri;
64
65 #region member variables
66
67 /// <summary>
68 /// The base Uri of the web-service e.g. http://www.google.com
69 /// </summary>
70 private string _url;
71
72 /// <summary>
73 /// Path elements of the query
74 /// </summary>
75 private List<string> _pathElements = new List<string>();
76
77 /// <summary>
78 /// Parameter elements of the query, e.g. min=34
79 /// </summary>
80 private Dictionary<string, string> _parameterElements = new Dictionary<string, string>();
81
82 /// <summary>
83 /// Request method. E.g. GET, POST, PUT or DELETE
84 /// </summary>
85 private string _method;
86
87 /// <summary>
88 /// Temporary buffer used to store bytes temporarily as they come in from the server
89 /// </summary>
90 private byte[] _readbuf;
91
92 /// <summary>
93 /// MemoryStream representing the resulting resource
94 /// </summary>
95 private Stream _resource;
96
97 /// <summary>
98 /// WebRequest object, held as a member variable
99 /// </summary>
100 private HttpWebRequest _request;
101
102 /// <summary>
103 /// WebResponse object, held as a member variable, so we can close it
104 /// </summary>
105 private HttpWebResponse _response;
106
107 /// <summary>
108 /// This flag will help block the main synchroneous method, in case we run in synchroneous mode
109 /// </summary>
110 //public static ManualResetEvent _allDone = new ManualResetEvent(false);
111
112 /// <summary>
113 /// Default time out period
114 /// </summary>
115 //private const int DefaultTimeout = 10*1000; // 10 seconds timeout
116
117 /// <summary>
118 /// Default Buffer size of a block requested from the web-server
119 /// </summary>
120 private const int BufferSize = 4096; // Read blocks of 4 KB.
121
122
123 /// <summary>
124 /// if an exception occours during async processing, we need to save it, so it can be
125 /// rethrown on the primary thread;
126 /// </summary>
127 private Exception _asyncException;
128
129 #endregion member variables
130
131 #region constructors
132
133 /// <summary>
134 /// Instantiate a new RestClient
135 /// </summary>
136 /// <param name="url">Web-service to query, e.g. http://osgrid.org:8003</param>
137 public RestClient(string url)
138 {
139 _url = url;
140 _readbuf = new byte[BufferSize];
141 _resource = new MemoryStream();
142 _request = null;
143 _response = null;
144 _lock = new object();
145 }
146
147 private object _lock;
148
149 #endregion constructors
150
151
152 #region Dispose
153
154 private bool disposed = false;
155
156 public void Dispose()
157 {
158 Dispose(true);
159 GC.SuppressFinalize(this);
160 }
161
162 protected virtual void Dispose(bool disposing)
163 {
164 if (disposed)
165 return;
166
167 if (disposing)
168 {
169 _resource.Dispose();
170 }
171
172 disposed = true;
173 }
174
175 #endregion Dispose
176
177
178 /// <summary>
179 /// Add a path element to the query, e.g. assets
180 /// </summary>
181 /// <param name="element">path entry</param>
182 public void AddResourcePath(string element)
183 {
184 if (isSlashed(element))
185 _pathElements.Add(element.Substring(0, element.Length - 1));
186 else
187 _pathElements.Add(element);
188 }
189
190 /// <summary>
191 /// Add a query parameter to the Url
192 /// </summary>
193 /// <param name="name">Name of the parameter, e.g. min</param>
194 /// <param name="value">Value of the parameter, e.g. 42</param>
195 public void AddQueryParameter(string name, string value)
196 {
197 try
198 {
199 _parameterElements.Add(HttpUtility.UrlEncode(name), HttpUtility.UrlEncode(value));
200 }
201 catch (ArgumentException)
202 {
203 m_log.Error("[REST]: Query parameter " + name + " is already added.");
204 }
205 catch (Exception e)
206 {
207 m_log.Error("[REST]: An exception was raised adding query parameter to dictionary. Exception: {0}",e);
208 }
209 }
210
211 /// <summary>
212 /// Add a query parameter to the Url
213 /// </summary>
214 /// <param name="name">Name of the parameter, e.g. min</param>
215 public void AddQueryParameter(string name)
216 {
217 try
218 {
219 _parameterElements.Add(HttpUtility.UrlEncode(name), null);
220 }
221 catch (ArgumentException)
222 {
223 m_log.Error("[REST]: Query parameter " + name + " is already added.");
224 }
225 catch (Exception e)
226 {
227 m_log.Error("[REST]: An exception was raised adding query parameter to dictionary. Exception: {0}",e);
228 }
229 }
230
231 /// <summary>
232 /// Web-Request method, e.g. GET, PUT, POST, DELETE
233 /// </summary>
234 public string RequestMethod
235 {
236 get { return _method; }
237 set { _method = value; }
238 }
239
240 /// <summary>
241 /// True if string contains a trailing slash '/'
242 /// </summary>
243 /// <param name="s">string to be examined</param>
244 /// <returns>true if slash is present</returns>
245 private static bool isSlashed(string s)
246 {
247 return s.Substring(s.Length - 1, 1) == "/";
248 }
249
250 /// <summary>
251 /// Build a Uri based on the initial Url, path elements and parameters
252 /// </summary>
253 /// <returns>fully constructed Uri</returns>
254 private Uri buildUri()
255 {
256 StringBuilder sb = new StringBuilder();
257 sb.Append(_url);
258
259 foreach (string e in _pathElements)
260 {
261 sb.Append("/");
262 sb.Append(e);
263 }
264
265 bool firstElement = true;
266 foreach (KeyValuePair<string, string> kv in _parameterElements)
267 {
268 if (firstElement)
269 {
270 sb.Append("?");
271 firstElement = false;
272 }
273 else
274 sb.Append("&");
275
276 sb.Append(kv.Key);
277 if (!string.IsNullOrEmpty(kv.Value))
278 {
279 sb.Append("=");
280 sb.Append(kv.Value);
281 }
282 }
283 // realuri = sb.ToString();
284 //m_log.InfoFormat("[REST CLIENT]: RestURL: {0}", realuri);
285 return new Uri(sb.ToString());
286 }
287
288 #region Async communications with server
289
290 /// <summary>
291 /// Async method, invoked when a block of data has been received from the service
292 /// </summary>
293 /// <param name="ar"></param>
294 private void StreamIsReadyDelegate(IAsyncResult ar)
295 {
296 try
297 {
298 Stream s = (Stream) ar.AsyncState;
299 int read = s.EndRead(ar);
300
301 if (read > 0)
302 {
303 _resource.Write(_readbuf, 0, read);
304 // IAsyncResult asynchronousResult =
305 // s.BeginRead(_readbuf, 0, BufferSize, new AsyncCallback(StreamIsReadyDelegate), s);
306 s.BeginRead(_readbuf, 0, BufferSize, new AsyncCallback(StreamIsReadyDelegate), s);
307
308 // TODO! Implement timeout, without killing the server
309 //ThreadPool.RegisterWaitForSingleObject(asynchronousResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
310 }
311 else
312 {
313 s.Close();
314 //_allDone.Set();
315 }
316 }
317 catch (Exception e)
318 {
319 //_allDone.Set();
320 _asyncException = e;
321 }
322 }
323
324 #endregion Async communications with server
325
326 /// <summary>
327 /// Perform a synchronous request
328 /// </summary>
329 public Stream Request()
330 {
331 return Request(null);
332 }
333
334 /// <summary>
335 /// Perform a synchronous request
336 /// </summary>
337 public Stream Request(IServiceAuth auth)
338 {
339 lock (_lock)
340 {
341 _request = (HttpWebRequest) WebRequest.Create(buildUri());
342 _request.KeepAlive = false;
343 _request.ContentType = "application/xml";
344 _request.Timeout = 200000;
345 _request.Method = RequestMethod;
346 _asyncException = null;
347 if (auth != null)
348 auth.AddAuthorization(_request.Headers);
349
350 int reqnum = WebUtil.RequestNumber++;
351 if (WebUtil.DebugLevel >= 3)
352 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} REST {1} to {2}", reqnum, _request.Method, _request.RequestUri);
353
354// IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request);
355
356 try
357 {
358 using (_response = (HttpWebResponse) _request.GetResponse())
359 {
360 using (Stream src = _response.GetResponseStream())
361 {
362 int length = src.Read(_readbuf, 0, BufferSize);
363 while (length > 0)
364 {
365 _resource.Write(_readbuf, 0, length);
366 length = src.Read(_readbuf, 0, BufferSize);
367 }
368
369 // TODO! Implement timeout, without killing the server
370 // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
371 //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
372
373 // _allDone.WaitOne();
374 }
375 }
376 }
377 catch (WebException e)
378 {
379 using (HttpWebResponse errorResponse = e.Response as HttpWebResponse)
380 {
381 if (null != errorResponse && HttpStatusCode.NotFound == errorResponse.StatusCode)
382 {
383 // This is often benign. E.g., requesting a missing asset will return 404.
384 m_log.DebugFormat("[REST CLIENT] Resource not found (404): {0}", _request.Address.ToString());
385 }
386 else
387 {
388 m_log.Error(string.Format("[REST CLIENT] Error fetching resource from server: {0} ", _request.Address.ToString()), e);
389 }
390 }
391
392 return null;
393 }
394
395 if (_asyncException != null)
396 throw _asyncException;
397
398 if (_resource != null)
399 {
400 _resource.Flush();
401 _resource.Seek(0, SeekOrigin.Begin);
402 }
403
404 if (WebUtil.DebugLevel >= 5)
405 WebUtil.LogResponseDetail(reqnum, _resource);
406
407 return _resource;
408 }
409 }
410
411 public Stream Request(Stream src, IServiceAuth auth)
412 {
413 _request = (HttpWebRequest) WebRequest.Create(buildUri());
414 _request.KeepAlive = false;
415 _request.ContentType = "application/xml";
416 _request.Timeout = 900000;
417 _request.Method = RequestMethod;
418 _asyncException = null;
419 _request.ContentLength = src.Length;
420 if (auth != null)
421 auth.AddAuthorization(_request.Headers);
422
423 src.Seek(0, SeekOrigin.Begin);
424
425 int reqnum = WebUtil.RequestNumber++;
426 if (WebUtil.DebugLevel >= 3)
427 m_log.DebugFormat("[LOGHTTP]: HTTP OUT {0} REST {1} to {2}", reqnum, _request.Method, _request.RequestUri);
428 if (WebUtil.DebugLevel >= 5)
429 WebUtil.LogOutgoingDetail(string.Format("SEND {0}: ", reqnum), src);
430
431 Stream dst = _request.GetRequestStream();
432
433 byte[] buf = new byte[1024];
434 int length = src.Read(buf, 0, 1024);
435 while (length > 0)
436 {
437 dst.Write(buf, 0, length);
438 length = src.Read(buf, 0, 1024);
439 }
440
441 try
442 {
443 _response = (HttpWebResponse)_request.GetResponse();
444 }
445 catch (WebException e)
446 {
447 m_log.WarnFormat("[REST]: Request {0} {1} failed with status {2} and message {3}",
448 RequestMethod, _request.RequestUri, e.Status, e.Message);
449 return null;
450 }
451 catch (Exception e)
452 {
453 m_log.WarnFormat(
454 "[REST]: Request {0} {1} failed with exception {2} {3}",
455 RequestMethod, _request.RequestUri, e.Message, e.StackTrace);
456 return null;
457 }
458
459 if (WebUtil.DebugLevel >= 5)
460 {
461 using (Stream responseStream = _response.GetResponseStream())
462 {
463 using (StreamReader reader = new StreamReader(responseStream))
464 {
465 string responseStr = reader.ReadToEnd();
466 WebUtil.LogResponseDetail(reqnum, responseStr);
467 }
468 }
469 }
470
471 _response.Close();
472
473// IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request);
474
475 // TODO! Implement timeout, without killing the server
476 // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
477 //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
478
479 return null;
480 }
481
482 #region Async Invocation
483
484 public IAsyncResult BeginRequest(AsyncCallback callback, object state)
485 {
486 /// <summary>
487 /// In case, we are invoked asynchroneously this object will keep track of the state
488 /// </summary>
489 AsyncResult<Stream> ar = new AsyncResult<Stream>(callback, state);
490 Util.FireAndForget(RequestHelper, ar, "RestClient.BeginRequest");
491 return ar;
492 }
493
494 public Stream EndRequest(IAsyncResult asyncResult)
495 {
496 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
497
498 // Wait for operation to complete, then return result or
499 // throw exception
500 return ar.EndInvoke();
501 }
502
503 private void RequestHelper(Object asyncResult)
504 {
505 // We know that it's really an AsyncResult<DateTime> object
506 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
507 try
508 {
509 // Perform the operation; if sucessful set the result
510 Stream s = Request(null);
511 ar.SetAsCompleted(s, false);
512 }
513 catch (Exception e)
514 {
515 // If operation fails, set the exception
516 ar.HandleException(e, false);
517 }
518 }
519
520 #endregion Async Invocation
521 }
522
523 internal class SimpleAsyncResult : IAsyncResult
524 {
525 private readonly AsyncCallback m_callback;
526
527 /// <summary>
528 /// Is process completed?
529 /// </summary>
530 /// <remarks>Should really be boolean, but VolatileRead has no boolean method</remarks>
531 private byte m_completed;
532
533 /// <summary>
534 /// Did process complete synchronously?
535 /// </summary>
536 /// <remarks>I have a hard time imagining a scenario where this is the case, again, same issue about
537 /// booleans and VolatileRead as m_completed
538 /// </remarks>
539 private byte m_completedSynchronously;
540
541 private readonly object m_asyncState;
542 private ManualResetEvent m_waitHandle;
543 private Exception m_exception;
544
545 internal SimpleAsyncResult(AsyncCallback cb, object state)
546 {
547 m_callback = cb;
548 m_asyncState = state;
549 m_completed = 0;
550 m_completedSynchronously = 1;
551 }
552
553 #region IAsyncResult Members
554
555 public object AsyncState
556 {
557 get { return m_asyncState; }
558 }
559
560 public WaitHandle AsyncWaitHandle
561 {
562 get
563 {
564 if (m_waitHandle == null)
565 {
566 bool done = IsCompleted;
567 ManualResetEvent mre = new ManualResetEvent(done);
568 if (Interlocked.CompareExchange(ref m_waitHandle, mre, null) != null)
569 {
570 mre.Close();
571 }
572 else
573 {
574 if (!done && IsCompleted)
575 {
576 m_waitHandle.Set();
577 }
578 }
579 }
580
581 return m_waitHandle;
582 }
583 }
584
585
586 public bool CompletedSynchronously
587 {
588 get { return Thread.VolatileRead(ref m_completedSynchronously) == 1; }
589 }
590
591
592 public bool IsCompleted
593 {
594 get { return Thread.VolatileRead(ref m_completed) == 1; }
595 }
596
597 #endregion
598
599 #region class Methods
600
601 internal void SetAsCompleted(bool completedSynchronously)
602 {
603 m_completed = 1;
604 if (completedSynchronously)
605 m_completedSynchronously = 1;
606 else
607 m_completedSynchronously = 0;
608
609 SignalCompletion();
610 }
611
612 internal void HandleException(Exception e, bool completedSynchronously)
613 {
614 m_completed = 1;
615 if (completedSynchronously)
616 m_completedSynchronously = 1;
617 else
618 m_completedSynchronously = 0;
619 m_exception = e;
620
621 SignalCompletion();
622 }
623
624 private void SignalCompletion()
625 {
626 if (m_waitHandle != null) m_waitHandle.Set();
627
628 if (m_callback != null) m_callback(this);
629 }
630
631 public void EndInvoke()
632 {
633 // This method assumes that only 1 thread calls EndInvoke
634 if (!IsCompleted)
635 {
636 // If the operation isn't done, wait for it
637 AsyncWaitHandle.WaitOne();
638 AsyncWaitHandle.Close();
639 m_waitHandle.Close();
640 m_waitHandle = null; // Allow early GC
641 }
642
643 // Operation is done: if an exception occured, throw it
644 if (m_exception != null) throw m_exception;
645 }
646
647 #endregion
648 }
649
650 internal class AsyncResult<T> : SimpleAsyncResult
651 {
652 private T m_result = default(T);
653
654 public AsyncResult(AsyncCallback asyncCallback, Object state) :
655 base(asyncCallback, state)
656 {
657 }
658
659 public void SetAsCompleted(T result, bool completedSynchronously)
660 {
661 // Save the asynchronous operation's result
662 m_result = result;
663
664 // Tell the base class that the operation completed
665 // sucessfully (no exception)
666 base.SetAsCompleted(completedSynchronously);
667 }
668
669 public new T EndInvoke()
670 {
671 base.EndInvoke();
672 return m_result;
673 }
674 }
675
676} \ No newline at end of file