RestHandler.cs 26 KB

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