HttpServer.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #region MIT License
  2. /**
  3. * HttpServer.cs
  4. *
  5. * The MIT License
  6. *
  7. * Copyright (c) 2012 sta.blockhead
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. */
  27. #endregion
  28. using System;
  29. using System.Collections.Generic;
  30. using System.Configuration;
  31. using System.Diagnostics;
  32. using System.IO;
  33. using System.Threading;
  34. using WebSocketSharp.Net;
  35. namespace WebSocketSharp.Server
  36. {
  37. public class HttpServer
  38. {
  39. #region Fields
  40. private Thread _acceptRequestThread;
  41. private bool _isWindows;
  42. private HttpListener _listener;
  43. private int _port;
  44. private string _rootPath;
  45. private ServiceManager _services;
  46. #endregion
  47. #region Constructors
  48. public HttpServer()
  49. : this(80)
  50. {
  51. }
  52. public HttpServer(int port)
  53. {
  54. _port = port;
  55. init();
  56. }
  57. #endregion
  58. #region Properties
  59. public int Port
  60. {
  61. get { return _port; }
  62. }
  63. public IEnumerable<string> ServicePath
  64. {
  65. get
  66. {
  67. return _services.Path;
  68. }
  69. }
  70. public bool Sweeped
  71. {
  72. get
  73. {
  74. return _services.Sweeped;
  75. }
  76. set
  77. {
  78. _services.Sweeped = value;
  79. }
  80. }
  81. #endregion
  82. #region Events
  83. public event EventHandler<ResponseEventArgs> OnConnect;
  84. public event EventHandler<ResponseEventArgs> OnDelete;
  85. public event EventHandler<ErrorEventArgs> OnError;
  86. public event EventHandler<ResponseEventArgs> OnGet;
  87. public event EventHandler<ResponseEventArgs> OnHead;
  88. public event EventHandler<ResponseEventArgs> OnOptions;
  89. public event EventHandler<ResponseEventArgs> OnPatch;
  90. public event EventHandler<ResponseEventArgs> OnPost;
  91. public event EventHandler<ResponseEventArgs> OnPut;
  92. public event EventHandler<ResponseEventArgs> OnTrace;
  93. #endregion
  94. #region Private Methods
  95. private void acceptRequest()
  96. {
  97. while (true)
  98. {
  99. try
  100. {
  101. var context = _listener.GetContext();
  102. respondAsync(context);
  103. }
  104. catch (HttpListenerException)
  105. {
  106. // HttpListener has been closed.
  107. break;
  108. }
  109. catch (Exception ex)
  110. {
  111. onError(ex.Message);
  112. break;
  113. }
  114. }
  115. }
  116. private void configureFromConfigFile()
  117. {
  118. _rootPath = ConfigurationManager.AppSettings["RootPath"];
  119. }
  120. private void init()
  121. {
  122. _isWindows = false;
  123. _listener = new HttpListener();
  124. _services = new ServiceManager();
  125. var os = Environment.OSVersion;
  126. if (os.Platform != PlatformID.Unix && os.Platform != PlatformID.MacOSX)
  127. _isWindows = true;
  128. var prefix = String.Format(
  129. "http{0}://*:{1}/", _port == 443 ? "s" : String.Empty, _port);
  130. _listener.Prefixes.Add(prefix);
  131. configureFromConfigFile();
  132. }
  133. private bool isUpgrade(HttpListenerRequest request, string value)
  134. {
  135. if (!Ext.Exists(request.Headers, "Upgrade", value))
  136. return false;
  137. if (!Ext.Exists(request.Headers, "Connection", "Upgrade"))
  138. return false;
  139. return true;
  140. }
  141. private void onError(string message)
  142. {
  143. #if DEBUG
  144. var callerFrame = new StackFrame(1);
  145. var caller = callerFrame.GetMethod();
  146. Console.WriteLine("HTTPSV: Error@{0}: {1}", caller.Name, message);
  147. #endif
  148. Ext.Emit(OnError, this, new ErrorEventArgs(message));
  149. }
  150. private void respond(HttpListenerContext context)
  151. {
  152. var req = context.Request;
  153. var res = context.Response;
  154. var eventArgs = new ResponseEventArgs(context);
  155. if (req.HttpMethod == "GET" && OnGet != null)
  156. {
  157. OnGet(this, eventArgs);
  158. return;
  159. }
  160. if (req.HttpMethod == "HEAD" && OnHead != null)
  161. {
  162. OnHead(this, eventArgs);
  163. return;
  164. }
  165. if (req.HttpMethod == "POST" && OnPost != null)
  166. {
  167. OnPost(this, eventArgs);
  168. return;
  169. }
  170. if (req.HttpMethod == "PUT" && OnPut != null)
  171. {
  172. OnPut(this, eventArgs);
  173. return;
  174. }
  175. if (req.HttpMethod == "DELETE" && OnDelete != null)
  176. {
  177. OnDelete(this, eventArgs);
  178. return;
  179. }
  180. if (req.HttpMethod == "OPTIONS" && OnOptions != null)
  181. {
  182. OnOptions(this, eventArgs);
  183. return;
  184. }
  185. if (req.HttpMethod == "TRACE" && OnTrace != null)
  186. {
  187. OnTrace(this, eventArgs);
  188. return;
  189. }
  190. if (req.HttpMethod == "CONNECT" && OnConnect != null)
  191. {
  192. OnConnect(this, eventArgs);
  193. return;
  194. }
  195. if (req.HttpMethod == "PATCH" && OnPatch != null)
  196. {
  197. OnPatch(this, eventArgs);
  198. return;
  199. }
  200. res.StatusCode = (int)HttpStatusCode.NotImplemented;
  201. }
  202. private void respondAsync(HttpListenerContext context)
  203. {
  204. WaitCallback respondCb = (state) =>
  205. {
  206. var req = context.Request;
  207. var res = context.Response;
  208. try
  209. {
  210. if (isUpgrade(req, "websocket"))
  211. {
  212. if (upgradeToWebSocket(context))
  213. return;
  214. }
  215. else
  216. {
  217. respond(context);
  218. }
  219. res.Close();
  220. }
  221. catch (Exception ex)
  222. {
  223. onError(ex.Message);
  224. }
  225. };
  226. ThreadPool.QueueUserWorkItem(respondCb);
  227. }
  228. private void startAcceptRequestThread()
  229. {
  230. _acceptRequestThread = new Thread(new ThreadStart(acceptRequest));
  231. _acceptRequestThread.IsBackground = true;
  232. _acceptRequestThread.Start();
  233. }
  234. private bool upgradeToWebSocket(HttpListenerContext context)
  235. {
  236. var res = context.Response;
  237. var wsContext = context.AcceptWebSocket();
  238. var socket = wsContext.WebSocket;
  239. var path = Ext.UrlDecode(wsContext.Path);
  240. IServiceHost svcHost;
  241. if (!_services.TryGetServiceHost(path, out svcHost))
  242. {
  243. res.StatusCode = (int)HttpStatusCode.NotImplemented;
  244. return false;
  245. }
  246. svcHost.BindWebSocket(socket);
  247. return true;
  248. }
  249. #endregion
  250. #region Public Methods
  251. public void AddService<T>(string absPath)
  252. where T : WebSocketService, new()
  253. {
  254. string msg;
  255. if (!Ext.IsValidAbsolutePath(absPath, out msg))
  256. {
  257. onError(msg);
  258. return;
  259. }
  260. var svcHost = new WebSocketServiceHost<T>();
  261. svcHost.Uri = Ext.ToUri(absPath);
  262. if (!Sweeped)
  263. svcHost.Sweeped = Sweeped;
  264. _services.Add(absPath, svcHost);
  265. }
  266. public byte[] GetFile(string path)
  267. {
  268. var filePath = _rootPath + path;
  269. if (_isWindows)
  270. filePath = filePath.Replace("/", "\\");
  271. if (File.Exists(filePath))
  272. return File.ReadAllBytes(filePath);
  273. return null;
  274. }
  275. public void Start()
  276. {
  277. _listener.Start();
  278. startAcceptRequestThread();
  279. }
  280. public void Stop()
  281. {
  282. _listener.Close();
  283. _acceptRequestThread.Join(5 * 1000);
  284. _services.Stop();
  285. }
  286. #endregion
  287. }
  288. }