opensim-development – 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.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 namespace OpenSim.Framework.Communications
39 {
40 /// <summary>
41 /// Implementation of a generic REST client
42 /// </summary>
43 /// <remarks>
44 /// This class is a generic implementation of a REST (Representational State Transfer) web service. This
45 /// class is designed to execute both synchronously and asynchronously.
46 ///
47 /// Internally the implementation works as a two stage asynchronous web-client.
48 /// When the request is initiated, RestClient will query asynchronously for for a web-response,
49 /// sleeping until the initial response is returned by the server. Once the initial response is retrieved
50 /// the second stage of asynchronous requests will be triggered, in an attempt to read of the response
51 /// object into a memorystream as a sequence of asynchronous reads.
52 ///
53 /// The asynchronisity of RestClient is designed to move as much processing into the back-ground, allowing
54 /// other threads to execute, while it waits for a response from the web-service. RestClient itself can be
55 /// invoked by the caller in either synchronous mode or asynchronous modes.
56 /// </remarks>
57 public class RestClient
58 {
59 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
60  
61 // private string realuri;
62  
63 #region member variables
64  
65 /// <summary>
66 /// The base Uri of the web-service e.g. http://www.google.com
67 /// </summary>
68 private string _url;
69  
70 /// <summary>
71 /// Path elements of the query
72 /// </summary>
73 private List<string> _pathElements = new List<string>();
74  
75 /// <summary>
76 /// Parameter elements of the query, e.g. min=34
77 /// </summary>
78 private Dictionary<string, string> _parameterElements = new Dictionary<string, string>();
79  
80 /// <summary>
81 /// Request method. E.g. GET, POST, PUT or DELETE
82 /// </summary>
83 private string _method;
84  
85 /// <summary>
86 /// Temporary buffer used to store bytes temporarily as they come in from the server
87 /// </summary>
88 private byte[] _readbuf;
89  
90 /// <summary>
91 /// MemoryStream representing the resultiong resource
92 /// </summary>
93 private Stream _resource;
94  
95 /// <summary>
96 /// WebRequest object, held as a member variable
97 /// </summary>
98 private HttpWebRequest _request;
99  
100 /// <summary>
101 /// WebResponse object, held as a member variable, so we can close it
102 /// </summary>
103 private HttpWebResponse _response;
104  
105 /// <summary>
106 /// This flag will help block the main synchroneous method, in case we run in synchroneous mode
107 /// </summary>
108 //public static ManualResetEvent _allDone = new ManualResetEvent(false);
109  
110 /// <summary>
111 /// Default time out period
112 /// </summary>
113 //private const int DefaultTimeout = 10*1000; // 10 seconds timeout
114  
115 /// <summary>
116 /// Default Buffer size of a block requested from the web-server
117 /// </summary>
118 private const int BufferSize = 4096; // Read blocks of 4 KB.
119  
120  
121 /// <summary>
122 /// if an exception occours during async processing, we need to save it, so it can be
123 /// rethrown on the primary thread;
124 /// </summary>
125 private Exception _asyncException;
126  
127 #endregion member variables
128  
129 #region constructors
130  
131 /// <summary>
132 /// Instantiate a new RestClient
133 /// </summary>
134 /// <param name="url">Web-service to query, e.g. http://osgrid.org:8003</param>
135 public RestClient(string url)
136 {
137 _url = url;
138 _readbuf = new byte[BufferSize];
139 _resource = new MemoryStream();
140 _request = null;
141 _response = null;
142 _lock = new object();
143 }
144  
145 private object _lock;
146  
147 #endregion constructors
148  
149 /// <summary>
150 /// Add a path element to the query, e.g. assets
151 /// </summary>
152 /// <param name="element">path entry</param>
153 public void AddResourcePath(string element)
154 {
155 if (isSlashed(element))
156 _pathElements.Add(element.Substring(0, element.Length - 1));
157 else
158 _pathElements.Add(element);
159 }
160  
161 /// <summary>
162 /// Add a query parameter to the Url
163 /// </summary>
164 /// <param name="name">Name of the parameter, e.g. min</param>
165 /// <param name="value">Value of the parameter, e.g. 42</param>
166 public void AddQueryParameter(string name, string value)
167 {
168 try
169 {
170 _parameterElements.Add(HttpUtility.UrlEncode(name), HttpUtility.UrlEncode(value));
171 }
172 catch (ArgumentException)
173 {
174 m_log.Error("[REST]: Query parameter " + name + " is already added.");
175 }
176 catch (Exception e)
177 {
178 m_log.Error("[REST]: An exception was raised adding query parameter to dictionary. Exception: {0}",e);
179 }
180 }
181  
182 /// <summary>
183 /// Add a query parameter to the Url
184 /// </summary>
185 /// <param name="name">Name of the parameter, e.g. min</param>
186 public void AddQueryParameter(string name)
187 {
188 try
189 {
190 _parameterElements.Add(HttpUtility.UrlEncode(name), null);
191 }
192 catch (ArgumentException)
193 {
194 m_log.Error("[REST]: Query parameter " + name + " is already added.");
195 }
196 catch (Exception e)
197 {
198 m_log.Error("[REST]: An exception was raised adding query parameter to dictionary. Exception: {0}",e);
199 }
200 }
201  
202 /// <summary>
203 /// Web-Request method, e.g. GET, PUT, POST, DELETE
204 /// </summary>
205 public string RequestMethod
206 {
207 get { return _method; }
208 set { _method = value; }
209 }
210  
211 /// <summary>
212 /// True if string contains a trailing slash '/'
213 /// </summary>
214 /// <param name="s">string to be examined</param>
215 /// <returns>true if slash is present</returns>
216 private static bool isSlashed(string s)
217 {
218 return s.Substring(s.Length - 1, 1) == "/";
219 }
220  
221 /// <summary>
222 /// Build a Uri based on the initial Url, path elements and parameters
223 /// </summary>
224 /// <returns>fully constructed Uri</returns>
225 private Uri buildUri()
226 {
227 StringBuilder sb = new StringBuilder();
228 sb.Append(_url);
229  
230 foreach (string e in _pathElements)
231 {
232 sb.Append("/");
233 sb.Append(e);
234 }
235  
236 bool firstElement = true;
237 foreach (KeyValuePair<string, string> kv in _parameterElements)
238 {
239 if (firstElement)
240 {
241 sb.Append("?");
242 firstElement = false;
243 }
244 else
245 sb.Append("&");
246  
247 sb.Append(kv.Key);
248 if (!string.IsNullOrEmpty(kv.Value))
249 {
250 sb.Append("=");
251 sb.Append(kv.Value);
252 }
253 }
254 // realuri = sb.ToString();
255 //m_log.InfoFormat("[REST CLIENT]: RestURL: {0}", realuri);
256 return new Uri(sb.ToString());
257 }
258  
259 #region Async communications with server
260  
261 /// <summary>
262 /// Async method, invoked when a block of data has been received from the service
263 /// </summary>
264 /// <param name="ar"></param>
265 private void StreamIsReadyDelegate(IAsyncResult ar)
266 {
267 try
268 {
269 Stream s = (Stream) ar.AsyncState;
270 int read = s.EndRead(ar);
271  
272 if (read > 0)
273 {
274 _resource.Write(_readbuf, 0, read);
275 // IAsyncResult asynchronousResult =
276 // s.BeginRead(_readbuf, 0, BufferSize, new AsyncCallback(StreamIsReadyDelegate), s);
277 s.BeginRead(_readbuf, 0, BufferSize, new AsyncCallback(StreamIsReadyDelegate), s);
278  
279 // TODO! Implement timeout, without killing the server
280 //ThreadPool.RegisterWaitForSingleObject(asynchronousResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
281 }
282 else
283 {
284 s.Close();
285 //_allDone.Set();
286 }
287 }
288 catch (Exception e)
289 {
290 //_allDone.Set();
291 _asyncException = e;
292 }
293 }
294  
295 #endregion Async communications with server
296  
297 /// <summary>
298 /// Perform a synchronous request
299 /// </summary>
300 public Stream Request()
301 {
302 lock (_lock)
303 {
304 _request = (HttpWebRequest) WebRequest.Create(buildUri());
305 _request.KeepAlive = false;
306 _request.ContentType = "application/xml";
307 _request.Timeout = 200000;
308 _request.Method = RequestMethod;
309 _asyncException = null;
310  
311 // IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request);
312 try
313 {
314 _response = (HttpWebResponse) _request.GetResponse();
315 }
316 catch (WebException e)
317 {
318 HttpWebResponse errorResponse = e.Response as HttpWebResponse;
319 if (null != errorResponse && HttpStatusCode.NotFound == errorResponse.StatusCode)
320 {
321 m_log.Warn("[REST CLIENT] Resource not found (404)");
322 }
323 else
324 {
325 m_log.Error("[REST CLIENT] Error fetching resource from server " + _request.Address.ToString());
326 m_log.Debug(e.ToString());
327 }
328  
329 return null;
330 }
331  
332 Stream src = _response.GetResponseStream();
333 int length = src.Read(_readbuf, 0, BufferSize);
334 while (length > 0)
335 {
336 _resource.Write(_readbuf, 0, length);
337 length = src.Read(_readbuf, 0, BufferSize);
338 }
339  
340  
341 // TODO! Implement timeout, without killing the server
342 // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
343 //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
344  
345 // _allDone.WaitOne();
346 if (_response != null)
347 _response.Close();
348 if (_asyncException != null)
349 throw _asyncException;
350  
351 if (_resource != null)
352 {
353 _resource.Flush();
354 _resource.Seek(0, SeekOrigin.Begin);
355 }
356  
357 return _resource;
358 }
359 }
360  
361 public Stream Request(Stream src)
362 {
363 _request = (HttpWebRequest) WebRequest.Create(buildUri());
364 _request.KeepAlive = false;
365 _request.ContentType = "application/xml";
366 _request.Timeout = 900000;
367 _request.Method = RequestMethod;
368 _asyncException = null;
369 _request.ContentLength = src.Length;
370  
371 m_log.InfoFormat("[REST]: Request Length {0}", _request.ContentLength);
372 m_log.InfoFormat("[REST]: Sending Web Request {0}", buildUri());
373 src.Seek(0, SeekOrigin.Begin);
374 m_log.Info("[REST]: Seek is ok");
375 Stream dst = _request.GetRequestStream();
376 m_log.Info("[REST]: GetRequestStream is ok");
377  
378 byte[] buf = new byte[1024];
379 int length = src.Read(buf, 0, 1024);
380 m_log.Info("[REST]: First Read is ok");
381 while (length > 0)
382 {
383 dst.Write(buf, 0, length);
384 length = src.Read(buf, 0, 1024);
385 }
386  
387 _response = (HttpWebResponse) _request.GetResponse();
388  
389 // IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request);
390  
391 // TODO! Implement timeout, without killing the server
392 // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
393 //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true);
394  
395 return null;
396 }
397  
398 #region Async Invocation
399  
400 public IAsyncResult BeginRequest(AsyncCallback callback, object state)
401 {
402 /// <summary>
403 /// In case, we are invoked asynchroneously this object will keep track of the state
404 /// </summary>
405 AsyncResult<Stream> ar = new AsyncResult<Stream>(callback, state);
406 Util.FireAndForget(RequestHelper, ar);
407 return ar;
408 }
409  
410 public Stream EndRequest(IAsyncResult asyncResult)
411 {
412 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
413  
414 // Wait for operation to complete, then return result or
415 // throw exception
416 return ar.EndInvoke();
417 }
418  
419 private void RequestHelper(Object asyncResult)
420 {
421 // We know that it's really an AsyncResult<DateTime> object
422 AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult;
423 try
424 {
425 // Perform the operation; if sucessful set the result
426 Stream s = Request();
427 ar.SetAsCompleted(s, false);
428 }
429 catch (Exception e)
430 {
431 // If operation fails, set the exception
432 ar.HandleException(e, false);
433 }
434 }
435  
436 #endregion Async Invocation
437 }
438 }