diff options
Diffstat (limited to 'OpenSim/Framework/Communications/RestClient.cs')
-rw-r--r-- | OpenSim/Framework/Communications/RestClient.cs | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/OpenSim/Framework/Communications/RestClient.cs b/OpenSim/Framework/Communications/RestClient.cs new file mode 100644 index 0000000..91284e8 --- /dev/null +++ b/OpenSim/Framework/Communications/RestClient.cs | |||
@@ -0,0 +1,368 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using System.IO; | ||
4 | using System.Net; | ||
5 | using System.Reflection; | ||
6 | using System.Text; | ||
7 | using System.Threading; | ||
8 | using System.Web; | ||
9 | using log4net; | ||
10 | |||
11 | namespace OpenSim.Framework.Communications | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Implementation of a generic REST client | ||
15 | /// </summary> | ||
16 | /// <remarks> | ||
17 | /// This class is a generic implementation of a REST (Representational State Transfer) web service. This | ||
18 | /// class is designed to execute both synchroneously and asynchroneously. | ||
19 | /// | ||
20 | /// Internally the implementation works as a two stage asynchroneous web-client. | ||
21 | /// When the request is initiated, RestClient will query asynchroneously for for a web-response, | ||
22 | /// sleeping until the initial response is returned by the server. Once the initial response is retrieved | ||
23 | /// the second stage of asynchroneous requests will be triggered, in an attempt to read of the response | ||
24 | /// object into a memorystream as a sequence of asynchroneous reads. | ||
25 | /// | ||
26 | /// The asynchronisity of RestClient is designed to move as much processing into the back-ground, allowing | ||
27 | /// other threads to execute, while it waits for a response from the web-service. RestClient it self, can be | ||
28 | /// invoked by the caller in either synchroneous mode or asynchroneous mode. | ||
29 | /// </remarks> | ||
30 | public class RestClient | ||
31 | { | ||
32 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
33 | |||
34 | private string realuri; | ||
35 | |||
36 | #region member variables | ||
37 | |||
38 | /// <summary> | ||
39 | /// The base Uri of the web-service e.g. http://www.google.com | ||
40 | /// </summary> | ||
41 | private string _url; | ||
42 | |||
43 | /// <summary> | ||
44 | /// Path elements of the query | ||
45 | /// </summary> | ||
46 | private List<string> _pathElements = new List<string>(); | ||
47 | |||
48 | /// <summary> | ||
49 | /// Parameter elements of the query, e.g. min=34 | ||
50 | /// </summary> | ||
51 | private Dictionary<string, string> _parameterElements = new Dictionary<string, string>(); | ||
52 | |||
53 | /// <summary> | ||
54 | /// Request method. E.g. GET, POST, PUT or DELETE | ||
55 | /// </summary> | ||
56 | private string _method; | ||
57 | |||
58 | /// <summary> | ||
59 | /// Temporary buffer used to store bytes temporarily as they come in from the server | ||
60 | /// </summary> | ||
61 | private byte[] _readbuf; | ||
62 | |||
63 | /// <summary> | ||
64 | /// MemoryStream representing the resultiong resource | ||
65 | /// </summary> | ||
66 | private Stream _resource; | ||
67 | |||
68 | /// <summary> | ||
69 | /// WebRequest object, held as a member variable | ||
70 | /// </summary> | ||
71 | private HttpWebRequest _request; | ||
72 | |||
73 | /// <summary> | ||
74 | /// WebResponse object, held as a member variable, so we can close it | ||
75 | /// </summary> | ||
76 | private HttpWebResponse _response; | ||
77 | |||
78 | /// <summary> | ||
79 | /// This flag will help block the main synchroneous method, in case we run in synchroneous mode | ||
80 | /// </summary> | ||
81 | public static ManualResetEvent _allDone = new ManualResetEvent(false); | ||
82 | |||
83 | /// <summary> | ||
84 | /// Default time out period | ||
85 | /// </summary> | ||
86 | private const int DefaultTimeout = 10*1000; // 10 seconds timeout | ||
87 | |||
88 | /// <summary> | ||
89 | /// Default Buffer size of a block requested from the web-server | ||
90 | /// </summary> | ||
91 | private const int BufferSize = 4096; // Read blocks of 4 KB. | ||
92 | |||
93 | |||
94 | /// <summary> | ||
95 | /// if an exception occours during async processing, we need to save it, so it can be | ||
96 | /// rethrown on the primary thread; | ||
97 | /// </summary> | ||
98 | private Exception _asyncException; | ||
99 | |||
100 | #endregion member variables | ||
101 | |||
102 | #region constructors | ||
103 | |||
104 | /// <summary> | ||
105 | /// Instantiate a new RestClient | ||
106 | /// </summary> | ||
107 | /// <param name="url">Web-service to query, e.g. http://osgrid.org:8003</param> | ||
108 | public RestClient(string url) | ||
109 | { | ||
110 | _url = url; | ||
111 | _readbuf = new byte[BufferSize]; | ||
112 | _resource = new MemoryStream(); | ||
113 | _request = null; | ||
114 | _response = null; | ||
115 | _lock = new object(); | ||
116 | } | ||
117 | |||
118 | private object _lock; | ||
119 | |||
120 | #endregion constructors | ||
121 | |||
122 | /// <summary> | ||
123 | /// Add a path element to the query, e.g. assets | ||
124 | /// </summary> | ||
125 | /// <param name="element">path entry</param> | ||
126 | public void AddResourcePath(string element) | ||
127 | { | ||
128 | if (isSlashed(element)) | ||
129 | _pathElements.Add(element.Substring(0, element.Length - 1)); | ||
130 | else | ||
131 | _pathElements.Add(element); | ||
132 | } | ||
133 | |||
134 | /// <summary> | ||
135 | /// Add a query parameter to the Url | ||
136 | /// </summary> | ||
137 | /// <param name="name">Name of the parameter, e.g. min</param> | ||
138 | /// <param name="value">Value of the parameter, e.g. 42</param> | ||
139 | public void AddQueryParameter(string name, string value) | ||
140 | { | ||
141 | _parameterElements.Add(HttpUtility.UrlEncode(name), HttpUtility.UrlEncode(value)); | ||
142 | } | ||
143 | |||
144 | /// <summary> | ||
145 | /// Add a query parameter to the Url | ||
146 | /// </summary> | ||
147 | /// <param name="name">Name of the parameter, e.g. min</param> | ||
148 | public void AddQueryParameter(string name) | ||
149 | { | ||
150 | _parameterElements.Add(HttpUtility.UrlEncode(name), null); | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Web-Request method, e.g. GET, PUT, POST, DELETE | ||
155 | /// </summary> | ||
156 | public string RequestMethod | ||
157 | { | ||
158 | get { return _method; } | ||
159 | set { _method = value; } | ||
160 | } | ||
161 | |||
162 | /// <summary> | ||
163 | /// True if string contains a trailing slash '/' | ||
164 | /// </summary> | ||
165 | /// <param name="s">string to be examined</param> | ||
166 | /// <returns>true if slash is present</returns> | ||
167 | private bool isSlashed(string s) | ||
168 | { | ||
169 | return s.Substring(s.Length - 1, 1) == "/"; | ||
170 | } | ||
171 | |||
172 | /// <summary> | ||
173 | /// Build a Uri based on the initial Url, path elements and parameters | ||
174 | /// </summary> | ||
175 | /// <returns>fully constructed Uri</returns> | ||
176 | private Uri buildUri() | ||
177 | { | ||
178 | StringBuilder sb = new StringBuilder(); | ||
179 | sb.Append(_url); | ||
180 | |||
181 | foreach (string e in _pathElements) | ||
182 | { | ||
183 | sb.Append("/"); | ||
184 | sb.Append(e); | ||
185 | } | ||
186 | |||
187 | bool firstElement = true; | ||
188 | foreach (KeyValuePair<string, string> kv in _parameterElements) | ||
189 | { | ||
190 | if (firstElement) | ||
191 | { | ||
192 | sb.Append("?"); | ||
193 | firstElement = false; | ||
194 | } | ||
195 | else | ||
196 | sb.Append("&"); | ||
197 | |||
198 | sb.Append(kv.Key); | ||
199 | if (kv.Value != null && kv.Value.Length != 0) | ||
200 | { | ||
201 | sb.Append("="); | ||
202 | sb.Append(kv.Value); | ||
203 | } | ||
204 | } | ||
205 | realuri = sb.ToString(); | ||
206 | //m_log.InfoFormat("[REST CLIENT]: RestURL: {0}", realuri); | ||
207 | return new Uri(sb.ToString()); | ||
208 | } | ||
209 | |||
210 | #region Async communications with server | ||
211 | |||
212 | /// <summary> | ||
213 | /// Async method, invoked when a block of data has been received from the service | ||
214 | /// </summary> | ||
215 | /// <param name="ar"></param> | ||
216 | private void StreamIsReadyDelegate(IAsyncResult ar) | ||
217 | { | ||
218 | try | ||
219 | { | ||
220 | Stream s = (Stream) ar.AsyncState; | ||
221 | int read = s.EndRead(ar); | ||
222 | |||
223 | if (read > 0) | ||
224 | { | ||
225 | _resource.Write(_readbuf, 0, read); | ||
226 | IAsyncResult asynchronousResult = | ||
227 | s.BeginRead(_readbuf, 0, BufferSize, new AsyncCallback(StreamIsReadyDelegate), s); | ||
228 | |||
229 | // TODO! Implement timeout, without killing the server | ||
230 | //ThreadPool.RegisterWaitForSingleObject(asynchronousResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true); | ||
231 | } | ||
232 | else | ||
233 | { | ||
234 | s.Close(); | ||
235 | _allDone.Set(); | ||
236 | } | ||
237 | } | ||
238 | catch (Exception e) | ||
239 | { | ||
240 | _allDone.Set(); | ||
241 | _asyncException = e; | ||
242 | } | ||
243 | } | ||
244 | |||
245 | #endregion Async communications with server | ||
246 | |||
247 | /// <summary> | ||
248 | /// Perform synchroneous request | ||
249 | /// </summary> | ||
250 | public Stream Request() | ||
251 | { | ||
252 | lock (_lock) | ||
253 | { | ||
254 | _request = (HttpWebRequest) WebRequest.Create(buildUri()); | ||
255 | _request.KeepAlive = false; | ||
256 | _request.ContentType = "application/xml"; | ||
257 | _request.Timeout = 200000; | ||
258 | _asyncException = null; | ||
259 | |||
260 | // IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request); | ||
261 | _response = (HttpWebResponse) _request.GetResponse(); | ||
262 | Stream src = _response.GetResponseStream(); | ||
263 | int length = src.Read(_readbuf, 0, BufferSize); | ||
264 | while (length > 0) | ||
265 | { | ||
266 | _resource.Write(_readbuf, 0, length); | ||
267 | length = src.Read(_readbuf, 0, BufferSize); | ||
268 | } | ||
269 | |||
270 | |||
271 | // TODO! Implement timeout, without killing the server | ||
272 | // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted | ||
273 | //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true); | ||
274 | |||
275 | // _allDone.WaitOne(); | ||
276 | if (_response != null) | ||
277 | _response.Close(); | ||
278 | if (_asyncException != null) | ||
279 | throw _asyncException; | ||
280 | |||
281 | if (_resource != null) | ||
282 | { | ||
283 | _resource.Flush(); | ||
284 | _resource.Seek(0, SeekOrigin.Begin); | ||
285 | } | ||
286 | |||
287 | return _resource; | ||
288 | } | ||
289 | } | ||
290 | |||
291 | public Stream Request(Stream src) | ||
292 | { | ||
293 | _request = (HttpWebRequest) WebRequest.Create(buildUri()); | ||
294 | _request.KeepAlive = false; | ||
295 | _request.ContentType = "application/xml"; | ||
296 | _request.Timeout = 900000; | ||
297 | _request.Method = RequestMethod; | ||
298 | _asyncException = null; | ||
299 | _request.ContentLength = src.Length; | ||
300 | |||
301 | m_log.InfoFormat("[REST]: Request Length {0}", _request.ContentLength); | ||
302 | m_log.InfoFormat("[REST]: Sending Web Request {0}", buildUri()); | ||
303 | src.Seek(0, SeekOrigin.Begin); | ||
304 | m_log.Info("[REST]: Seek is ok"); | ||
305 | Stream dst = _request.GetRequestStream(); | ||
306 | m_log.Info("[REST]: GetRequestStream is ok"); | ||
307 | |||
308 | byte[] buf = new byte[1024]; | ||
309 | int length = src.Read(buf, 0, 1024); | ||
310 | m_log.Info("[REST]: First Read is ok"); | ||
311 | while (length > 0) | ||
312 | { | ||
313 | dst.Write(buf, 0, length); | ||
314 | length = src.Read(buf, 0, 1024); | ||
315 | } | ||
316 | |||
317 | _response = (HttpWebResponse) _request.GetResponse(); | ||
318 | |||
319 | // IAsyncResult responseAsyncResult = _request.BeginGetResponse(new AsyncCallback(ResponseIsReadyDelegate), _request); | ||
320 | |||
321 | // TODO! Implement timeout, without killing the server | ||
322 | // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted | ||
323 | //ThreadPool.RegisterWaitForSingleObject(responseAsyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), _request, DefaultTimeout, true); | ||
324 | |||
325 | return null; | ||
326 | } | ||
327 | |||
328 | #region Async Invocation | ||
329 | |||
330 | public IAsyncResult BeginRequest(AsyncCallback callback, object state) | ||
331 | { | ||
332 | /// <summary> | ||
333 | /// In case, we are invoked asynchroneously this object will keep track of the state | ||
334 | /// </summary> | ||
335 | AsyncResult<Stream> ar = new AsyncResult<Stream>(callback, state); | ||
336 | ThreadPool.QueueUserWorkItem(RequestHelper, ar); | ||
337 | return ar; | ||
338 | } | ||
339 | |||
340 | public Stream EndRequest(IAsyncResult asyncResult) | ||
341 | { | ||
342 | AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult; | ||
343 | |||
344 | // Wait for operation to complete, then return result or | ||
345 | // throw exception | ||
346 | return ar.EndInvoke(); | ||
347 | } | ||
348 | |||
349 | private void RequestHelper(Object asyncResult) | ||
350 | { | ||
351 | // We know that it's really an AsyncResult<DateTime> object | ||
352 | AsyncResult<Stream> ar = (AsyncResult<Stream>) asyncResult; | ||
353 | try | ||
354 | { | ||
355 | // Perform the operation; if sucessful set the result | ||
356 | Stream s = Request(); | ||
357 | ar.SetAsCompleted(s, false); | ||
358 | } | ||
359 | catch (Exception e) | ||
360 | { | ||
361 | // If operation fails, set the exception | ||
362 | ar.HandleException(e, false); | ||
363 | } | ||
364 | } | ||
365 | |||
366 | #endregion Async Invocation | ||
367 | } | ||
368 | } \ No newline at end of file | ||