From 536852c4cdc485fa76999e782c359d52a5546c25 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Thu, 14 Feb 2019 09:14:50 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 41 ++++ HTTPServer.cs | 145 ++++++++++++ HttpApplication.cs | 13 ++ HttpReader.cs | 297 ++++++++++++++++++++++++ HttpRequest.cs | 121 ++++++++++ HttpResponse.cs | 113 +++++++++ HttpStatusCodes.cs | 23 ++ Properties/AssemblyInfo.cs | 26 +++ QueryStringParameters.cs | 65 ++++++ exceptions/HttpException.cs | 29 +++ exceptions/IllegalRequestException.cs | 11 + exceptions/ResourceNotFoundException.cs | 11 + ln.http.csproj | 51 ++++ threads/Pool.cs | 82 +++++++ 14 files changed, 1028 insertions(+) create mode 100644 .gitignore create mode 100644 HTTPServer.cs create mode 100644 HttpApplication.cs create mode 100644 HttpReader.cs create mode 100644 HttpRequest.cs create mode 100644 HttpResponse.cs create mode 100644 HttpStatusCodes.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 QueryStringParameters.cs create mode 100644 exceptions/HttpException.cs create mode 100644 exceptions/IllegalRequestException.cs create mode 100644 exceptions/ResourceNotFoundException.cs create mode 100644 ln.http.csproj create mode 100644 threads/Pool.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf793ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Autosave files +*~ + +# build +[Oo]bj/ +[Bb]in/ +packages/ +TestResults/ + +# globs +Makefile.in +*.DS_Store +*.sln.cache +*.suo +*.cache +*.pidb +*.userprefs +*.usertasks +config.log +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.user +*.tar.gz +tarballs/ +test-results/ +Thumbs.db +.vs/ + +# Mac bundle stuff +*.dmg +*.app + +# resharper +*_Resharper.* +*.Resharper + +# dotCover +*.dotCover diff --git a/HTTPServer.cs b/HTTPServer.cs new file mode 100644 index 0000000..aef50b4 --- /dev/null +++ b/HTTPServer.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using ln.http.threads; +using System.IO; +using System.Text; + + +namespace ln.http +{ + public class HTTPServer + { + public static int backlog = 5; + public static int defaultPort = 8080; + public static bool exclusivePortListener = false; + + public HttpApplication DefaultApplication { get; set; } + + bool shutdown = false; + + Dictionary tcpListeners = new Dictionary(); + Dictionary applications = new Dictionary(); + + Pool threadPool = new Pool(); + + public HTTPServer() + { + } + + public void AddEndpoint(IPEndPoint endpoint) + { + if (this.tcpListeners.ContainsKey(endpoint)) + { + throw new ArgumentOutOfRangeException(nameof(endpoint), "EndPoint already added"); + } + + this.tcpListeners.Add(endpoint, new TcpListener(endpoint)); + } + public void RemoveEndpoint(IPEndPoint endpoint) + { + if (this.tcpListeners.ContainsKey(endpoint)) + { + this.tcpListeners[endpoint].Stop(); + this.tcpListeners.Remove(endpoint); + } + } + + public void AddApplication(Uri BaseURI,HttpApplication application) + { + applications[BaseURI] = application; + } + + + public void Start() + { + foreach (TcpListener tcpListener in this.tcpListeners.Values) + { + tcpListener.Start(backlog); + this.threadPool.Enqueue(() => AcceptConnections(tcpListener)); + } + + } + + public void Stop() + { + lock (this) + { + this.shutdown = true; + } + } + + private void AcceptConnections(TcpListener tcpListener) + { + while (true) + { + lock(this) + { + if (this.shutdown) { + return; + } + } + AcceptConnection(tcpListener); + } + } + + private void AcceptConnection(TcpListener tcpListener) + { + TcpClient tcpClient = tcpListener.AcceptTcpClient(); + this.threadPool.Enqueue(() => HandleConnection(tcpClient)); + } + + private void HandleConnection(TcpClient tcpClient) + { + HttpReader httpReader = new HttpReader(tcpClient.GetStream()); + httpReader.Read(); + + HttpRequest httpRequest = new HttpRequest(httpReader, (IPEndPoint)tcpClient.Client.LocalEndPoint); + HttpResponse response = null; + + try + { + HttpApplication application = DefaultApplication; + + if (applications.ContainsKey(httpRequest.BaseURI)) + application = applications[httpRequest.BaseURI]; + + response = application.GetResponse( httpRequest ); + } catch (Exception e) + { + response = new HttpResponse(httpRequest,"text/plain"); + response.StatusCode = 500; + response.ContentWriter.WriteLine("Exception caught: {0}",e); + } + + if (!response.HasCustomContentStream) + { + response.ContentWriter.Flush(); + MemoryStream cstream = (MemoryStream)response.ContentStream; + cstream.Position = 0; + + response.SetHeader("content-length", cstream.Length.ToString()); + } + + StreamWriter streamWriter = new StreamWriter(tcpClient.GetStream()); + + streamWriter.WriteLine("{0} {1} {2}", httpRequest.Protocol,response.StatusCode, response.StatusMessage); + foreach (String headerName in response.GetHeaderNames()) + { + streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName)); + } + + streamWriter.WriteLine(); + streamWriter.Flush(); + + response.ContentStream.CopyTo(tcpClient.GetStream()); + response.ContentStream.Close(); + response.ContentStream.Dispose(); + + tcpClient.Close(); + } + + } +} diff --git a/HttpApplication.cs b/HttpApplication.cs new file mode 100644 index 0000000..5d8683b --- /dev/null +++ b/HttpApplication.cs @@ -0,0 +1,13 @@ +using System; +using ln.http; +namespace ln.http +{ + public abstract class HttpApplication + { + public HttpApplication() + { + } + + public abstract HttpResponse GetResponse(HttpRequest httpRequest); + } +} diff --git a/HttpReader.cs b/HttpReader.cs new file mode 100644 index 0000000..57674bc --- /dev/null +++ b/HttpReader.cs @@ -0,0 +1,297 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Net; + +namespace ln.http +{ + public class HttpReader + { + delegate bool ReadCondition(int b); + + public Stream Stream { get; } + + private byte[] buffer = new byte[8192]; + private int hlen; + private int blen; + private int bptr; + + public IPEndPoint RemoteEndpoint { get; private set; } + + public String Method { get; private set; } + public String URL { get; private set; } + public String Protocol { get; private set; } + + public Dictionary Headers { get; } = new Dictionary(); + + public HttpReader(Stream stream) + { + Stream = stream; + } + public HttpReader(Stream stream,IPEndPoint remoteEndpoint) + { + Stream = stream; + RemoteEndpoint = remoteEndpoint; + } + + public void Read() + { + ReadRequestHead(); + + Method = ReadToken(); + SkipWhiteSpace(); + URL = ReadToken(); + SkipWhiteSpace(); + Protocol = ReadToken(); + ReadLine(); + + ReadHeaders(); + } + + public int ReadByte() + { + if (bptr >= blen) + return -1; + return buffer[bptr++]; + } + + public int Current + { + get + { + if (bptr >= blen) + return -1; + return buffer[bptr]; + } + } + + public void Reverse() + { + if ((bptr > 0) && (bptr < blen)) + bptr--; + } + + public void ReadRequestHead() + { + bptr = 0; + do + { + blen += Stream.Read(buffer, blen, buffer.Length - blen); + while (bptr <= (blen - 4)) + { + if ( + (buffer[bptr + 0] == '\r') && + (buffer[bptr + 1] == '\n') && + (buffer[bptr + 2] == '\r') && + (buffer[bptr + 3] == '\n') + ) + { + hlen = bptr; + bptr = 0; + return; + } + bptr++; + } + + byte[] nbuffer = new byte[buffer.Length << 1]; + Array.Copy(buffer, nbuffer, buffer.Length); + buffer = nbuffer; + } while (blen >= buffer.Length); + bptr = 0; + } + + private String ReadConditional(ReadCondition readCondition,bool reverse = true) + { + StringBuilder stringBuilder = new StringBuilder(); + int b = ReadByte(); + while ((b != -1) && (readCondition(b)) ) + { + stringBuilder.Append((char)b); + b = ReadByte(); + } + if (reverse) + Reverse(); + + return stringBuilder.ToString(); + } + + public void SkipWhiteSpace() + { + while (Char.IsWhiteSpace((char)ReadByte())) { }; + Reverse(); + } + + public String ReadToken() + { + return ReadConditional((b) => !Char.IsWhiteSpace((char)b)); + } + + public String ReadLine() + { + int p = bptr; + while (p < blen - 1) + { + if ((buffer[p] == '\r') && (buffer[p + 1] == '\n')) + { + break; + } + p++; + } + + string result = Encoding.ASCII.GetString(buffer, bptr, p - bptr); + bptr = p + 2; + return result; + } + + public String ReadHeaderName() + { + return ReadConditional((b) => b != ':', false).ToUpper(); + } + public String ReadHeaderValue() + { + String value = ReadLine(); + while (Char.IsWhiteSpace((char)Current)) + { + value = value + ReadLine(); + } + return value.Trim(); + } + + public void ReadHeaders() + { + while (bptr < hlen) + { + String name = ReadHeaderName(); + String value = ReadHeaderValue(); + + Headers.Add(name, value); + } + } + + public int ReadRequestBody(byte[] dst,int offset,int length) + { + int nRead = 0; + if (bptr < blen) + { + int len = Math.Min(length, blen - bptr); + Array.Copy(buffer, bptr, dst, offset, len); + bptr += len; + length -= len; + offset += len; + nRead += len; + } + if (length > 0) + { + nRead += Stream.Read(dst, offset, length); + } + return nRead; + } + + + + /* + byte[] buffer; + int blen = 0; + int bptr = 0; + + public HttpReader(Stream stream) + { + Stream = stream; + buffer = new byte[1024]; + } + public HttpReader(Stream stream, int buffersize) + { + Stream = stream; + buffer = new byte[buffersize]; + } + + private int read() + { + if (bptr >= blen) + { + bptr = 0; + blen = Stream.Read(buffer, 0, buffer.Length); + if (blen <= 0) + throw new EndOfStreamException(); + } + return buffer[bptr++]; + } + + private int ReadTo(byte[] b,int offset,byte[] mark) + { + int pm = 0; + int p = offset; + while (p < b.Length) + { + b[p] = (byte)read(); + if (b[p] == mark[pm]) + { + pm++; + if (pm >= mark.Length) + { + p++; + break; + } + } + else + { + pm = 0; + } + p++; + } + return p; + } + + + public String ReadRequestLine(int maxSize = 1024) + { + byte[] b = new byte[maxSize]; + int l = ReadTo(b, 0, new byte[] { 0x0d, 0x0a }); + return Encoding.ASCII.GetString(b, 0, l); + } + + public Dictionary ReadHTTPHeaders() + { + byte[] b = new byte[8192]; + int hlen = ReadTo(b, 0, new byte[] { 0x0d, 0x0a, 0x0d, 0x0a }); + + Dictionary headers = new Dictionary(); + + string rawHeaders = Encoding.ASCII.GetString(b, 0, hlen); + String[] rawLines = rawHeaders.Split(new String[] { "\r\n" }, StringSplitOptions.None); + + for (int n = rawLines.Length-1; n >= 0 ; n--) + { + if ((rawLines[n].Length > 0) && (Char.IsWhiteSpace(rawLines[n][0]))) + { + rawLines[n - 1] = rawLines[n - 1] + rawLines[n]; + rawLines[n] = null; + } + } + + foreach (String rawLine in rawLines) + { + if (rawLine != null) + { + int colon = rawLine.IndexOf(':'); + if (colon > 0) + { + String name = rawLine.Substring(0, colon).Trim().ToUpper(); + String value = rawLine.Substring(colon + 1).Trim(); + headers.Add(name, value); + } + } + } + + return headers; + } + + public byte[] ReadRequestBody(byte[] buffer) + { + return null; + } + */ + } + +} diff --git a/HttpRequest.cs b/HttpRequest.cs new file mode 100644 index 0000000..6d731f2 --- /dev/null +++ b/HttpRequest.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using ln.http.exceptions; + +namespace ln.http +{ + public class HttpRequest + { + Dictionary requestHeaders; + + public IPEndPoint RemoteEndpoint { get; private set; } + public IPEndPoint LocalEndpoint { get; private set; } + + public Uri BaseURI { get; set; } + public Uri URI { get; private set; } + + public String Method { get; private set; } + public String RequestURL { get; private set; } + public String Protocol { get; private set; } + + public String Hostname { get; private set; } + public int Port { get; private set; } + + public QueryStringParameters Query { get; private set; } + + public HttpRequest(HttpReader httpReader,IPEndPoint localEndpoint) + { + LocalEndpoint = localEndpoint; + RemoteEndpoint = httpReader.RemoteEndpoint; + Method = httpReader.Method; + Protocol = httpReader.Protocol; + RequestURL = httpReader.URL; + + requestHeaders = new Dictionary(httpReader.Headers); + + Setup(); + } + + private void Setup() + { + SetupResourceURI(); + } + + /* + * SetupResourceURI() + * + * Setup the following fields: + * + * - Hostname + * - Port + * - BaseURI + * - URI + * - Query + * + */ + private void SetupResourceURI() + { + String host = GetRequestHeader("HOST"); + String[] hostTokens = host.Split(':'); + + Hostname = hostTokens[0]; + Port = (hostTokens.Length > 1) ? int.Parse(hostTokens[1]) : LocalEndpoint.Port; + BaseURI = new UriBuilder("http", Hostname, Port).Uri; + URI = new Uri(BaseURI, RequestURL); + Query = new QueryStringParameters(URI.Query); + } + + + public override string ToString() + { + //return string.Format("[HttpRequest: RemoteEndpoint={0}, Hostname={1} Port={2} URI={4}, Method={4}, RequestURL={5}, Protocol={6} Query={7}]", RemoteEndpoint, URI, Method, RequestURL, Protocol, Hostname, Port,Query); + return base.ToString(); + } + + public String GetRequestHeader(String name, String def = "") + { + name = name.ToUpper(); + + if (requestHeaders.ContainsKey(name)) + return requestHeaders[name]; + + return def; + } + + public HttpResponse Redirect(string location,int status = 301) + { + HttpResponse httpResponse = new HttpResponse(this); + httpResponse.AddHeader("location", location); + httpResponse.StatusCode = status; + httpResponse.AddHeader("content-type", "text/plain"); + httpResponse.ContentWriter.WriteLine("Redirect: {0}", location); + return httpResponse; + } + + + //private void SendResponse() + //{ + // using (StreamWriter writer = new StreamWriter(this.stream)) + // { + // ResponseStream.Position = 0; + // SetResponseHeader("Content-Length", responseStream.Length.ToString()); + + // writer.WriteLine("{0} {1} {2}", Protocol, StatusCode, HttpStatusCodes.GetStatusMessage(StatusCode)); + // foreach (String rhName in responseHeaders.Keys){ + // writer.WriteLine("{0}: {1}", rhName, responseHeaders[rhName]); + // } + // writer.WriteLine(); + // writer.Flush(); + + // responseStream.CopyTo(this.stream); + // } + //} + + + } +} diff --git a/HttpResponse.cs b/HttpResponse.cs new file mode 100644 index 0000000..b4a3c42 --- /dev/null +++ b/HttpResponse.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; + +namespace ln.http +{ + public class HttpResponse + { + public HttpRequest HttpRequest { get; } + + public Stream ContentStream { get; private set; } + public TextWriter ContentWriter { get; private set; } + public bool HasCustomContentStream { get; private set; } + + int statusCode; + string statusMessage; + Dictionary> headers = new Dictionary>(); + + public HttpResponse(HttpRequest httpRequest) + { + HttpRequest = httpRequest; + ContentStream = new MemoryStream(); + ContentWriter = new StreamWriter(ContentStream); + + StatusCode = 200; + SetHeader("content-type", "text/html"); + } + public HttpResponse(HttpRequest httpRequest,string contentType) + :this(httpRequest) + { + SetHeader("content-type", contentType); + } + + public HttpResponse(HttpRequest httpRequest, Stream contentStream) + { + HttpRequest = httpRequest; + ContentStream = contentStream; + ContentWriter = null; + HasCustomContentStream = true; + + StatusCode = 200; + SetHeader("content-type", "text/html"); + } + public HttpResponse(HttpRequest httpRequest, Stream contentStream,string contentType) + :this(httpRequest,contentStream) + { + SetHeader("content-type", contentType); + } + + + public String GetHeader(string name) + { + return String.Join(",", headers[name.ToUpper()]); + } + public String[] GetHeaderValues(string name) + { + return headers[name.ToUpper()].ToArray(); + } + public String[] GetHeaderNames() + { + return headers.Keys.ToArray(); + } + + public void SetHeader(String name, String value) + { + name = name.ToUpper(); + + headers[name] = new List(); + headers[name].Add(value); + } + public void AddHeader(String name, String value) + { + name = name.ToUpper(); + + if (!headers.ContainsKey(name)) + headers[name] = new List(); + + headers[name].Add(value); + } + public void RemoveHeader(String name) + { + headers.Remove(name.ToUpper()); + } + + + public int StatusCode + { + get + { + return statusCode; + } + set { + statusCode = value; + statusMessage = HttpStatusCodes.GetStatusMessage(statusCode); + } + } + public String StatusMessage + { + get { + return statusMessage; + } + set { + statusMessage = value; + } + } + + + + + + } +} diff --git a/HttpStatusCodes.cs b/HttpStatusCodes.cs new file mode 100644 index 0000000..7b294b2 --- /dev/null +++ b/HttpStatusCodes.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Remoting.Messaging; +namespace ln.http +{ + public class HttpStatusCodes + { + static Dictionary statusMessages = new Dictionary() + { + { 200, "Ok" }, + { 403, "Access denied" }, + { 404, "Not Found" }, + { 500, "Internal Error" } + }; + + public static String GetStatusMessage(int code){ + if (statusMessages.ContainsKey(code)) + return statusMessages[code]; + return ""; + } + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8da6c0b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("ln.http")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("${AuthorCopyright}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/QueryStringParameters.cs b/QueryStringParameters.cs new file mode 100644 index 0000000..18c3631 --- /dev/null +++ b/QueryStringParameters.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +namespace ln.http +{ + public class QueryStringParameters : IDictionary + { + Dictionary parameters = new Dictionary(); + + public QueryStringParameters(String query) + { + if (query.StartsWith("?")) + query = query.Substring(1); + + String[] pairs = query.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (String pair in pairs) + { + String[] kv = pair.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); + string key = Uri.UnescapeDataString(kv[0].Replace('+',' ')); + string value = kv.Length == 2 ? Uri.UnescapeDataString(kv[1].Replace('+',' ')) : ""; + if (!key.Equals(String.Empty)){ + parameters[key] = value; + } + } + } + + public string this[string key] { + get => parameters[key]; + set => throw new NotImplementedException(); } + + public ICollection Keys => parameters.Keys; + public ICollection Values => parameters.Values; + public int Count => parameters.Count; + public bool IsReadOnly => true; + + public void Add(string key, string value) => throw new NotImplementedException(); + public void Add(KeyValuePair item) => throw new NotImplementedException(); + public void Clear() => throw new NotImplementedException(); + public bool Remove(string key) => throw new NotImplementedException(); + public bool Remove(KeyValuePair item) => throw new NotImplementedException(); + + public bool Contains(KeyValuePair item) => parameters.Contains(item); + public bool ContainsKey(string key) => parameters.ContainsKey(key); + public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary)parameters).CopyTo(array, arrayIndex); + public IEnumerator> GetEnumerator() => parameters.GetEnumerator(); + public bool TryGetValue(string key, out string value) => parameters.TryGetValue(key, out value); + IEnumerator IEnumerable.GetEnumerator() => parameters.GetEnumerator(); + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.Append("[Query"); + foreach (String key in parameters.Keys){ + stringBuilder.AppendFormat(" {0}={1}", key, parameters[key]); + } + stringBuilder.Append("]"); + + return stringBuilder.ToString(); + } + } +} diff --git a/exceptions/HttpException.cs b/exceptions/HttpException.cs new file mode 100644 index 0000000..b57d44c --- /dev/null +++ b/exceptions/HttpException.cs @@ -0,0 +1,29 @@ +using System; + +namespace ln.http.exceptions +{ + public class HttpException : Exception + { + public int StatusCode { get; } = 500; + + public HttpException(String message) + : base(message) + { + } + public HttpException(String message, Exception innerException) + : base(message, innerException) + { + } + public HttpException(int statusCode,String message) + : base(message) + { + StatusCode = statusCode; + } + public HttpException(int statusCode,String message, Exception innerException) + : base(message, innerException) + { + StatusCode = statusCode; + } + + } +} diff --git a/exceptions/IllegalRequestException.cs b/exceptions/IllegalRequestException.cs new file mode 100644 index 0000000..4ee86dc --- /dev/null +++ b/exceptions/IllegalRequestException.cs @@ -0,0 +1,11 @@ +using System; +namespace ln.http.exceptions +{ + public class IllegalRequestException : Exception + { + public IllegalRequestException(String requestLine) + :base(requestLine) + { + } + } +} diff --git a/exceptions/ResourceNotFoundException.cs b/exceptions/ResourceNotFoundException.cs new file mode 100644 index 0000000..81869f0 --- /dev/null +++ b/exceptions/ResourceNotFoundException.cs @@ -0,0 +1,11 @@ +using System; +namespace ln.http.exceptions +{ + public class ResourceNotFoundException : HttpException + { + public ResourceNotFoundException(String resourcePath, String nextResource) + : base(404, String.Format("Could not find resource \"{0}\" within \"{1}\"", nextResource, resourcePath)) + { + } + } +} diff --git a/ln.http.csproj b/ln.http.csproj new file mode 100644 index 0000000..93067a3 --- /dev/null +++ b/ln.http.csproj @@ -0,0 +1,51 @@ + + + + Debug + AnyCPU + {CEEEEB41-3059-46A2-A871-2ADE22C013D9} + Library + ln.http + ln.http + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/threads/Pool.cs b/threads/Pool.cs new file mode 100644 index 0000000..9c30c28 --- /dev/null +++ b/threads/Pool.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading; +using System.Collections.Generic; +namespace ln.http.threads +{ + public delegate void JobDelegate(); + + public class Pool + { + public int Timeout { get; set; } = 15000; + + ISet threads = new HashSet(); + Queue queuedJobs = new Queue(); + HashSet idleThreads = new HashSet(); + + public Pool() + { + } + + private void AllocateThread() + { + Thread thread = new Thread(pool_thread); + thread.Start(); + } + + public void Enqueue(JobDelegate job) + { + lock (this) + { + queuedJobs.Enqueue(job); + + if (idleThreads.Count == 0) + { + AllocateThread(); + } + else + { + Monitor.Pulse(this); + } + } + } + + + private void pool_thread() + { + Thread me = Thread.CurrentThread; + + try + { + JobDelegate job = null; + do + { + lock (this) + { + if (queuedJobs.Count == 0) + { + idleThreads.Add(me); + bool s = Monitor.Wait(this, Timeout); + idleThreads.Remove(me); + if (!s) + break; + } + job = queuedJobs.Dequeue(); + } + + job(); + + } while (true); + + } + catch (Exception e) + { + Console.WriteLine("Exception in worker thread: {0}", e); + } + + lock (this) + { + threads.Remove(me); + } + } + } +}