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.cs681
1 files changed, 681 insertions, 0 deletions
diff --git a/OpenSim/Framework/RestClient.cs b/OpenSim/Framework/RestClient.cs
new file mode 100644
index 0000000..ca19392
--- /dev/null
+++ b/OpenSim/Framework/RestClient.cs
@@ -0,0 +1,681 @@
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 return null;
392 }
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 = 90000;
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 using (Stream dst = _request.GetRequestStream())
432 {
433 m_log.Info("[REST]: GetRequestStream is ok");
434
435 byte[] buf = new byte[1024];
436 int length = src.Read(buf, 0, 1024);
437 m_log.Info("[REST]: First Read is ok");
438 while (length > 0)
439 {
440 dst.Write(buf, 0, length);
441 length = src.Read(buf, 0, 1024);
442 }
443 }
444
445 try
446 {
447 _response = (HttpWebResponse)_request.GetResponse();
448 }
449 catch (WebException e)
450 {
451 m_log.WarnFormat("[REST]: Request {0} {1} failed with status {2} and message {3}",
452 RequestMethod, _request.RequestUri, e.Status, e.Message);
453 return null;
454 }
455 catch (Exception e)
456 {
457 m_log.WarnFormat(
458 "[REST]: Request {0} {1} failed with exception {2} {3}",
459 RequestMethod, _request.RequestUri, e.Message, e.StackTrace);
460 return null;
461 }
462
463 if (WebUtil.DebugLevel >= 5)
464 {
465 using (Stream responseStream = _response.GetResponseStream())
466 {
467 using (StreamReader reader = new StreamReader(responseStream))
468 {
469 string responseStr = reader.ReadToEnd();
470 WebUtil.LogResponseDetail(reqnum, responseStr);
471 }
472 }
473 }
474
475 if (_response != null)
476 _response.Close();
477
478// IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request);
479
480 // TODO! Implement timeout, without killing the server
481 // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
482 //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
483
484 return null;
485 }
486
487 #region Async Invocation
488
489 public IAsyncResult BeginRequest(AsyncCallback callback, object state)
490 {
491 /// <summary>
492 /// In case, we are invoked asynchroneously this object will keep track of the state
493 /// </summary>
494 AsyncResult<Stream> ar = new AsyncResult<Stream>(callback, state);
495 Util.FireAndForget(RequestHelper, ar, "RestClient.BeginRequest");
496 return ar;
497 }
498
499 public Stream EndRequest(IAsyncResult asyncResult)
500 {
501 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
502
503 // Wait for operation to complete, then return result or
504 // throw exception
505 return ar.EndInvoke();
506 }
507
508 private void RequestHelper(Object asyncResult)
509 {
510 // We know that it's really an AsyncResult<DateTime> object
511 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
512 try
513 {
514 // Perform the operation; if sucessful set the result
515 Stream s = Request(null);
516 ar.SetAsCompleted(s, false);
517 }
518 catch (Exception e)
519 {
520 // If operation fails, set the exception
521 ar.HandleException(e, false);
522 }
523 }
524
525 #endregion Async Invocation
526 }
527
528 internal class SimpleAsyncResult : IAsyncResult
529 {
530 private readonly AsyncCallback m_callback;
531
532 /// <summary>
533 /// Is process completed?
534 /// </summary>
535 /// <remarks>Should really be boolean, but VolatileRead has no boolean method</remarks>
536 private byte m_completed;
537
538 /// <summary>
539 /// Did process complete synchronously?
540 /// </summary>
541 /// <remarks>I have a hard time imagining a scenario where this is the case, again, same issue about
542 /// booleans and VolatileRead as m_completed
543 /// </remarks>
544 private byte m_completedSynchronously;
545
546 private readonly object m_asyncState;
547 private ManualResetEvent m_waitHandle;
548 private Exception m_exception;
549
550 internal SimpleAsyncResult(AsyncCallback cb, object state)
551 {
552 m_callback = cb;
553 m_asyncState = state;
554 m_completed = 0;
555 m_completedSynchronously = 1;
556 }
557
558 #region IAsyncResult Members
559
560 public object AsyncState
561 {
562 get { return m_asyncState; }
563 }
564
565 public WaitHandle AsyncWaitHandle
566 {
567 get
568 {
569 if (m_waitHandle == null)
570 {
571 bool done = IsCompleted;
572 ManualResetEvent mre = new ManualResetEvent(done);
573 if (Interlocked.CompareExchange(ref m_waitHandle, mre, null) != null)
574 {
575 mre.Close();
576 }
577 else
578 {
579 if (!done && IsCompleted)
580 {
581 m_waitHandle.Set();
582 }
583 }
584 }
585
586 return m_waitHandle;
587 }
588 }
589
590
591 public bool CompletedSynchronously
592 {
593 get { return Thread.VolatileRead(ref m_completedSynchronously) == 1; }
594 }
595
596
597 public bool IsCompleted
598 {
599 get { return Thread.VolatileRead(ref m_completed) == 1; }
600 }
601
602 #endregion
603
604 #region class Methods
605
606 internal void SetAsCompleted(bool completedSynchronously)
607 {
608 m_completed = 1;
609 if (completedSynchronously)
610 m_completedSynchronously = 1;
611 else
612 m_completedSynchronously = 0;
613
614 SignalCompletion();
615 }
616
617 internal void HandleException(Exception e, bool completedSynchronously)
618 {
619 m_completed = 1;
620 if (completedSynchronously)
621 m_completedSynchronously = 1;
622 else
623 m_completedSynchronously = 0;
624 m_exception = e;
625
626 SignalCompletion();
627 }
628
629 private void SignalCompletion()
630 {
631 if (m_waitHandle != null) m_waitHandle.Set();
632
633 if (m_callback != null) m_callback(this);
634 }
635
636 public void EndInvoke()
637 {
638 // This method assumes that only 1 thread calls EndInvoke
639 if (!IsCompleted)
640 {
641 // If the operation isn't done, wait for it
642 AsyncWaitHandle.WaitOne();
643 AsyncWaitHandle.Close();
644 m_waitHandle.Close();
645 m_waitHandle = null; // Allow early GC
646 }
647
648 // Operation is done: if an exception occured, throw it
649 if (m_exception != null) throw m_exception;
650 }
651
652 #endregion
653 }
654
655 internal class AsyncResult<T> : SimpleAsyncResult
656 {
657 private T m_result = default(T);
658
659 public AsyncResult(AsyncCallback asyncCallback, Object state) :
660 base(asyncCallback, state)
661 {
662 }
663
664 public void SetAsCompleted(T result, bool completedSynchronously)
665 {
666 // Save the asynchronous operation's result
667 m_result = result;
668
669 // Tell the base class that the operation completed
670 // sucessfully (no exception)
671 base.SetAsCompleted(completedSynchronously);
672 }
673
674 public new T EndInvoke()
675 {
676 base.EndInvoke();
677 return m_result;
678 }
679 }
680
681}