PageRenderTime 54ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/CIAPI.Tests/TestingInfrastructure/ServerBase.cs

https://github.com/cityindex-attic/CIAPI.CS
C# | 361 lines | 288 code | 56 blank | 17 comment | 25 complexity | ec5ad8268eb04a3dc70788f103f9c14b MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.NetworkInformation;
  8. using System.Net.Sockets;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11. using System.Threading;
  12. using System.Web;
  13. namespace CIAPI.Tests.TestingInfrastructure
  14. {
  15. public abstract class ServerBase
  16. {
  17. private readonly int _port;
  18. private readonly int _recieveBufferSize;
  19. private bool _listen;
  20. private TcpListener _listener;
  21. public bool Debug;
  22. public int Port
  23. {
  24. get { return _port; }
  25. }
  26. private void LogMessage(string message)
  27. {
  28. if (!Debug)
  29. {
  30. return;
  31. }
  32. Trace.WriteLine("TESTSERVER\r\n" + message + "\r\n");
  33. }
  34. protected ServerBase(int port, int recieveBufferSize, bool debug)
  35. {
  36. Debug = debug;
  37. _port = port;
  38. _recieveBufferSize = recieveBufferSize;
  39. }
  40. public void Stop()
  41. {
  42. _listen = false;
  43. _listener.Stop();
  44. }
  45. public void Start()
  46. {
  47. _listen = true;
  48. try
  49. {
  50. _listener = new TcpListener(IPAddress.Loopback, _port);
  51. _listener.Start();
  52. LogMessage("Server started on " + _port);
  53. var th = new Thread(Listen);
  54. th.Start();
  55. }
  56. catch (Exception e)
  57. {
  58. LogMessage("Error starting server: \r\n" + e);
  59. }
  60. }
  61. private void WriteToSocket(Byte[] data, ref Socket socket)
  62. {
  63. try
  64. {
  65. if (socket.Connected)
  66. {
  67. int count;
  68. if ((count = socket.Send(data, data.Length, 0)) == -1)
  69. LogMessage("Socket Error cannot Send Packet");
  70. else
  71. {
  72. LogMessage("No. of bytes sent " + count);
  73. }
  74. }
  75. else
  76. LogMessage("Connection Dropped....");
  77. }
  78. catch (Exception e)
  79. {
  80. LogMessage("Error Occurred : " + e.ToString());
  81. }
  82. }
  83. private void Listen()
  84. {
  85. while (_listen)
  86. {
  87. Socket socket;
  88. try
  89. {
  90. socket = _listener.AcceptSocket();
  91. }
  92. catch (SocketException ex)
  93. {
  94. if (ex.Message.Contains("A blocking operation was interrupted by a call to WSACancelBlockingCall"))
  95. {
  96. return;
  97. }
  98. throw;
  99. }
  100. if (socket.Connected)
  101. {
  102. try
  103. {
  104. LogMessage("Socket connected");
  105. var buffer = new Byte[_recieveBufferSize];
  106. StringBuilder sb = new StringBuilder();
  107. int count = -1;
  108. string reqText;
  109. count = socket.Receive(buffer, buffer.Length, 0);
  110. reqText = Encoding.ASCII.GetString(buffer, 0, count);
  111. sb.Append(reqText);
  112. var pattern = new Regex("Content-Length: (?<length>\\d+)", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
  113. int contentLength;
  114. int bodyLength;
  115. if (pattern.IsMatch(reqText))
  116. {
  117. contentLength = int.Parse(pattern.Match(reqText).Groups["length"].Value);
  118. if (contentLength > 0)
  119. {
  120. int pos = reqText.IndexOf("\r\n\r\n");
  121. bodyLength = reqText.Length - (pos + 4);
  122. while (bodyLength < contentLength)
  123. {
  124. count = socket.Receive(buffer, buffer.Length, 0);
  125. reqText = Encoding.ASCII.GetString(buffer, 0, count);
  126. bodyLength = bodyLength + reqText.Length;
  127. sb.Append(reqText);
  128. }
  129. }
  130. }
  131. reqText = sb.ToString();
  132. LogMessage("SERVER RECEVIED:\n" + reqText);
  133. var request = new RequestInfo(reqText);
  134. ResponseInfo response = HandleRequest(request);
  135. byte[] responseBytes = Encoding.ASCII.GetBytes(response.ToString());
  136. WriteToSocket(responseBytes, ref socket);
  137. LogMessage("SERVER SENT:\n" + response.ToString());
  138. }
  139. catch (Exception ex)
  140. {
  141. ResponseInfo response = new ResponseInfo()
  142. {
  143. Body = ex.ToString(),
  144. Status = "503 Internal Server Error"
  145. };
  146. byte[] responseBytes = Encoding.ASCII.GetBytes(response.ToString());
  147. WriteToSocket(responseBytes, ref socket);
  148. LogMessage("SERVER SENT:\n" + response.ToString());
  149. }
  150. socket.Close();
  151. LogMessage("Socket Closed");
  152. }
  153. }
  154. }
  155. public static int GetAvailablePort()
  156. {
  157. return GetAvailablePort(9000, 30000, IPAddress.Loopback, false);
  158. }
  159. /// <summary>
  160. /// Returns first available port on the specified IP address.
  161. /// The port scan excludes ports that are open on ANY loopback adapter.
  162. ///
  163. /// If the address upon which a port is requested is an 'ANY' address all
  164. /// ports that are open on ANY IP are excluded.
  165. /// </summary>
  166. /// <param name="rangeStart"></param>
  167. /// <param name="rangeEnd"></param>
  168. /// <param name="ip">The IP address upon which to search for available port.</param>
  169. /// <param name="includeIdlePorts">If true includes ports in TIME_WAIT state in results.
  170. /// TIME_WAIT state is typically cool down period for recently released ports.</param>
  171. /// <returns></returns>
  172. public static int GetAvailablePort(int rangeStart, int rangeEnd, IPAddress ip, bool includeIdlePorts)
  173. {
  174. IPGlobalProperties ipProps = IPGlobalProperties.GetIPGlobalProperties();
  175. // if the ip we want a port on is an 'any' or loopback port we need to exclude all ports that are active on any IP
  176. Func<IPAddress, bool> isIpAnyOrLoopBack = i => IPAddress.Any.Equals(i) ||
  177. IPAddress.IPv6Any.Equals(i) ||
  178. IPAddress.Loopback.Equals(i) ||
  179. IPAddress.IPv6Loopback.
  180. Equals(i);
  181. // get all active ports on specified IP.
  182. var excludedPorts = new List<ushort>();
  183. // if a port is open on an 'any' or 'loopback' interface then include it in the excludedPorts
  184. excludedPorts.AddRange(from n in ipProps.GetActiveTcpConnections()
  185. where
  186. n.LocalEndPoint.Port >= rangeStart &&
  187. n.LocalEndPoint.Port <= rangeEnd && (
  188. isIpAnyOrLoopBack(ip) ||
  189. n.LocalEndPoint.Address.Equals(ip) ||
  190. isIpAnyOrLoopBack(n.LocalEndPoint.Address)) &&
  191. (!includeIdlePorts || n.State != TcpState.TimeWait)
  192. select (ushort)n.LocalEndPoint.Port);
  193. excludedPorts.AddRange(from n in ipProps.GetActiveTcpListeners()
  194. where n.Port >= rangeStart && n.Port <= rangeEnd && (
  195. isIpAnyOrLoopBack(ip) ||
  196. n.Address.Equals(ip) ||
  197. isIpAnyOrLoopBack(n.Address))
  198. select (ushort)n.Port);
  199. excludedPorts.AddRange(from n in ipProps.GetActiveUdpListeners()
  200. where n.Port >= rangeStart && n.Port <= rangeEnd && (
  201. isIpAnyOrLoopBack(ip) ||
  202. n.Address.Equals(ip) ||
  203. isIpAnyOrLoopBack(n.Address))
  204. select (ushort)n.Port);
  205. excludedPorts.Sort();
  206. for (int port = rangeStart; port <= rangeEnd; port++)
  207. {
  208. if (!excludedPorts.Contains((ushort)port))
  209. {
  210. return port;
  211. }
  212. }
  213. return 0;
  214. }
  215. public abstract ResponseInfo HandleRequest(RequestInfo request);
  216. #region Nested type: RequestEventArgs
  217. public class RequestEventArgs : EventArgs
  218. {
  219. public RequestInfo Request;
  220. public ResponseInfo Response;
  221. }
  222. #endregion
  223. #region Nested type: RequestInfo
  224. public class RequestInfo
  225. {
  226. public string Body = "";
  227. public NameValueCollection Headers = new NameValueCollection();
  228. public string Method = "";
  229. public NameValueCollection Parameters = new NameValueCollection();
  230. public string Route = "";
  231. public string Url;
  232. public string QueryString;
  233. public RequestInfo(string request)
  234. {
  235. ParseRequest(request);
  236. }
  237. private void ParseRequest(string request)
  238. {
  239. string[] lines = request.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
  240. // first line is request descriptor
  241. string line = lines[0].Trim();
  242. Method = line.Substring(0, line.IndexOf(' '));
  243. Route = line.Substring(Method.Length, line.LastIndexOf("HTTP/1.1", StringComparison.Ordinal) - Method.Length).Trim();
  244. Url = Route;
  245. int queryIndex = Route.IndexOf('?');
  246. if (queryIndex > 0)
  247. {
  248. string queryString = Route.Substring(queryIndex + 1);
  249. Route = Route.Substring(0, queryIndex);
  250. QueryString = queryString;
  251. Parameters = HttpUtility.ParseQueryString(queryString);
  252. }
  253. int lineIndex = 1;
  254. bool isBody = false;
  255. while (lineIndex < lines.Length)
  256. {
  257. line = lines[lineIndex].Trim('\0');
  258. if (string.IsNullOrEmpty(line) && !isBody)
  259. {
  260. isBody = true;
  261. }
  262. else
  263. {
  264. if (isBody)
  265. {
  266. Body = Body + line + "\r\n";
  267. }
  268. else
  269. {
  270. string key = line.Substring(0, line.IndexOf(':'));
  271. string value = line.Substring(key.Length + 2);
  272. Headers[key] = value;
  273. }
  274. }
  275. lineIndex++;
  276. }
  277. Body = Body.Trim();
  278. }
  279. }
  280. #endregion
  281. #region Nested type: ResponseInfo
  282. public class ResponseInfo
  283. {
  284. public string Body = "";
  285. public NameValueCollection Headers = new NameValueCollection();
  286. public string Status = "200 OK";
  287. public override string ToString()
  288. {
  289. var sb = new StringBuilder();
  290. sb.AppendLine(string.Format("HTTP/1.1 {0}", Status));
  291. foreach (string key in Headers)
  292. {
  293. sb.AppendLine(string.Format("{0}:{1}", key, Headers[key]));
  294. }
  295. if (!string.IsNullOrEmpty(Body))
  296. {
  297. sb.AppendLine();
  298. sb.Append(Body);
  299. }
  300. return sb.ToString();
  301. }
  302. }
  303. #endregion
  304. }
  305. }