BaseHttpServer.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  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.Net.Sockets;
  33. using System.Reflection;
  34. using System.Text;
  35. using System.Threading;
  36. using System.Xml;
  37. using libsecondlife.StructuredData;
  38. using log4net;
  39. using Nwc.XmlRpc;
  40. namespace OpenSim.Framework.Servers
  41. {
  42. public class BaseHttpServer
  43. {
  44. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  45. protected Thread m_workerThread;
  46. protected HttpListener m_httpListener;
  47. protected Dictionary<string, XmlRpcMethod> m_rpcHandlers = new Dictionary<string, XmlRpcMethod>();
  48. protected LLSDMethod m_llsdHandler = null;
  49. protected Dictionary<string, IRequestHandler> m_streamHandlers = new Dictionary<string, IRequestHandler>();
  50. protected Dictionary<string, GenericHTTPMethod> m_HTTPHandlers = new Dictionary<string, GenericHTTPMethod>();
  51. protected Dictionary<string, IHttpAgentHandler> m_agentHandlers = new Dictionary<string, IHttpAgentHandler>();
  52. protected uint m_port;
  53. protected bool m_ssl = false;
  54. protected bool m_firstcaps = true;
  55. public uint Port
  56. {
  57. get { return m_port; }
  58. }
  59. public BaseHttpServer(uint port)
  60. {
  61. m_port = port;
  62. }
  63. public BaseHttpServer(uint port, bool ssl)
  64. {
  65. m_ssl = ssl;
  66. m_port = port;
  67. }
  68. /// <summary>
  69. /// Add a stream handler to the http server. If the handler already exists, then nothing happens.
  70. /// </summary>
  71. /// <param name="handler"></param>
  72. public void AddStreamHandler(IRequestHandler handler)
  73. {
  74. string httpMethod = handler.HttpMethod;
  75. string path = handler.Path;
  76. string handlerKey = GetHandlerKey(httpMethod, path);
  77. lock (m_streamHandlers)
  78. {
  79. if (!m_streamHandlers.ContainsKey(handlerKey))
  80. {
  81. //m_log.DebugFormat("[BASE HTTP SERVER]: Adding handler key {0}", handlerKey);
  82. m_streamHandlers.Add(handlerKey, handler);
  83. }
  84. }
  85. }
  86. private static string GetHandlerKey(string httpMethod, string path)
  87. {
  88. return httpMethod + ":" + path;
  89. }
  90. public bool AddXmlRPCHandler(string method, XmlRpcMethod handler)
  91. {
  92. lock (m_rpcHandlers)
  93. {
  94. if (!m_rpcHandlers.ContainsKey(method))
  95. {
  96. m_rpcHandlers.Add(method, handler);
  97. return true;
  98. }
  99. }
  100. //must already have a handler for that path so return false
  101. return false;
  102. }
  103. public bool AddHTTPHandler(string method, GenericHTTPMethod handler)
  104. {
  105. lock (m_HTTPHandlers)
  106. {
  107. if (!m_HTTPHandlers.ContainsKey(method))
  108. {
  109. m_HTTPHandlers.Add(method, handler);
  110. return true;
  111. }
  112. }
  113. //must already have a handler for that path so return false
  114. return false;
  115. }
  116. // Note that the agent string is provided simply to differentiate
  117. // the handlers - it is NOT required to be an actual agent header
  118. // value.
  119. public bool AddAgentHandler(string agent, IHttpAgentHandler handler)
  120. {
  121. lock (m_agentHandlers)
  122. {
  123. if (!m_agentHandlers.ContainsKey(agent))
  124. {
  125. m_agentHandlers.Add(agent, handler);
  126. return true;
  127. }
  128. }
  129. //must already have a handler for that path so return false
  130. return false;
  131. }
  132. public bool SetLLSDHandler(LLSDMethod handler)
  133. {
  134. m_llsdHandler = handler;
  135. return true;
  136. }
  137. /// <summary>
  138. /// Handle an individual http request. This method is given to a worker in the thread pool.
  139. /// </summary>
  140. /// <param name="stateinfo"></param>
  141. public virtual void HandleRequest(Object stateinfo)
  142. {
  143. // If we don't catch the exception here it will just disappear into the thread pool and we'll be none the wiser
  144. try
  145. {
  146. HttpListenerContext context = (HttpListenerContext) stateinfo;
  147. OSHttpRequest request = new OSHttpRequest(context.Request);
  148. OSHttpResponse response = new OSHttpResponse(context.Response);
  149. // user agent based requests? not sure where this actually gets used from
  150. if (request.UserAgent != null)
  151. {
  152. IHttpAgentHandler agentHandler;
  153. if (TryGetAgentHandler(request, response, out agentHandler))
  154. {
  155. if (HandleAgentRequest(agentHandler, request, response))
  156. {
  157. m_log.DebugFormat("[HTTP-AGENT] Handler located for {0}", request.UserAgent);
  158. return;
  159. }
  160. }
  161. }
  162. IRequestHandler requestHandler;
  163. response.KeepAlive = false;
  164. response.SendChunked = false;
  165. string path = request.RawUrl;
  166. string handlerKey = GetHandlerKey(request.HttpMethod, path);
  167. //m_log.DebugFormat("[BASE HTTP SERVER]: Handling {0} request for {1}", request.HttpMethod, path);
  168. if (TryGetStreamHandler(handlerKey, out requestHandler))
  169. {
  170. // Okay, so this is bad, but should be considered temporary until everything is IStreamHandler.
  171. byte[] buffer;
  172. if (requestHandler is IStreamedRequestHandler)
  173. {
  174. IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler;
  175. buffer = streamedRequestHandler.Handle(path, request.InputStream, request, response);
  176. }
  177. else
  178. {
  179. IStreamHandler streamHandler = (IStreamHandler) requestHandler;
  180. using (MemoryStream memoryStream = new MemoryStream())
  181. {
  182. streamHandler.Handle(path, request.InputStream, memoryStream, request, response);
  183. memoryStream.Flush();
  184. buffer = memoryStream.ToArray();
  185. }
  186. }
  187. request.InputStream.Close();
  188. if (!response.IsContentTypeSet) response.ContentType = requestHandler.ContentType;
  189. response.ContentLength64 = buffer.LongLength;
  190. try
  191. {
  192. response.OutputStream.Write(buffer, 0, buffer.Length);
  193. response.OutputStream.Close();
  194. }
  195. catch (HttpListenerException)
  196. {
  197. m_log.WarnFormat("[BASE HTTP SERVER]: HTTP request abnormally terminated.");
  198. }
  199. return;
  200. }
  201. switch (request.ContentType)
  202. {
  203. case null:
  204. case "text/html":
  205. HandleHTTPRequest(request, response);
  206. return;
  207. case "application/xml+llsd":
  208. HandleLLSDRequests(request, response);
  209. return;
  210. case "text/xml":
  211. case "application/xml":
  212. default:
  213. HandleXmlRpcRequests(request, response);
  214. return;
  215. }
  216. }
  217. catch (SocketException e)
  218. {
  219. // At least on linux, it appears that if the client makes a request without requiring the response,
  220. // an unconnected socket exception is thrown when we close the response output stream. There's no
  221. // obvious way to tell if the client didn't require the response, so instead we'll catch and ignore
  222. // the exception instead.
  223. //
  224. // An alternative may be to turn off all response write exceptions on the HttpListener, but let's go
  225. // with the minimum first
  226. m_log.WarnFormat("[BASE HTTP SERVER]: HandleRequest threw {0}.\nNOTE: this may be spurious on Linux", e);
  227. }
  228. catch (Exception e)
  229. {
  230. m_log.ErrorFormat("[BASE HTTP SERVER]: HandleRequest() threw {0}", e);
  231. }
  232. }
  233. private bool TryGetStreamHandler(string handlerKey, out IRequestHandler streamHandler)
  234. {
  235. string bestMatch = null;
  236. foreach (string pattern in m_streamHandlers.Keys)
  237. {
  238. if (handlerKey.StartsWith(pattern))
  239. {
  240. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  241. {
  242. bestMatch = pattern;
  243. }
  244. }
  245. }
  246. if (String.IsNullOrEmpty(bestMatch))
  247. {
  248. streamHandler = null;
  249. return false;
  250. }
  251. else
  252. {
  253. streamHandler = m_streamHandlers[bestMatch];
  254. return true;
  255. }
  256. }
  257. private bool TryGetHTTPHandler(string handlerKey, out GenericHTTPMethod HTTPHandler)
  258. {
  259. string bestMatch = null;
  260. foreach (string pattern in m_HTTPHandlers.Keys)
  261. {
  262. if (handlerKey.StartsWith(pattern))
  263. {
  264. if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
  265. {
  266. bestMatch = pattern;
  267. }
  268. }
  269. }
  270. if (String.IsNullOrEmpty(bestMatch))
  271. {
  272. HTTPHandler = null;
  273. return false;
  274. }
  275. else
  276. {
  277. HTTPHandler = m_HTTPHandlers[bestMatch];
  278. return true;
  279. }
  280. }
  281. private bool TryGetAgentHandler(OSHttpRequest request, OSHttpResponse response, out IHttpAgentHandler agentHandler)
  282. {
  283. agentHandler = null;
  284. try
  285. {
  286. foreach (IHttpAgentHandler handler in m_agentHandlers.Values)
  287. {
  288. if (handler.Match(request, response))
  289. {
  290. agentHandler = handler;
  291. return true;
  292. }
  293. }
  294. }
  295. catch(KeyNotFoundException)
  296. {
  297. }
  298. return false;
  299. }
  300. /// <summary>
  301. /// Try all the registered xmlrpc handlers when an xmlrpc request is received.
  302. /// Sends back an XMLRPC unknown request response if no handler is registered for the requested method.
  303. /// </summary>
  304. /// <param name="request"></param>
  305. /// <param name="response"></param>
  306. private void HandleXmlRpcRequests(OSHttpRequest request, OSHttpResponse response)
  307. {
  308. Stream requestStream = request.InputStream;
  309. Encoding encoding = Encoding.UTF8;
  310. StreamReader reader = new StreamReader(requestStream, encoding);
  311. string requestBody = reader.ReadToEnd();
  312. reader.Close();
  313. requestStream.Close();
  314. string responseString = String.Empty;
  315. XmlRpcRequest xmlRprcRequest = null;
  316. try
  317. {
  318. xmlRprcRequest = (XmlRpcRequest) (new XmlRpcRequestDeserializer()).Deserialize(requestBody);
  319. }
  320. catch (XmlException)
  321. {
  322. }
  323. if (xmlRprcRequest != null)
  324. {
  325. string methodName = xmlRprcRequest.MethodName;
  326. if (methodName != null)
  327. {
  328. XmlRpcResponse xmlRpcResponse;
  329. XmlRpcMethod method;
  330. if (m_rpcHandlers.TryGetValue(methodName, out method))
  331. {
  332. xmlRpcResponse = method(xmlRprcRequest);
  333. }
  334. else
  335. {
  336. xmlRpcResponse = new XmlRpcResponse();
  337. // Code set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
  338. xmlRpcResponse.SetFault(-32601, String.Format("Requested method [{0}] not found", methodName));
  339. }
  340. responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse);
  341. }
  342. else
  343. {
  344. m_log.ErrorFormat("[BASE HTTP SERVER] Handler not found for http request {0}", request.RawUrl);
  345. responseString = "Error";
  346. }
  347. }
  348. response.ContentType = "text/xml";
  349. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  350. response.SendChunked = false;
  351. response.ContentLength64 = buffer.Length;
  352. response.ContentEncoding = Encoding.UTF8;
  353. try
  354. {
  355. response.OutputStream.Write(buffer, 0, buffer.Length);
  356. }
  357. catch (Exception ex)
  358. {
  359. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  360. }
  361. finally
  362. {
  363. try
  364. {
  365. response.OutputStream.Close();
  366. }
  367. catch (SocketException e)
  368. {
  369. // This has to be here to prevent a Linux/Mono crash
  370. m_log.WarnFormat("[BASE HTTP SERVER] XmlRpcRequest issue {0}.\nNOTE: this may be spurious on Linux.", e);
  371. }
  372. }
  373. }
  374. private void HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response)
  375. {
  376. Stream requestStream = request.InputStream;
  377. Encoding encoding = Encoding.UTF8;
  378. StreamReader reader = new StreamReader(requestStream, encoding);
  379. string requestBody = reader.ReadToEnd();
  380. reader.Close();
  381. requestStream.Close();
  382. LLSD llsdRequest = null;
  383. LLSD llsdResponse = null;
  384. try
  385. {
  386. llsdRequest = LLSDParser.DeserializeXml(requestBody);
  387. }
  388. catch (Exception ex)
  389. {
  390. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  391. }
  392. if (llsdRequest != null && m_llsdHandler != null)
  393. {
  394. llsdResponse = m_llsdHandler(llsdRequest);
  395. }
  396. else
  397. {
  398. LLSDMap map = new LLSDMap();
  399. map["reason"] = LLSD.FromString("LLSDRequest");
  400. map["message"] = LLSD.FromString("No handler registered for LLSD Requests");
  401. map["login"] = LLSD.FromString("false");
  402. llsdResponse = map;
  403. }
  404. response.ContentType = "application/xml+llsd";
  405. byte[] buffer = LLSDParser.SerializeXmlBytes(llsdResponse);
  406. response.SendChunked = false;
  407. response.ContentLength64 = buffer.Length;
  408. response.ContentEncoding = Encoding.UTF8;
  409. try
  410. {
  411. response.OutputStream.Write(buffer, 0, buffer.Length);
  412. }
  413. catch (Exception ex)
  414. {
  415. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  416. }
  417. finally
  418. {
  419. response.OutputStream.Close();
  420. }
  421. }
  422. /// <summary>
  423. /// A specific agent handler was provided. Such a handler is expecetd to have an
  424. /// intimate, and highly specific relationship with the client. Consequently,
  425. /// nothing is done here.
  426. /// </summary>
  427. /// <param name="handler"></param>
  428. /// <param name="request"></param>
  429. /// <param name="response"></param>
  430. private bool HandleAgentRequest(IHttpAgentHandler handler, OSHttpRequest request, OSHttpResponse response)
  431. {
  432. // In the case of REST, then handler is responsible for ALL aspects of
  433. // the request/response handling. Nothing is done here, not even encoding.
  434. try
  435. {
  436. return handler.Handle(request, response);
  437. }
  438. catch (Exception e)
  439. {
  440. // If the handler did in fact close the stream, then this will blow
  441. // chunks, so that that doesn;t disturb anybody we throw away any
  442. // and all exceptions raised. We've done our best to release the
  443. // client.
  444. try
  445. {
  446. m_log.Warn("[HTTP-AGENT]: Error - " + e.Message);
  447. response.SendChunked = false;
  448. response.KeepAlive = false;
  449. response.StatusCode = (int)OSHttpStatusCode.ServerErrorInternalError;
  450. response.OutputStream.Close();
  451. }
  452. catch(Exception)
  453. {
  454. }
  455. }
  456. return true;
  457. }
  458. public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response)
  459. {
  460. switch (request.HttpMethod)
  461. {
  462. case "OPTIONS":
  463. response.StatusCode = (int)OSHttpStatusCode.SuccessOk;
  464. return;
  465. default:
  466. HandleContentVerbs(request, response);
  467. return;
  468. }
  469. }
  470. private void HandleContentVerbs(OSHttpRequest request, OSHttpResponse response)
  471. {
  472. // This is a test. There's a workable alternative.. as this way sucks.
  473. // We'd like to put this into a text file parhaps that's easily editable.
  474. //
  475. // For this test to work, I used the following secondlife.exe parameters
  476. // "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
  477. //
  478. // Even after all that, there's still an error, but it's a start.
  479. //
  480. // I depend on show_login_form being in the secondlife.exe parameters to figure out
  481. // to display the form, or process it.
  482. // a better way would be nifty.
  483. Stream requestStream = request.InputStream;
  484. Encoding encoding = Encoding.UTF8;
  485. StreamReader reader = new StreamReader(requestStream, encoding);
  486. //string requestBody = reader.ReadToEnd();
  487. // avoid warning for now
  488. reader.ReadToEnd();
  489. reader.Close();
  490. requestStream.Close();
  491. Hashtable keysvals = new Hashtable();
  492. Hashtable headervals = new Hashtable();
  493. string host = String.Empty;
  494. string[] querystringkeys = request.QueryString.AllKeys;
  495. string[] rHeaders = request.Headers.AllKeys;
  496. foreach (string queryname in querystringkeys)
  497. {
  498. keysvals.Add(queryname, request.QueryString[queryname]);
  499. }
  500. foreach (string headername in rHeaders)
  501. {
  502. //m_log.Warn("[HEADER]: " + headername + "=" + request.Headers[headername]);
  503. headervals[headername] = request.Headers[headername];
  504. }
  505. if (headervals.Contains("Host"))
  506. {
  507. host = (string)headervals["Host"];
  508. }
  509. if (keysvals.Contains("method"))
  510. {
  511. //m_log.Warn("[HTTP]: Contains Method");
  512. string method = (string) keysvals["method"];
  513. //m_log.Warn("[HTTP]: " + requestBody);
  514. GenericHTTPMethod requestprocessor;
  515. bool foundHandler = TryGetHTTPHandler(method, out requestprocessor);
  516. if (foundHandler)
  517. {
  518. Hashtable responsedata = requestprocessor(keysvals);
  519. DoHTTPGruntWork(responsedata,response);
  520. //SendHTML500(response);
  521. }
  522. else
  523. {
  524. //m_log.Warn("[HTTP]: Handler Not Found");
  525. SendHTML404(response, host);
  526. }
  527. }
  528. else
  529. {
  530. //m_log.Warn("[HTTP]: No Method specified");
  531. SendHTML404(response, host);
  532. }
  533. }
  534. private static void DoHTTPGruntWork(Hashtable responsedata, OSHttpResponse response)
  535. {
  536. int responsecode = (int)responsedata["int_response_code"];
  537. string responseString = (string)responsedata["str_response_string"];
  538. string contentType = (string)responsedata["content_type"];
  539. //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this
  540. //and should check for NullReferenceExceptions
  541. if (string.IsNullOrEmpty(contentType))
  542. {
  543. contentType = "text/html";
  544. }
  545. // We're forgoing the usual error status codes here because the client
  546. // ignores anything but 200 and 301
  547. response.StatusCode = (int)OSHttpStatusCode.SuccessOk;
  548. if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently)
  549. {
  550. response.RedirectLocation = (string)responsedata["str_redirect_location"];
  551. response.StatusCode = responsecode;
  552. }
  553. response.AddHeader("Content-type", contentType);
  554. byte[] buffer;
  555. if (!contentType.Contains("image"))
  556. {
  557. buffer = Encoding.UTF8.GetBytes(responseString);
  558. }
  559. else
  560. {
  561. buffer = Convert.FromBase64String(responseString);
  562. }
  563. response.SendChunked = false;
  564. response.ContentLength64 = buffer.Length;
  565. response.ContentEncoding = Encoding.UTF8;
  566. try
  567. {
  568. response.OutputStream.Write(buffer, 0, buffer.Length);
  569. }
  570. catch (Exception ex)
  571. {
  572. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  573. }
  574. finally
  575. {
  576. response.OutputStream.Close();
  577. }
  578. }
  579. public void SendHTML404(OSHttpResponse response, string host)
  580. {
  581. // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s
  582. response.StatusCode = (int)OSHttpStatusCode.SuccessOk;
  583. response.AddHeader("Content-type", "text/html");
  584. string responseString = GetHTTP404(host);
  585. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  586. response.SendChunked = false;
  587. response.ContentLength64 = buffer.Length;
  588. response.ContentEncoding = Encoding.UTF8;
  589. try
  590. {
  591. response.OutputStream.Write(buffer, 0, buffer.Length);
  592. }
  593. catch (Exception ex)
  594. {
  595. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  596. }
  597. finally
  598. {
  599. response.OutputStream.Close();
  600. }
  601. }
  602. public void SendHTML500(OSHttpResponse response)
  603. {
  604. // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s
  605. response.StatusCode = (int)OSHttpStatusCode.SuccessOk;
  606. response.AddHeader("Content-type", "text/html");
  607. string responseString = GetHTTP500();
  608. byte[] buffer = Encoding.UTF8.GetBytes(responseString);
  609. response.SendChunked = false;
  610. response.ContentLength64 = buffer.Length;
  611. response.ContentEncoding = Encoding.UTF8;
  612. try
  613. {
  614. response.OutputStream.Write(buffer, 0, buffer.Length);
  615. }
  616. catch (Exception ex)
  617. {
  618. m_log.Warn("[HTTPD]: Error - " + ex.Message);
  619. }
  620. finally
  621. {
  622. response.OutputStream.Close();
  623. }
  624. }
  625. public void Start()
  626. {
  627. m_log.Info("[HTTPD]: Starting up HTTP Server");
  628. m_workerThread = new Thread(new ThreadStart(StartHTTP));
  629. m_workerThread.Name = "HttpThread";
  630. m_workerThread.IsBackground = true;
  631. m_workerThread.Start();
  632. ThreadTracker.Add(m_workerThread);
  633. }
  634. private void StartHTTP()
  635. {
  636. try
  637. {
  638. m_log.Info("[HTTPD]: Spawned main thread OK");
  639. m_httpListener = new HttpListener();
  640. if (!m_ssl)
  641. {
  642. m_httpListener.Prefixes.Add("http://+:" + m_port + "/");
  643. }
  644. else
  645. {
  646. m_httpListener.Prefixes.Add("https://+:" + m_port + "/");
  647. }
  648. m_httpListener.Start();
  649. HttpListenerContext context;
  650. while (true)
  651. {
  652. context = m_httpListener.GetContext();
  653. ThreadPool.QueueUserWorkItem(new WaitCallback(HandleRequest), context);
  654. }
  655. }
  656. catch (Exception e)
  657. {
  658. m_log.Warn("[HTTPD]: Error - " + e.Message);
  659. m_log.Warn("Tip: Do you have permission to listen on port " + m_port + "?");
  660. }
  661. }
  662. public void RemoveStreamHandler(string httpMethod, string path)
  663. {
  664. string handlerKey = GetHandlerKey(httpMethod, path);
  665. //m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey);
  666. m_streamHandlers.Remove(handlerKey);
  667. }
  668. public void RemoveHTTPHandler(string httpMethod, string path)
  669. {
  670. m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path));
  671. }
  672. // Remove the agent IF it is registered. Intercept the possible
  673. // exception.
  674. public bool RemoveAgentHandler(string agent, IHttpAgentHandler handler)
  675. {
  676. try
  677. {
  678. if (handler == m_agentHandlers[agent])
  679. {
  680. m_agentHandlers.Remove(agent);
  681. return true;
  682. }
  683. }
  684. catch(KeyNotFoundException)
  685. {
  686. }
  687. return false;
  688. }
  689. public string GetHTTP404(string host)
  690. {
  691. string file = Path.Combine(Util.configDir(), "http_404.html");
  692. if (!File.Exists(file))
  693. return getDefaultHTTP404(host);
  694. StreamReader sr = File.OpenText(file);
  695. string result = sr.ReadToEnd();
  696. sr.Close();
  697. return result;
  698. }
  699. public string GetHTTP500()
  700. {
  701. string file = Path.Combine(Util.configDir(), "http_500.html");
  702. if (!File.Exists(file))
  703. return getDefaultHTTP500();
  704. StreamReader sr = File.OpenText(file);
  705. string result = sr.ReadToEnd();
  706. sr.Close();
  707. return result;
  708. }
  709. // Fallback HTTP responses in case the HTTP error response files don't exist
  710. private static string getDefaultHTTP404(string host)
  711. {
  712. 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>";
  713. }
  714. private static string getDefaultHTTP500()
  715. {
  716. 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>";
  717. }
  718. }
  719. }