OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
RestClient.cs
Go to the documentation of this file.
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.Generic;
30 using System.IO;
31 using System.Net;
32 using System.Reflection;
33 using System.Text;
34 using System.Threading;
35 using System.Web;
36 using log4net;
37 
38 using OpenSim.Framework.ServiceAuth;
39 
40 namespace OpenSim.Framework
41 {
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 
70  private string _url;
71 
75  private List<string> _pathElements = new List<string>();
76 
80  private Dictionary<string, string> _parameterElements = new Dictionary<string, string>();
81 
85  private string _method;
86 
90  private byte[] _readbuf;
91 
95  private Stream _resource;
96 
100  private HttpWebRequest _request;
101 
105  private HttpWebResponse _response;
106 
110  //public static ManualResetEvent _allDone = new ManualResetEvent(false);
111 
115  //private const int DefaultTimeout = 10*1000; // 10 seconds timeout
116 
120  private const int BufferSize = 4096; // Read blocks of 4 KB.
121 
122 
127  private Exception _asyncException;
128 
129  #endregion member variables
130 
131  #region constructors
132 
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 
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 
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 
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 
234  public string RequestMethod
235  {
236  get { return _method; }
237  set { _method = value; }
238  }
239 
245  private static bool isSlashed(string s)
246  {
247  return s.Substring(s.Length - 1, 1) == "/";
248  }
249 
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 
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 
329  public Stream Request()
330  {
331  return Request(null);
332  }
333 
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  {
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 
536  private byte m_completed;
537 
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 }
Stream Request()
Perform a synchronous request
Definition: RestClient.cs:329
void AddResourcePath(string element)
Add a path element to the query, e.g. assets
Definition: RestClient.cs:182
IAsyncResult BeginRequest(AsyncCallback callback, object state)
Definition: RestClient.cs:489
virtual void Dispose(bool disposing)
Definition: RestClient.cs:162
Stream Request(IServiceAuth auth)
Perform a synchronous request
Definition: RestClient.cs:337
delegate void RequestMethod(UUID requestID, Hashtable request)
void AddQueryParameter(string name, string value)
Add a query parameter to the Url
Definition: RestClient.cs:195
Stream Request(Stream src, IServiceAuth auth)
Definition: RestClient.cs:411
RestClient(string url)
Instantiate a new RestClient
Definition: RestClient.cs:137
void AddQueryParameter(string name)
Add a query parameter to the Url
Definition: RestClient.cs:215
Implementation of a generic REST client
Definition: RestClient.cs:59
Stream EndRequest(IAsyncResult asyncResult)
Definition: RestClient.cs:499