BaseHttpServer.cs 25 KB

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