Initial Commit
commit
536852c4cd
|
@ -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
|
|
@ -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<IPEndPoint, TcpListener> tcpListeners = new Dictionary<IPEndPoint, TcpListener>();
|
||||
Dictionary<Uri, HttpApplication> applications = new Dictionary<Uri, HttpApplication>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using ln.http;
|
||||
namespace ln.http
|
||||
{
|
||||
public abstract class HttpApplication
|
||||
{
|
||||
public HttpApplication()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract HttpResponse GetResponse(HttpRequest httpRequest);
|
||||
}
|
||||
}
|
|
@ -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<string, string> Headers { get; } = new Dictionary<string, string>();
|
||||
|
||||
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<string,string> ReadHTTPHeaders()
|
||||
{
|
||||
byte[] b = new byte[8192];
|
||||
int hlen = ReadTo(b, 0, new byte[] { 0x0d, 0x0a, 0x0d, 0x0a });
|
||||
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
|
||||
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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> 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<string, string>(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);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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<string, List<String>> headers = new Dictionary<string, List<string>>();
|
||||
|
||||
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<string>();
|
||||
headers[name].Add(value);
|
||||
}
|
||||
public void AddHeader(String name, String value)
|
||||
{
|
||||
name = name.ToUpper();
|
||||
|
||||
if (!headers.ContainsKey(name))
|
||||
headers[name] = new List<string>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
namespace ln.http
|
||||
{
|
||||
public class HttpStatusCodes
|
||||
{
|
||||
static Dictionary<int, string> statusMessages = new Dictionary<int, string>()
|
||||
{
|
||||
{ 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 "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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("")]
|
|
@ -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<String,String>
|
||||
{
|
||||
Dictionary<string, string> parameters = new Dictionary<string, string>();
|
||||
|
||||
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<string> Keys => parameters.Keys;
|
||||
public ICollection<string> 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<string, string> item) => throw new NotImplementedException();
|
||||
public void Clear() => throw new NotImplementedException();
|
||||
public bool Remove(string key) => throw new NotImplementedException();
|
||||
public bool Remove(KeyValuePair<string, string> item) => throw new NotImplementedException();
|
||||
|
||||
public bool Contains(KeyValuePair<string, string> item) => parameters.Contains(item);
|
||||
public bool ContainsKey(string key) => parameters.ContainsKey(key);
|
||||
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) => ((IDictionary<string, string>)parameters).CopyTo(array, arrayIndex);
|
||||
public IEnumerator<KeyValuePair<string, string>> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace ln.http.exceptions
|
||||
{
|
||||
public class IllegalRequestException : Exception
|
||||
{
|
||||
public IllegalRequestException(String requestLine)
|
||||
:base(requestLine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{CEEEEB41-3059-46A2-A871-2ADE22C013D9}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>ln.http</RootNamespace>
|
||||
<AssemblyName>ln.http</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="exceptions\HttpException.cs" />
|
||||
<Compile Include="exceptions\IllegalRequestException.cs" />
|
||||
<Compile Include="exceptions\ResourceNotFoundException.cs" />
|
||||
<Compile Include="threads\Pool.cs" />
|
||||
<Compile Include="HTTPServer.cs" />
|
||||
<Compile Include="HttpReader.cs" />
|
||||
<Compile Include="HttpRequest.cs" />
|
||||
<Compile Include="HttpResponse.cs" />
|
||||
<Compile Include="HttpStatusCodes.cs" />
|
||||
<Compile Include="QueryStringParameters.cs" />
|
||||
<Compile Include="HttpApplication.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="exceptions\" />
|
||||
<Folder Include="threads\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -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<Thread> threads = new HashSet<Thread>();
|
||||
Queue<JobDelegate> queuedJobs = new Queue<JobDelegate>();
|
||||
HashSet<Thread> idleThreads = new HashSet<Thread>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue