RestPlugin.cs 14 KB

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