RestHandler.cs 26 KB

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