BaseHttpServer.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  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;
  29. using System.Collections.Generic;
  30. using System.IO;
  31. using System.Net;
  32. using System.Text;
  33. using System.Threading;
  34. using System.Xml;
  35. using Nwc.XmlRpc;
  36. using libsecondlife.StructuredData;
  37. using OpenSim.Framework.Console;
  38. namespace OpenSim.Framework.Servers
  39. {
  40. public class BaseHttpServer
  41. {
  42. private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  43. protected Thread m_workerThread;
  44. protected HttpListener m_httpListener;
  45. protected Dictionary<string, XmlRpcMethod> m_rpcHandlers = new Dictionary<string, XmlRpcMethod>();
  46. protected LLSDMethod m_llsdHandler = null;
  47. protected Dictionary<string, IRequestHandler> m_streamHandlers = new Dictionary<string, IRequestHandler>();
  48. protected Dictionary<string, GenericHTTPMethod> m_HTTPHandlers = new Dictionary<string, GenericHTTPMethod>();
  49. protected uint m_port;
  50. protected bool m_ssl = false;
  51. protected bool m_firstcaps = true;
  52. public uint Port
  53. {
  54. get { return m_port; }
  55. }
  56. public BaseHttpServer(uint port)
  57. {
  58. m_port = port;
  59. }
  60. public BaseHttpServer(uint port, bool ssl)
  61. {
  62. m_ssl = ssl;
  63. m_port = port;
  64. }
  65. /// <summary>
  66. /// Add a stream handler to the http server. If the handler already exists, then nothing happens.
  67. /// </summary>
  68. /// <param name="handler"></param>
  69. public void AddStreamHandler(IRequestHandler handler)
  70. {
  71. string httpMethod = handler.HttpMethod;
  72. string path = handler.Path;
  73. string handlerKey = GetHandlerKey(httpMethod, path);
  74. if (!m_streamHandlers.ContainsKey(handlerKey))
  75. {
  76. //m_log.DebugFormat("[BASE HTTP SERVER]: Adding handler key {0}", handlerKey);
  77. m_streamHandlers.Add(handlerKey, handler);
  78. }
  79. }
  80. private static string GetHandlerKey(string httpMethod, string path)
  81. {
  82. return httpMethod + ":" + path;
  83. }
  84. public bool AddXmlRPCHandler(string method, XmlRpcMethod handler)
  85. {
  86. if (!m_rpcHandlers.ContainsKey(method))
  87. {
  88. m_rpcHandlers.Add(method, handler);
  89. return true;
  90. }
  91. //must already have a handler for that path so return false
  92. return false;
  93. }
  94. public bool AddHTTPHandler(string method, GenericHTTPMethod handler)
  95. {
  96. if (!m_HTTPHandlers.ContainsKey(method))
  97. {
  98. m_HTTPHandlers.Add(method, handler);
  99. return true;
  100. }
  101. //must already have a handler for that path so return false
  102. return false;
  103. }
  104. public bool SetLLSDHandler(LLSDMethod handler)
  105. {
  106. m_llsdHandler = handler;
  107. return true;
  108. }
  109. public virtual void HandleRequest(Object stateinfo)
  110. {
  111. HttpListenerContext context = (HttpListenerContext) stateinfo;
  112. HttpListenerRequest request = context.Request;
  113. HttpListenerResponse response = context.Response;
  114. response.KeepAlive = false;
  115. response.SendChunked = false;
  116. string path = request.RawUrl;
  117. string handlerKey = GetHandlerKey(request.HttpMethod, path);
  118. IRequestHandler requestHandler;
  119. if (TryGetStreamHandler(handlerKey, out requestHandler))
  120. {
  121. // Okay, so this is bad, but should be considered temporary until everything is IStreamHandler.
  122. byte[] buffer;
  123. if (requestHandler is IStreamedRequestHandler)
  124. {
  125. IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler;
  126. buffer = streamedRequestHandler.Handle(path, request.InputStream);
  127. }
  128. else
  129. {
  130. IStreamHandler streamHandler = (IStreamHandler) requestHandler;
  131. using (MemoryStream memoryStream = new MemoryStream())
  132. {
  133. streamHandler.Handle(path, request.InputStream, memoryStream);
  134. memoryStream.Flush();
  135. buffer = memoryStream.ToArray();
  136. }
  137. }
  138. request.InputStream.Close();
  139. response.ContentType = requestHandler.ContentType;
  140. response.ContentLength64 = buffer.LongLength;
  141. try
  142. {
  143. response.OutputStream.Write(buffer, 0, buffer.Length);
  144. response.OutputStream.Close();
  145. }
  146. catch (HttpListenerException)
  147. {
  148. m_log.InfoFormat("[BASEHTTPSERVER] Http request abnormally terminated.");
  149. }
  150. }
  151. else
  152. {
  153. switch (request.ContentType)
  154. {
  155. case null:
  156. case "text/html":
  157. HandleHTTPRequest(request, response);
  158. break;
  159. case "application/xml+llsd":
  160. HandleLLSDRequests(request, response);
  161. break;
  162. case "text/xml":
  163. case "application/xml":
  164. default:
  165. HandleXmlRpcRequests(request, response);
  166. break;
  167. }
  168. }
  169. }
  170. private bool TryGetStreamHandler(string handlerKey, out IRequestHandler streamHandler)
  171. {
  172. string bestMatch = null;
  173. foreach (string pattern in m_streamHandlers.Keys)
  174. {
  175. if (handlerKey.StartsWith(pattern))
  176. {
  177. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  178. {
  179. bestMatch = pattern;
  180. }
  181. }
  182. }
  183. if (String.IsNullOrEmpty(bestMatch))
  184. {
  185. streamHandler = null;
  186. return false;
  187. }
  188. else
  189. {
  190. streamHandler = m_streamHandlers[bestMatch];
  191. return true;
  192. }
  193. }
  194. private bool TryGetHTTPHandler(string handlerKey, out GenericHTTPMethod HTTPHandler)
  195. {
  196. string bestMatch = null;
  197. foreach (string pattern in m_HTTPHandlers.Keys)
  198. {
  199. if (handlerKey.StartsWith(pattern))
  200. {
  201. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  202. {
  203. bestMatch = pattern;
  204. }
  205. }
  206. }
  207. if (String.IsNullOrEmpty(bestMatch))
  208. {
  209. HTTPHandler = null;
  210. return false;
  211. }
  212. else
  213. {
  214. HTTPHandler = m_HTTPHandlers[bestMatch];
  215. return true;
  216. }
  217. }
  218. /// <summary>
  219. /// Try all the registered xmlrpc handlers when an xmlrpc request is received.
  220. /// Sends back an XMLRPC unknown request response if no handler is registered for the requested method.
  221. /// </summary>
  222. /// <param name="request"></param>
  223. /// <param name="response"></param>
  224. private void HandleXmlRpcRequests(HttpListenerRequest request, HttpListenerResponse response)
  225. {
  226. Stream requestStream = request.InputStream;
  227. Encoding encoding = Encoding.UTF8;
  228. StreamReader reader = new StreamReader(requestStream, encoding);
  229. string requestBody = reader.ReadToEnd();
  230. reader.Close();
  231. requestStream.Close();
  232. string responseString = String.Empty;
  233. XmlRpcRequest xmlRprcRequest = null;
  234. try
  235. {
  236. xmlRprcRequest = (XmlRpcRequest) (new XmlRpcRequestDeserializer()).Deserialize(requestBody);
  237. }
  238. catch (XmlException)
  239. {
  240. }
  241. if (xmlRprcRequest != null)
  242. {
  243. string methodName = xmlRprcRequest.MethodName;
  244. if (methodName != null)
  245. {
  246. XmlRpcResponse xmlRpcResponse;
  247. XmlRpcMethod method;
  248. if (m_rpcHandlers.TryGetValue(methodName, out method))
  249. {
  250. xmlRpcResponse = method(xmlRprcRequest);
  251. }
  252. else
  253. {
  254. xmlRpcResponse = new XmlRpcResponse();
  255. // Code set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
  256. xmlRpcResponse.SetFault(-32601, String.Format("Requested method [{0}] not found", methodName));
  257. }
  258. responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse);
  259. }
  260. else
  261. {
  262. m_log.ErrorFormat("[BASE HTTP SERVER] Handler not found for http request {0}", request.RawUrl);
  263. responseString = "Error";
  264. }
  265. }
  266. response.ContentType = "text/xml";
  267. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  268. response.SendChunked = false;
  269. response.ContentLength64 = buffer.Length;
  270. response.ContentEncoding = Encoding.UTF8;
  271. try
  272. {
  273. response.OutputStream.Write(buffer, 0, buffer.Length);
  274. }
  275. catch (Exception ex)
  276. {
  277. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  278. }
  279. finally
  280. {
  281. response.OutputStream.Close();
  282. }
  283. }
  284. private void HandleLLSDRequests(HttpListenerRequest request, HttpListenerResponse response)
  285. {
  286. Stream requestStream = request.InputStream;
  287. Encoding encoding = Encoding.UTF8;
  288. StreamReader reader = new StreamReader(requestStream, encoding);
  289. string requestBody = reader.ReadToEnd();
  290. reader.Close();
  291. requestStream.Close();
  292. LLSD llsdRequest = null;
  293. LLSD llsdResponse = null;
  294. try { llsdRequest = LLSDParser.DeserializeXml(requestBody); }
  295. catch (Exception ex) { m_log.Warn("[HTTPD]: Error - " + ex.Message); }
  296. if (llsdRequest != null && m_llsdHandler != null)
  297. {
  298. llsdResponse = m_llsdHandler(llsdRequest);
  299. }
  300. else
  301. {
  302. LLSDMap map = new LLSDMap();
  303. map["reason"] = LLSD.FromString("LLSDRequest");
  304. map["message"] = LLSD.FromString("No handler registered for LLSD Requests");
  305. map["login"] = LLSD.FromString("false");
  306. llsdResponse = map;
  307. }
  308. response.ContentType = "application/xml+llsd";
  309. byte[] buffer = LLSDParser.SerializeXmlBytes(llsdResponse);
  310. response.SendChunked = false;
  311. response.ContentLength64 = buffer.Length;
  312. response.ContentEncoding = Encoding.UTF8;
  313. try
  314. {
  315. response.OutputStream.Write(buffer, 0, buffer.Length);
  316. }
  317. catch (Exception ex)
  318. {
  319. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  320. }
  321. finally
  322. {
  323. response.OutputStream.Close();
  324. }
  325. }
  326. public void HandleHTTPRequest(HttpListenerRequest request, HttpListenerResponse response)
  327. {
  328. switch( request.HttpMethod )
  329. {
  330. case "OPTIONS":
  331. response.StatusCode = 200;
  332. return;
  333. default:
  334. HandleContentVerbs(request, response);
  335. return;
  336. }
  337. }
  338. private void HandleContentVerbs(HttpListenerRequest request, HttpListenerResponse response)
  339. {
  340. // This is a test. There's a workable alternative.. as this way sucks.
  341. // We'd like to put this into a text file parhaps that's easily editable.
  342. //
  343. // For this test to work, I used the following secondlife.exe parameters
  344. // "C:\Program Files\SecondLifeWindLight\SecondLifeWindLight.exe" -settings settings_windlight.xml -channel "Second Life WindLight" -set SystemLanguage en-us -loginpage http://10.1.1.2:8002/?show_login_form=TRUE -loginuri http://10.1.1.2:8002 -user 10.1.1.2
  345. //
  346. // Even after all that, there's still an error, but it's a start.
  347. //
  348. // I depend on show_login_form being in the secondlife.exe parameters to figure out
  349. // to display the form, or process it.
  350. // a better way would be nifty.
  351. Stream requestStream = request.InputStream;
  352. Encoding encoding = Encoding.UTF8;
  353. StreamReader reader = new StreamReader(requestStream, encoding);
  354. string requestBody = reader.ReadToEnd();
  355. reader.Close();
  356. requestStream.Close();
  357. Hashtable keysvals = new Hashtable();
  358. Hashtable headervals = new Hashtable();
  359. string host = String.Empty;
  360. string[] querystringkeys = request.QueryString.AllKeys;
  361. string[] rHeaders = request.Headers.AllKeys;
  362. foreach (string queryname in querystringkeys)
  363. {
  364. keysvals.Add(queryname, request.QueryString[queryname]);
  365. }
  366. foreach (string headername in rHeaders)
  367. {
  368. //m_log.Warn("[HEADER]: " + headername + "=" + request.Headers[headername]);
  369. headervals[headername] = request.Headers[headername];
  370. }
  371. if (headervals.Contains("Host"))
  372. {
  373. host = (string)headervals["Host"];
  374. }
  375. if (keysvals.Contains("method"))
  376. {
  377. //m_log.Warn("[HTTP]: Contains Method");
  378. string method = (string) keysvals["method"];
  379. //m_log.Warn("[HTTP]: " + requestBody);
  380. GenericHTTPMethod requestprocessor;
  381. bool foundHandler = TryGetHTTPHandler(method, out requestprocessor);
  382. if (foundHandler)
  383. {
  384. Hashtable responsedata = requestprocessor(keysvals);
  385. DoHTTPGruntWork(responsedata,response);
  386. //SendHTML500(response);
  387. }
  388. else
  389. {
  390. //m_log.Warn("[HTTP]: Handler Not Found");
  391. SendHTML404(response, host);
  392. }
  393. }
  394. else
  395. {
  396. //m_log.Warn("[HTTP]: No Method specified");
  397. SendHTML404(response, host);
  398. }
  399. }
  400. private void DoHTTPGruntWork(Hashtable responsedata, HttpListenerResponse response)
  401. {
  402. int responsecode = (int)responsedata["int_response_code"];
  403. string responseString = (string)responsedata["str_response_string"];
  404. // We're forgoing the usual error status codes here because the client
  405. // ignores anything but 200 and 301
  406. response.StatusCode = 200;
  407. if (responsecode == 301)
  408. {
  409. response.RedirectLocation = (string)responsedata["str_redirect_location"];
  410. response.StatusCode = responsecode;
  411. }
  412. response.AddHeader("Content-type", "text/html");
  413. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  414. response.SendChunked = false;
  415. response.ContentLength64 = buffer.Length;
  416. response.ContentEncoding = Encoding.UTF8;
  417. try
  418. {
  419. response.OutputStream.Write(buffer, 0, buffer.Length);
  420. }
  421. catch (Exception ex)
  422. {
  423. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  424. }
  425. finally
  426. {
  427. response.OutputStream.Close();
  428. }
  429. }
  430. public void SendHTML404(HttpListenerResponse response, string host)
  431. {
  432. // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s
  433. response.StatusCode = 200;
  434. response.AddHeader("Content-type", "text/html");
  435. string responseString = GetHTTP404(host);
  436. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  437. response.SendChunked = false;
  438. response.ContentLength64 = buffer.Length;
  439. response.ContentEncoding = Encoding.UTF8;
  440. try
  441. {
  442. response.OutputStream.Write(buffer, 0, buffer.Length);
  443. }
  444. catch (Exception ex)
  445. {
  446. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  447. }
  448. finally
  449. {
  450. response.OutputStream.Close();
  451. }
  452. }
  453. public void SendHTML500(HttpListenerResponse response)
  454. {
  455. // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s
  456. response.StatusCode = 200;
  457. response.AddHeader("Content-type", "text/html");
  458. string responseString = GetHTTP500();
  459. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  460. response.SendChunked = false;
  461. response.ContentLength64 = buffer.Length;
  462. response.ContentEncoding = Encoding.UTF8;
  463. try
  464. {
  465. response.OutputStream.Write(buffer, 0, buffer.Length);
  466. }
  467. catch (Exception ex)
  468. {
  469. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  470. }
  471. finally
  472. {
  473. response.OutputStream.Close();
  474. }
  475. }
  476. public void Start()
  477. {
  478. m_log.Info("[HTTPD]: Starting up HTTP Server");
  479. m_workerThread = new Thread(new ThreadStart(StartHTTP));
  480. m_workerThread.Name = "HttpThread";
  481. m_workerThread.IsBackground = true;
  482. m_workerThread.Start();
  483. OpenSim.Framework.ThreadTracker.Add(m_workerThread);
  484. }
  485. private void StartHTTP()
  486. {
  487. try
  488. {
  489. m_log.Info("[HTTPD]: Spawned main thread OK");
  490. m_httpListener = new HttpListener();
  491. if (!m_ssl)
  492. {
  493. m_httpListener.Prefixes.Add("http://+:" + m_port + "/");
  494. }
  495. else
  496. {
  497. m_httpListener.Prefixes.Add("https://+:" + m_port + "/");
  498. }
  499. m_httpListener.Start();
  500. HttpListenerContext context;
  501. while (true)
  502. {
  503. context = m_httpListener.GetContext();
  504. ThreadPool.QueueUserWorkItem(new WaitCallback(HandleRequest), context);
  505. }
  506. }
  507. catch (Exception e)
  508. {
  509. m_log.Warn("[HTTPD]: Error - " + e.Message);
  510. m_log.Warn("Tip: Do you have permission to listen on port " + m_port + "?");
  511. }
  512. }
  513. public void RemoveStreamHandler(string httpMethod, string path)
  514. {
  515. string handlerKey = GetHandlerKey(httpMethod, path);
  516. //m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey);
  517. m_streamHandlers.Remove(handlerKey);
  518. }
  519. public void RemoveHTTPHandler(string httpMethod, string path)
  520. {
  521. m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path));
  522. }
  523. public string GetHTTP404(string host)
  524. {
  525. string file = Path.Combine(Util.configDir(), "http_404.html");
  526. if (!File.Exists(file))
  527. return getDefaultHTTP404(host);
  528. StreamReader sr = File.OpenText(file);
  529. string result = sr.ReadToEnd();
  530. sr.Close();
  531. return result;
  532. }
  533. public string GetHTTP500()
  534. {
  535. string file = Path.Combine(Util.configDir(), "http_500.html");
  536. if (!File.Exists(file))
  537. return getDefaultHTTP500();
  538. StreamReader sr = File.OpenText(file);
  539. string result = sr.ReadToEnd();
  540. sr.Close();
  541. return result;
  542. }
  543. // Fallback HTTP responses in case the HTTP error response files don't exist
  544. private string getDefaultHTTP404(string host)
  545. {
  546. return "<HTML><HEAD><TITLE>404 Page not found</TITLE><BODY><BR /><H1>Ooops!</H1><P>The page you requested has been obsconded with by knomes. Find hippos quick!</P><P>If you are trying to log-in, your link parameters should have: &quot;-loginpage http://" + host + "/?method=login -loginuri http://" + host + "/&quot; in your link </P></BODY></HTML>";
  547. }
  548. private string getDefaultHTTP500()
  549. {
  550. return "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE><BODY><BR /><H1>Ooops!</H1><P>The server you requested is overun by knomes! Find hippos quick!</P></BODY></HTML>";
  551. }
  552. }
  553. }