RestPlugin.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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.IO;
  30. using System.Reflection;
  31. using System.Xml;
  32. using log4net;
  33. using Nini.Config;
  34. using OpenMetaverse;
  35. using OpenSim.Framework;
  36. using OpenSim.Framework.Servers;
  37. using OpenSim.Framework.Servers.HttpServer;
  38. namespace OpenSim.ApplicationPlugins.Rest
  39. {
  40. public abstract class RestPlugin : IApplicationPlugin
  41. {
  42. #region properties
  43. protected static readonly ILog m_log =
  44. LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  45. private IConfig _config; // Configuration source: Rest Plugins
  46. private IConfig _pluginConfig; // Configuration source: Plugin specific
  47. private OpenSimBase _app; // The 'server'
  48. private BaseHttpServer _httpd; // The server's RPC interface
  49. private string _prefix; // URL prefix below
  50. // which all REST URLs
  51. // are living
  52. private StringWriter _sw = null;
  53. private RestXmlWriter _xw = null;
  54. private string _godkey;
  55. private int _reqk;
  56. [ThreadStatic]
  57. private static string _threadRequestID = String.Empty;
  58. /// <summary>
  59. /// Return an ever increasing request ID for logging
  60. /// </summary>
  61. protected string RequestID
  62. {
  63. get { return _reqk++.ToString(); }
  64. set { _reqk = Convert.ToInt32(value); }
  65. }
  66. /// <summary>
  67. /// Thread-constant message IDs for logging.
  68. /// </summary>
  69. protected string MsgID
  70. {
  71. get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); }
  72. set { _threadRequestID = value; }
  73. }
  74. /// <summary>
  75. /// Returns true if Rest Plugins are enabled.
  76. /// </summary>
  77. public bool PluginsAreEnabled
  78. {
  79. get { return null != _config; }
  80. }
  81. /// <summary>
  82. /// Returns true if specific Rest Plugin is enabled.
  83. /// </summary>
  84. public bool IsEnabled
  85. {
  86. get
  87. {
  88. return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false);
  89. }
  90. }
  91. /// <summary>
  92. /// OpenSimMain application
  93. /// </summary>
  94. public OpenSimBase App
  95. {
  96. get { return _app; }
  97. }
  98. /// <summary>
  99. /// RPC server
  100. /// </summary>
  101. public BaseHttpServer HttpServer
  102. {
  103. get { return _httpd; }
  104. }
  105. /// <summary>
  106. /// URL prefix to use for all REST handlers
  107. /// </summary>
  108. public string Prefix
  109. {
  110. get { return _prefix; }
  111. }
  112. /// <summary>
  113. /// Access to GOD password string
  114. /// </summary>
  115. protected string GodKey
  116. {
  117. get { return _godkey; }
  118. }
  119. /// <summary>
  120. /// Configuration of the plugin
  121. /// </summary>
  122. public IConfig Config
  123. {
  124. get { return _pluginConfig; }
  125. }
  126. /// <summary>
  127. /// Name of the plugin
  128. /// </summary>
  129. public abstract string Name { get; }
  130. /// <summary>
  131. /// Return the config section name
  132. /// </summary>
  133. public abstract string ConfigName { get; }
  134. public XmlTextWriter XmlWriter
  135. {
  136. get
  137. {
  138. if (null == _xw)
  139. {
  140. _sw = new StringWriter();
  141. _xw = new RestXmlWriter(_sw);
  142. _xw.Formatting = Formatting.Indented;
  143. }
  144. return _xw;
  145. }
  146. }
  147. public string XmlWriterResult
  148. {
  149. get
  150. {
  151. _xw.Flush();
  152. _xw.Close();
  153. _xw = null;
  154. return _sw.ToString();
  155. }
  156. }
  157. #endregion properties
  158. #region methods
  159. // TODO: required by IPlugin, but likely not at all right
  160. private string m_version = "0.0";
  161. public string Version
  162. {
  163. get { return m_version; }
  164. }
  165. public void Initialise()
  166. {
  167. m_log.Info("[RESTPLUGIN]: " + Name + " cannot be default-initialized!");
  168. throw new PluginNotInitialisedException(Name);
  169. }
  170. /// <summary>
  171. /// This method is called by OpenSimMain immediately after loading the
  172. /// plugin and after basic server setup, but before running any server commands.
  173. /// </summary>
  174. /// <remarks>
  175. /// Note that entries MUST be added to the active configuration files before
  176. /// the plugin can be enabled.
  177. /// </remarks>
  178. public virtual void Initialise(OpenSimBase openSim)
  179. {
  180. RequestID = "0";
  181. MsgID = RequestID;
  182. try
  183. {
  184. if ((_config = openSim.ConfigSource.Source.Configs["RestPlugins"]) == null)
  185. {
  186. m_log.WarnFormat("{0} Rest Plugins not configured", MsgID);
  187. return;
  188. }
  189. if (!_config.GetBoolean("enabled", false))
  190. {
  191. m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
  192. return;
  193. }
  194. _app = openSim;
  195. _httpd = openSim.HttpServer;
  196. // Retrieve GOD key value, if any.
  197. _godkey = _config.GetString("god_key", String.Empty);
  198. // Retrive prefix if any.
  199. _prefix = _config.GetString("prefix", "/admin");
  200. // Get plugin specific config
  201. _pluginConfig = openSim.ConfigSource.Source.Configs[ConfigName];
  202. m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID);
  203. }
  204. catch (Exception e)
  205. {
  206. // we can safely ignore this, as it just means that
  207. // the key lookup in Configs failed, which signals to
  208. // us that noone is interested in our services...they
  209. // don't know what they are missing out on...
  210. // NOTE: Under the present OpenSim implementation it is
  211. // not possible for the openSim pointer to be null. However
  212. // were the implementation to be changed, this could
  213. // result in a silent initialization failure. Harmless
  214. // except for lack of function and lack of any
  215. // diagnostic indication as to why. The same is true if
  216. // the HTTP server reference is bad.
  217. // We should at least issue a message...
  218. m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
  219. m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
  220. }
  221. }
  222. public virtual void PostInitialise()
  223. {
  224. }
  225. private List<RestStreamHandler> _handlers = new List<RestStreamHandler>();
  226. private Dictionary<string, IHttpAgentHandler> _agents = new Dictionary<string, IHttpAgentHandler>();
  227. /// <summary>
  228. /// Add a REST stream handler to the underlying HTTP server.
  229. /// </summary>
  230. /// <param name="httpMethod">GET/PUT/POST/DELETE or
  231. /// similar</param>
  232. /// <param name="path">URL prefix</param>
  233. /// <param name="method">RestMethod handler doing the actual work</param>
  234. public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method)
  235. {
  236. if (!IsEnabled) return;
  237. if (!path.StartsWith(_prefix))
  238. {
  239. path = String.Format("{0}{1}", _prefix, path);
  240. }
  241. RestStreamHandler h = new RestStreamHandler(httpMethod, path, method);
  242. _httpd.AddStreamHandler(h);
  243. _handlers.Add(h);
  244. m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path);
  245. }
  246. /// <summary>
  247. /// Add a powerful Agent handler to the underlying HTTP
  248. /// server.
  249. /// </summary>
  250. /// <param name="agentName">name of agent handler</param>
  251. /// <param name="handler">agent handler method</param>
  252. /// <returns>false when the plugin is disabled or the agent
  253. /// handler could not be added. Any generated exceptions are
  254. /// allowed to drop through to the caller, i.e. ArgumentException.
  255. /// </returns>
  256. public bool AddAgentHandler(string agentName, IHttpAgentHandler handler)
  257. {
  258. if (!IsEnabled) return false;
  259. _agents.Add(agentName, handler);
  260. return _httpd.AddAgentHandler(agentName, handler);
  261. }
  262. /// <summary>
  263. /// Remove a powerful Agent handler from the underlying HTTP
  264. /// server.
  265. /// </summary>
  266. /// <param name="agentName">name of agent handler</param>
  267. /// <param name="handler">agent handler method</param>
  268. /// <returns>false when the plugin is disabled or the agent
  269. /// handler could not be removed. Any generated exceptions are
  270. /// allowed to drop through to the caller, i.e. KeyNotFound.
  271. /// </returns>
  272. public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler)
  273. {
  274. if (!IsEnabled) return false;
  275. if (_agents[agentName] == handler)
  276. {
  277. _agents.Remove(agentName);
  278. return _httpd.RemoveAgentHandler(agentName, handler);
  279. }
  280. return false;
  281. }
  282. /// <summary>
  283. /// Check whether the HTTP request came from god; that is, is
  284. /// the god_key as configured in the config section supplied
  285. /// via X-OpenSim-Godkey?
  286. /// </summary>
  287. /// <param name="request">HTTP request header</param>
  288. /// <returns>true when the HTTP request came from god.</returns>
  289. protected bool IsGod(OSHttpRequest request)
  290. {
  291. string[] keys = request.Headers.GetValues("X-OpenSim-Godkey");
  292. if (null == keys) return false;
  293. // we take the last key supplied
  294. return keys[keys.Length - 1] == _godkey;
  295. }
  296. /// <summary>
  297. /// Checks wether the X-OpenSim-Password value provided in the
  298. /// HTTP header is indeed the password on file for the avatar
  299. /// specified by the UUID
  300. /// </summary>
  301. protected bool IsVerifiedUser(OSHttpRequest request, UUID uuid)
  302. {
  303. // XXX under construction
  304. return false;
  305. }
  306. /// <summary>
  307. /// Clean up and remove all handlers that were added earlier.
  308. /// </summary>
  309. public virtual void Close()
  310. {
  311. foreach (RestStreamHandler h in _handlers)
  312. {
  313. _httpd.RemoveStreamHandler(h.HttpMethod, h.Path);
  314. }
  315. _handlers = null;
  316. foreach (KeyValuePair<string, IHttpAgentHandler> h in _agents)
  317. {
  318. _httpd.RemoveAgentHandler(h.Key, h.Value);
  319. }
  320. _agents = null;
  321. }
  322. public virtual void Dispose()
  323. {
  324. Close();
  325. }
  326. /// <summary>
  327. /// Return a failure message.
  328. /// </summary>
  329. /// <param name="method">origin of the failure message</param>
  330. /// <param name="message">failure message</param>
  331. /// <remarks>This should probably set a return code as
  332. /// well. (?)</remarks>
  333. protected string Failure(OSHttpResponse response, OSHttpStatusCode status,
  334. string method, string format, params string[] msg)
  335. {
  336. string m = String.Format(format, msg);
  337. response.StatusCode = (int) status;
  338. response.StatusDescription = m;
  339. m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m);
  340. return String.Format("<error>{0}</error>", m);
  341. }
  342. /// <summary>
  343. /// Return a failure message.
  344. /// </summary>
  345. /// <param name="method">origin of the failure message</param>
  346. /// <param name="e">exception causing the failure message</param>
  347. /// <remarks>This should probably set a return code as
  348. /// well. (?)</remarks>
  349. public string Failure(OSHttpResponse response, OSHttpStatusCode status,
  350. string method, Exception e)
  351. {
  352. string m = String.Format("exception occurred: {0}", e.Message);
  353. response.StatusCode = (int) status;
  354. response.StatusDescription = m;
  355. m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString());
  356. m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message);
  357. return String.Format("<error>{0}</error>", e.Message);
  358. }
  359. #endregion methods
  360. }
  361. }