RestHandler.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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 OpenSim 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.Reflection;
  31. using OpenSim.Framework;
  32. using OpenSim.Framework.Servers;
  33. using OpenSim.ApplicationPlugins.Rest;
  34. using Mono.Addins;
  35. [assembly : Addin]
  36. [assembly : AddinDependency("OpenSim", "0.5")]
  37. namespace OpenSim.ApplicationPlugins.Rest.Inventory
  38. {
  39. [Extension("/OpenSim/Startup")]
  40. public class RestHandler : RestPlugin, IHttpAgentHandler
  41. {
  42. #region local static state
  43. /// <summary>
  44. /// This static initializer scans the assembly for classes that
  45. /// export the IRest interface and builds a list of them. These
  46. /// are later activated by the handler. To add a new handler it
  47. /// is only necessary to create a new services class that implements
  48. /// the IRest interface, and recompile the handler. This gives
  49. /// all of the build-time flexibility of a modular approach
  50. /// while not introducing yet-another module loader. Note that
  51. /// multiple assembles can still be built, each with its own set
  52. /// of handlers.
  53. /// </summary>
  54. private static bool handlersLoaded = false;
  55. private static List<Type> classes = new List<Type>();
  56. private static List<IRest> handlers = new List<IRest>();
  57. private static Type[] parms = new Type[1];
  58. private static Object[] args = new Object[1];
  59. static RestHandler()
  60. {
  61. Module[] mods = Assembly.GetExecutingAssembly().GetModules();
  62. foreach (Module m in mods)
  63. {
  64. Type[] types = m.GetTypes();
  65. foreach (Type t in types)
  66. {
  67. if (t.GetInterface("IRest") != null)
  68. {
  69. classes.Add(t);
  70. }
  71. }
  72. }
  73. }
  74. #endregion local static state
  75. #region local instance state
  76. /// <remarks>
  77. /// The handler delegate is not noteworthy. The allocator allows
  78. /// a given handler to optionally subclass the base RequestData
  79. /// structure to carry any locally required per-request state
  80. /// needed.
  81. /// </remarks>
  82. internal delegate void RestMethodHandler(RequestData rdata);
  83. internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
  84. // Handler tables: both stream and REST are supported
  85. internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
  86. internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
  87. internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
  88. /// <summary>
  89. /// This routine loads all of the handlers discovered during
  90. /// instance initialization. Each handler is responsible for
  91. /// registering itself with this handler.
  92. /// I was not able to make this code work in a constructor.
  93. /// </summary>
  94. private void LoadHandlers()
  95. {
  96. lock (handlers)
  97. {
  98. if (!handlersLoaded)
  99. {
  100. parms[0] = this.GetType();
  101. args[0] = this;
  102. ConstructorInfo ci;
  103. Object ht;
  104. foreach (Type t in classes)
  105. {
  106. ci = t.GetConstructor(parms);
  107. ht = ci.Invoke(args);
  108. handlers.Add((IRest)ht);
  109. }
  110. handlersLoaded = true;
  111. }
  112. }
  113. }
  114. #endregion local instance state
  115. #region overriding properties
  116. // Used to differentiate the message header.
  117. public override string Name
  118. {
  119. get { return "HANDLER"; }
  120. }
  121. // Used to partition the configuration space.
  122. public override string ConfigName
  123. {
  124. get { return "RestHandler"; }
  125. }
  126. // We have to rename these because we want
  127. // to be able to share the values with other
  128. // classes in our assembly and the base
  129. // names are protected.
  130. internal string MsgId
  131. {
  132. get { return base.MsgID; }
  133. }
  134. internal string RequestId
  135. {
  136. get { return base.RequestID; }
  137. }
  138. #endregion overriding properties
  139. #region overriding methods
  140. /// <summary>
  141. /// This method is called by OpenSimMain immediately after loading the
  142. /// plugin and after basic server setup, but before running any server commands.
  143. /// </summary>
  144. /// <remarks>
  145. /// Note that entries MUST be added to the active configuration files before
  146. /// the plugin can be enabled.
  147. /// </remarks>
  148. public override void Initialise(OpenSimBase openSim)
  149. {
  150. try
  151. {
  152. /// <remarks>
  153. /// This plugin will only be enabled if the broader
  154. /// REST plugin mechanism is enabled.
  155. /// </remarks>
  156. Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID);
  157. base.Initialise(openSim);
  158. if (!IsEnabled)
  159. {
  160. Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID);
  161. return;
  162. }
  163. Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgID);
  164. /// <remarks>
  165. /// These are stored in static variables to make
  166. /// them easy to reach from anywhere in the assembly.
  167. /// </remarks>
  168. Rest.main = openSim;
  169. Rest.Plugin = this;
  170. Rest.Comms = App.CommunicationsManager;
  171. Rest.UserServices = Rest.Comms.UserService;
  172. Rest.InventoryServices = Rest.Comms.InventoryService;
  173. Rest.AssetServices = Rest.Comms.AssetCache;
  174. Rest.Config = Config;
  175. Rest.Prefix = Prefix;
  176. Rest.GodKey = GodKey;
  177. Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true);
  178. Rest.Secure = Rest.Config.GetBoolean("secured",true);
  179. Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
  180. Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
  181. Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
  182. Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
  183. Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
  184. (Rest.Authenticate ? "" : "not "));
  185. Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
  186. (Rest.Authenticate ? "" : "not "));
  187. Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
  188. (Rest.ExtendedEscape ? "" : "not "));
  189. Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
  190. (Rest.DumpAsset ? "" : "not "));
  191. if (Rest.DumpAsset)
  192. {
  193. Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId,
  194. Rest.DumpLineSize);
  195. }
  196. // Load all of the handlers present in the
  197. // assembly
  198. // In principle, as we're an application plug-in,
  199. // most of what needs to be done could be done using
  200. // static resources, however the Open Sim plug-in
  201. // model makes this an instance, so that's what we
  202. // need to be.
  203. // There is only one Communications manager per
  204. // server, and by inference, only one each of the
  205. // user, asset, and inventory servers. So we can cache
  206. // those using a static initializer.
  207. // We move all of this processing off to another
  208. // services class to minimize overlap between function
  209. // and infrastructure.
  210. LoadHandlers();
  211. /// <remarks>
  212. /// The intention of a post construction initializer
  213. /// is to allow for setup that is dependent upon other
  214. /// activities outside of the agency. We don't currently
  215. /// have any, but the design allows for it.
  216. /// </remarks>
  217. foreach (IRest handler in handlers)
  218. {
  219. handler.Initialize();
  220. }
  221. /// <remarks>
  222. /// Now that everything is setup we can proceed and
  223. /// add this agent to the HTTP server's handler list
  224. /// </remarks>
  225. if (!AddAgentHandler(Rest.Name,this))
  226. {
  227. Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
  228. foreach (IRest handler in handlers)
  229. {
  230. handler.Close();
  231. }
  232. }
  233. }
  234. catch (Exception e)
  235. {
  236. Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgID, e.Message);
  237. }
  238. }
  239. /// <summary>
  240. /// In the interests of efficiency, and because we cannot determine whether
  241. /// or not this instance will actually be harvested, we clobber the only
  242. /// anchoring reference to the working state for this plug-in. What the
  243. /// call to close does is irrelevant to this class beyond knowing that it
  244. /// can nullify the reference when it returns.
  245. /// To make sure everything is copacetic we make sure the primary interface
  246. /// is disabled by deleting the handler from the HTTP server tables.
  247. /// </summary>
  248. public override void Close()
  249. {
  250. Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID);
  251. try
  252. {
  253. RemoveAgentHandler(Rest.Name, this);
  254. }
  255. catch (KeyNotFoundException){}
  256. foreach (IRest handler in handlers)
  257. {
  258. handler.Close();
  259. }
  260. }
  261. #endregion overriding methods
  262. #region interface methods
  263. /// <summary>
  264. /// This method is called by the server to match the client, it could
  265. /// just return true if we only want one such handler. For now we
  266. /// match any explicitly specified client.
  267. /// </summary>
  268. public bool Match(OSHttpRequest request, OSHttpResponse response)
  269. {
  270. string path = request.RawUrl;
  271. foreach (string key in pathHandlers.Keys)
  272. {
  273. if (path.StartsWith(key))
  274. {
  275. return ( path.Length == key.Length ||
  276. path.Substring(key.Length,1) == Rest.UrlPathSeparator);
  277. }
  278. }
  279. path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
  280. foreach (string key in streamHandlers.Keys)
  281. {
  282. if (path.StartsWith(key))
  283. {
  284. return true;
  285. }
  286. }
  287. return false;
  288. }
  289. /// <summary>
  290. /// Preconditions:
  291. /// [1] request != null and is a valid request object
  292. /// [2] response != null and is a valid response object
  293. /// Behavior is undefined if preconditions are not satisfied.
  294. /// </summary>
  295. public bool Handle(OSHttpRequest request, OSHttpResponse response)
  296. {
  297. bool handled;
  298. base.MsgID = base.RequestID;
  299. if (Rest.DEBUG)
  300. {
  301. Rest.Log.DebugFormat("{0} ENTRY", MsgId);
  302. Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
  303. Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
  304. for (int i = 0; i < request.Headers.Count; i++)
  305. {
  306. Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
  307. MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
  308. }
  309. Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
  310. }
  311. // If a path handler worked we're done, otherwise try any
  312. // available stream handlers too.
  313. try
  314. {
  315. handled = FindPathHandler(request, response) ||
  316. FindStreamHandler(request, response);
  317. }
  318. catch (Exception e)
  319. {
  320. // A raw exception indicates that something we weren't expecting has
  321. // happened. This should always reflect a shortcoming in the plugin,
  322. // or a failure to satisfy the preconditions.
  323. Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
  324. handled = true;
  325. }
  326. Rest.Log.DebugFormat("{0} EXIT", MsgId);
  327. return handled;
  328. }
  329. #endregion interface methods
  330. /// <summary>
  331. /// If there is a stream handler registered that can handle the
  332. /// request, then fine. If the request is not matched, do
  333. /// nothing.
  334. /// </summary>
  335. private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
  336. {
  337. RequestData rdata = new RequestData(request, response, String.Empty);
  338. string bestMatch = null;
  339. string path = String.Format("{0}:{1}", rdata.method, rdata.path);
  340. Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
  341. foreach (string pattern in streamHandlers.Keys)
  342. {
  343. if (path.StartsWith(pattern))
  344. {
  345. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  346. {
  347. bestMatch = pattern;
  348. }
  349. }
  350. }
  351. // Handle using the best match available
  352. if (!String.IsNullOrEmpty(bestMatch))
  353. {
  354. Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
  355. RestStreamHandler handler = streamHandlers[bestMatch];
  356. rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
  357. rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
  358. rdata.Respond("FindStreamHandler Completion");
  359. }
  360. return rdata.handled;
  361. }
  362. // Preserves the original handler's semantics
  363. public new void AddStreamHandler(string httpMethod, string path, RestMethod method)
  364. {
  365. if (!IsEnabled)
  366. {
  367. return;
  368. }
  369. if (!path.StartsWith(Rest.Prefix))
  370. {
  371. path = String.Format("{0}{1}", Rest.Prefix, path);
  372. }
  373. path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
  374. // Conditionally add to the list
  375. if (!streamHandlers.ContainsKey(path))
  376. {
  377. streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
  378. Rest.Log.DebugFormat("{0} Added handler for {1}", MsgID, path);
  379. }
  380. else
  381. {
  382. Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgID, path);
  383. }
  384. }
  385. internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
  386. {
  387. RequestData rdata = null;
  388. string bestMatch = null;
  389. if (!IsEnabled)
  390. {
  391. return false;
  392. }
  393. // Conditionally add to the list
  394. Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
  395. foreach (string pattern in pathHandlers.Keys)
  396. {
  397. if (request.RawUrl.StartsWith(pattern))
  398. {
  399. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  400. {
  401. bestMatch = pattern;
  402. }
  403. }
  404. }
  405. if (!String.IsNullOrEmpty(bestMatch))
  406. {
  407. rdata = pathAllocators[bestMatch](request, response);
  408. Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
  409. try
  410. {
  411. pathHandlers[bestMatch](rdata);
  412. }
  413. // A plugin generated error indicates a request-related error
  414. // that has been handled by the plugin.
  415. catch (RestException r)
  416. {
  417. Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
  418. }
  419. }
  420. return (rdata == null) ? false : rdata.handled;
  421. }
  422. internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
  423. {
  424. if (pathHandlers.ContainsKey(path))
  425. {
  426. Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
  427. pathHandlers.Remove(path);
  428. }
  429. if (pathAllocators.ContainsKey(path))
  430. {
  431. Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
  432. pathAllocators.Remove(path);
  433. }
  434. Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
  435. pathHandlers.Add(path, mh);
  436. pathAllocators.Add(path, ra);
  437. }
  438. }
  439. }