aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs662
1 files changed, 0 insertions, 662 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
deleted file mode 100644
index 072bd6f..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
+++ /dev/null
@@ -1,662 +0,0 @@
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.Reflection;
31using OpenSim.Framework.Servers;
32using OpenSim.Framework.Servers.HttpServer;
33
34namespace OpenSim.ApplicationPlugins.Rest.Inventory
35{
36 /// <remarks>
37 /// The class signature reveals the roles that RestHandler plays.
38 ///
39 /// [1] It is a sub-class of RestPlugin. It inherits and extends
40 /// the functionality of this class, constraining it to the
41 /// specific needs of this REST implementation. This relates
42 /// to the plug-in mechanism supported by OpenSim, the specifics
43 /// of which are mostly hidden by RestPlugin.
44 /// [2] IRestHandler describes the interface that this class
45 /// exports to service implementations. This is the services
46 /// management interface.
47 /// [3] IHttpAgentHandler describes the interface that is exported
48 /// to the BaseHttpServer in support of this particular HTTP
49 /// processing model. This is the request interface of the
50 /// handler.
51 /// </remarks>
52
53 public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
54 {
55 // Handler tables: both stream and REST are supported. The path handlers and their
56 // respective allocators are stored in separate tables.
57
58 internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
59 internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
60 internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
61
62 #region local static state
63
64 private static bool handlersLoaded = false;
65 private static List<Type> classes = new List<Type>();
66 private static List<IRest> handlers = new List<IRest>();
67 private static Type[] parms = new Type[0];
68 private static Object[] args = new Object[0];
69
70 /// <summary>
71 /// This static initializer scans the ASSEMBLY for classes that
72 /// export the IRest interface and builds a list of them. These
73 /// are later activated by the handler. To add a new handler it
74 /// is only necessary to create a new services class that implements
75 /// the IRest interface, and recompile the handler. This gives
76 /// all of the build-time flexibility of a modular approach
77 /// while not introducing yet-another module loader. Note that
78 /// multiple assembles can still be built, each with its own set
79 /// of handlers. Examples of services classes are RestInventoryServices
80 /// and RestSkeleton.
81 /// </summary>
82
83 static RestHandler()
84 {
85 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
86
87 foreach (Module m in mods)
88 {
89 Type[] types = m.GetTypes();
90 foreach (Type t in types)
91 {
92 try
93 {
94 if (t.GetInterface("IRest") != null)
95 {
96 classes.Add(t);
97 }
98 }
99 catch (Exception)
100 {
101 Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t);
102 Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t);
103 }
104 }
105 }
106 }
107
108 #endregion local static state
109
110 #region local instance state
111
112 /// <summary>
113 /// This routine loads all of the handlers discovered during
114 /// instance initialization.
115 /// A table of all loaded and successfully constructed handlers
116 /// is built, and this table is then used by the constructor to
117 /// initialize each of the handlers in turn.
118 /// NOTE: The loading process does not automatically imply that
119 /// the handler has registered any kind of an interface, that
120 /// may be (optionally) done by the handler either during
121 /// construction, or during initialization.
122 ///
123 /// I was not able to make this code work within a constructor
124 /// so it is isolated within this method.
125 /// </summary>
126
127 private void LoadHandlers()
128 {
129 lock (handlers)
130 {
131 if (!handlersLoaded)
132 {
133 ConstructorInfo ci;
134 Object ht;
135
136 foreach (Type t in classes)
137 {
138 try
139 {
140 ci = t.GetConstructor(parms);
141 ht = ci.Invoke(args);
142 handlers.Add((IRest)ht);
143 }
144 catch (Exception e)
145 {
146 Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message);
147 }
148 }
149 handlersLoaded = true;
150 }
151 }
152 }
153
154 #endregion local instance state
155
156 #region overriding properties
157
158 // These properties override definitions
159 // in the base class.
160
161 // Name is used to differentiate the message header.
162
163 public override string Name
164 {
165 get { return "HANDLER"; }
166 }
167
168 // Used to partition the .ini configuration space.
169
170 public override string ConfigName
171 {
172 get { return "RestHandler"; }
173 }
174
175 // We have to rename these because we want
176 // to be able to share the values with other
177 // classes in our assembly and the base
178 // names are protected.
179
180 public string MsgId
181 {
182 get { return base.MsgID; }
183 }
184
185 public string RequestId
186 {
187 get { return base.RequestID; }
188 }
189
190 #endregion overriding properties
191
192 #region overriding methods
193
194 /// <summary>
195 /// This method is called by OpenSimMain immediately after loading the
196 /// plugin and after basic server setup, but before running any server commands.
197 /// </summary>
198 /// <remarks>
199 /// Note that entries MUST be added to the active configuration files before
200 /// the plugin can be enabled.
201 /// </remarks>
202
203 public override void Initialise(OpenSimBase openSim)
204 {
205 try
206 {
207 // This plugin will only be enabled if the broader
208 // REST plugin mechanism is enabled.
209
210 //Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
211
212 base.Initialise(openSim);
213
214 // IsEnabled is implemented by the base class and
215 // reflects an overall RestPlugin status
216
217 if (!IsEnabled)
218 {
219 //Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
220 return;
221 }
222
223 Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name);
224 Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName);
225
226 // These are stored in static variables to make
227 // them easy to reach from anywhere in the assembly.
228
229 Rest.main = openSim;
230 if (Rest.main == null)
231 throw new Exception("OpenSim base pointer is null");
232
233 Rest.Plugin = this;
234 Rest.Config = Config;
235 Rest.Prefix = Prefix;
236 Rest.GodKey = GodKey;
237 Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
238 Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
239 Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
240 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
241 Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
242 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
243 Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
244 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
245 Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
246
247 // Note: Odd spacing is required in the following strings
248
249 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
250 (Rest.Authenticate ? "" : "not "));
251
252 Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
253 (Rest.Secure ? "" : "not "));
254
255 Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
256 (Rest.ExtendedEscape ? "" : "not "));
257
258 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
259 (Rest.DumpAsset ? "" : "not "));
260
261 // The supplied prefix MUST be absolute
262
263 if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
264 {
265 Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix);
266 Rest.Log.InfoFormat("{0} Prefix changed to </{1}>", MsgId, Rest.Prefix);
267 Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix);
268 }
269
270 // If data dumping is requested, report on the chosen line
271 // length.
272
273 if (Rest.DumpAsset)
274 {
275 Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize);
276 }
277
278 // Load all of the handlers present in the
279 // assembly
280
281 // In principle, as we're an application plug-in,
282 // most of what needs to be done could be done using
283 // static resources, however the Open Sim plug-in
284 // model makes this an instance, so that's what we
285 // need to be.
286 // There is only one Communications manager per
287 // server, and by inference, only one each of the
288 // user, asset, and inventory servers. So we can cache
289 // those using a static initializer.
290 // We move all of this processing off to another
291 // services class to minimize overlap between function
292 // and infrastructure.
293
294 LoadHandlers();
295
296 // The intention of a post construction initializer
297 // is to allow for setup that is dependent upon other
298 // activities outside of the agency.
299
300 foreach (IRest handler in handlers)
301 {
302 try
303 {
304 handler.Initialize();
305 }
306 catch (Exception e)
307 {
308 Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message);
309 }
310 }
311
312 // Now that everything is setup we can proceed to
313 // add THIS agent to the HTTP server's handler list
314
315 // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
316 // have to be handled through the AddHttpHandler interface.
317// if (!AddAgentHandler(Rest.Name,this))
318// {
319// Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
320// foreach (IRest handler in handlers)
321// {
322// handler.Close();
323// }
324// }
325
326 }
327 catch (Exception e)
328 {
329 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
330 }
331 }
332
333 /// <summary>
334 /// In the interests of efficiency, and because we cannot determine whether
335 /// or not this instance will actually be harvested, we clobber the only
336 /// anchoring reference to the working state for this plug-in. What the
337 /// call to close does is irrelevant to this class beyond knowing that it
338 /// can nullify the reference when it returns.
339 /// To make sure everything is copacetic we make sure the primary interface
340 /// is disabled by deleting the handler from the HTTP server tables.
341 /// </summary>
342
343 public override void Close()
344 {
345 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
346
347 // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
348 // have to be handled through the AddHttpHandler interface.
349// try
350// {
351// RemoveAgentHandler(Rest.Name, this);
352// }
353// catch (KeyNotFoundException){}
354
355 foreach (IRest handler in handlers)
356 {
357 handler.Close();
358 }
359 }
360
361 #endregion overriding methods
362
363 #region interface methods
364
365 /// <summary>
366 /// This method is called by the HTTP server to match an incoming
367 /// request. It scans all of the strings registered by the
368 /// underlying handlers and looks for the best match. It returns
369 /// true if a match is found.
370 /// The matching process could be made arbitrarily complex.
371 /// Note: The match is case-insensitive.
372 /// </summary>
373
374 public bool Match(OSHttpRequest request, OSHttpResponse response)
375 {
376
377 string path = request.RawUrl.ToLower();
378
379 // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
380
381 try
382 {
383 foreach (string key in pathHandlers.Keys)
384 {
385 // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
386
387 // Note that Match will not necessarily find the handler that will
388 // actually be used - it does no test for the "closest" fit. It
389 // simply reflects that at least one possible handler exists.
390
391 if (path.StartsWith(key))
392 {
393 // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
394
395 // This apparently odd evaluation is needed to prevent a match
396 // on anything other than a URI token boundary. Otherwise we
397 // may match on URL's that were not intended for this handler.
398
399 return (path.Length == key.Length ||
400 path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
401 }
402 }
403
404 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
405
406 foreach (string key in streamHandlers.Keys)
407 {
408 // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
409
410 // Note that Match will not necessarily find the handler that will
411 // actually be used - it does no test for the "closest" fit. It
412 // simply reflects that at least one possible handler exists.
413
414 if (path.StartsWith(key))
415 {
416 // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
417
418 // This apparently odd evaluation is needed to prevent a match
419 // on anything other than a URI token boundary. Otherwise we
420 // may match on URL's that were not intended for this handler.
421
422 return (path.Length == key.Length ||
423 path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
424 }
425 }
426 }
427 catch (Exception e)
428 {
429 Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
430 }
431
432 return false;
433 }
434
435 /// <summary>
436 /// This is called by the HTTP server once the handler has indicated
437 /// that it is able to handle the request.
438 /// Preconditions:
439 /// [1] request != null and is a valid request object
440 /// [2] response != null and is a valid response object
441 /// Behavior is undefined if preconditions are not satisfied.
442 /// </summary>
443
444 public bool Handle(OSHttpRequest request, OSHttpResponse response)
445 {
446 bool handled;
447 base.MsgID = base.RequestID;
448
449 // Debug only
450
451 if (Rest.DEBUG)
452 {
453 Rest.Log.DebugFormat("{0} ENTRY", MsgId);
454 Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
455 Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
456
457 for (int i = 0; i < request.Headers.Count; i++)
458 {
459 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
460 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
461 }
462 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
463 }
464
465 // If a path handler worked we're done, otherwise try any
466 // available stream handlers too.
467
468 try
469 {
470 handled = (FindPathHandler(request, response) ||
471 FindStreamHandler(request, response));
472 }
473 catch (Exception e)
474 {
475 // A raw exception indicates that something we weren't expecting has
476 // happened. This should always reflect a shortcoming in the plugin,
477 // or a failure to satisfy the preconditions. It should not reflect
478 // an error in the request itself. Under such circumstances the state
479 // of the request cannot be determined and we are obliged to mark it
480 // as 'handled'.
481
482 Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
483 handled = true;
484 }
485
486 Rest.Log.DebugFormat("{0} EXIT", MsgId);
487
488 return handled;
489 }
490
491 #endregion interface methods
492
493 /// <summary>
494 /// If there is a stream handler registered that can handle the
495 /// request, then fine. If the request is not matched, do
496 /// nothing.
497 /// Note: The selection is case-insensitive
498 /// </summary>
499
500 private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
501 {
502 RequestData rdata = new RequestData(request, response, String.Empty);
503
504 string bestMatch = String.Empty;
505 string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
506
507 Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
508
509 if (!IsEnabled)
510 {
511 return false;
512 }
513
514 foreach (string pattern in streamHandlers.Keys)
515 {
516 if (path.StartsWith(pattern))
517 {
518 if (pattern.Length > bestMatch.Length)
519 {
520 bestMatch = pattern;
521 }
522 }
523 }
524
525 // Handle using the best match available
526
527 if (bestMatch.Length > 0)
528 {
529 Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
530 RestStreamHandler handler = streamHandlers[bestMatch];
531 rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
532 rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
533 rdata.Respond("FindStreamHandler Completion");
534 }
535
536 return rdata.handled;
537 }
538
539 /// <summary>
540 /// Add a stream handler for the designated HTTP method and path prefix.
541 /// If the handler is not enabled, the request is ignored. If the path
542 /// does not start with the REST prefix, it is added. If method-qualified
543 /// path has not already been registered, the method is added to the active
544 /// handler table.
545 /// </summary>
546 public void AddStreamHandler(string httpMethod, string path, RestMethod method)
547 {
548 if (!IsEnabled)
549 {
550 return;
551 }
552
553 if (!path.StartsWith(Rest.Prefix))
554 {
555 path = String.Format("{0}{1}", Rest.Prefix, path);
556 }
557
558 path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
559
560 // Conditionally add to the list
561
562 if (!streamHandlers.ContainsKey(path))
563 {
564 streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
565 Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path);
566 }
567 else
568 {
569 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
570 }
571 }
572
573 /// <summary>
574 /// Given the supplied request/response, if the handler is enabled, the inbound
575 /// information is used to match an entry in the active path handler tables, using
576 /// the method-qualified path information. If a match is found, then the handler is
577 /// invoked. The result is the boolean result of the handler, or false if no
578 /// handler was located. The boolean indicates whether or not the request has been
579 /// handled, not whether or not the request was successful - that information is in
580 /// the response.
581 /// Note: The selection process is case-insensitive
582 /// </summary>
583
584 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
585 {
586 RequestData rdata = null;
587 string bestMatch = null;
588
589 if (!IsEnabled)
590 {
591 return false;
592 }
593
594 // Conditionally add to the list
595
596 Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
597
598 foreach (string pattern in pathHandlers.Keys)
599 {
600 if (request.RawUrl.ToLower().StartsWith(pattern))
601 {
602 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
603 {
604 bestMatch = pattern;
605 }
606 }
607 }
608
609 if (!String.IsNullOrEmpty(bestMatch))
610 {
611 rdata = pathAllocators[bestMatch](request, response, bestMatch);
612
613 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
614
615 try
616 {
617 pathHandlers[bestMatch](rdata);
618 }
619
620 // A plugin generated error indicates a request-related error
621 // that has been handled by the plugin.
622
623 catch (RestException r)
624 {
625 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
626 }
627 }
628
629 return (rdata == null) ? false : rdata.handled;
630 }
631
632 /// <summary>
633 /// A method handler and a request allocator are stored using the designated
634 /// path as a key. If an entry already exists, it is replaced by the new one.
635 /// </summary>
636
637 public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
638 {
639 if (!IsEnabled)
640 {
641 return;
642 }
643
644 if (pathHandlers.ContainsKey(path))
645 {
646 Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
647 pathHandlers.Remove(path);
648 }
649
650 if (pathAllocators.ContainsKey(path))
651 {
652 Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
653 pathAllocators.Remove(path);
654 }
655
656 Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
657
658 pathHandlers.Add(path, mh);
659 pathAllocators.Add(path, ra);
660 }
661 }
662}