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