Skip to content

Instantly share code, notes, and snippets.

@Quacky2200
Last active October 18, 2018 20:44
Show Gist options
  • Save Quacky2200/ed7eed524488b4247f9421c7f8ed55b5 to your computer and use it in GitHub Desktop.
Save Quacky2200/ed7eed524488b4247f9421c7f8ed55b5 to your computer and use it in GitHub Desktop.
Do you ever need a tiny C# web server in your back pocket?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Diagnostics;
using System.Net.Sockets;
// Created a temporary web server as part of a payment SDK example
// This is due to HttpListener needing admin privileges or otherwise
// with the need of an ASP.NET project
public class WebServer
{
private TcpListener server;
private Boolean Quit = false;
private Thread listener;
private const int BUFFER_SIZE = 1024;
List<Thread> Threads = new List<Thread>();
public delegate void OnRequestHandler(WebServer sender, Request Request);
public event OnRequestHandler OnRequest;
public class Request
{
public Dictionary<String, String> Headers = new Dictionary<string, string>();
public string Method;
public string Path;
public string QueryString;
public string HttpVersion;
public string Body;
public Socket Client;
}
public class Response
{
public int Status = 200;
private string StatusName = "";
public string ContentType = "text/plain";
public byte[] Content;
public Dictionary<string, dynamic> Headers = new Dictionary<string, dynamic>();
public byte[] GetBytes()
{
ResolveStatus();
string HttpMessage = String.Format("{0} {1} {2}", "HTTP/1.1", Status, StatusName) + "\r\n";
List<string> HeaderStrings = new List<string>();
List<string> HeaderKeys = Headers.Keys.ToList();
for (int i = 0; i < HeaderKeys.Count; i++)
{
dynamic Obj = Headers[HeaderKeys[i]];
Headers.Remove(HeaderKeys[i]);
Headers.Add(HeaderKeys[i].ToLower(), Obj);
}
if (!Headers.ContainsKey("Content-Type"))
{
Headers.Add("Content-Type", ContentType);
}
Headers.Add("Content-Length", Content.Length);
Headers.Add("Server", "dotnet");
Headers.Add("Content-Disposition", "inline");
Headers.Add("Connection", "close");
foreach (KeyValuePair<string, dynamic> KVP in Headers)
{
HeaderStrings.Add(KVP.Key + ": " + KVP.Value + "\r\n");
}
HttpMessage += String.Join("", HeaderStrings) + "\r\n";
List<byte> HttpBytes = Encoding.ASCII.GetBytes(HttpMessage).ToList();
HttpBytes.AddRange(Content);
return HttpBytes.ToArray();
}
protected void ResolveStatus()
{
Dictionary<int, string> codes = new Dictionary<int, string>
{
[200] = "OK",
[500] = "Internal Server Error",
[401] = "Bad Request",
[403] = "Forbidden"
};
if (!codes.ContainsKey(Status))
{
Status = 500;
StatusName = codes[500];
Content = Encoding.ASCII.GetBytes(StatusName);
// More should be added, add codes above when needed ^
Debug.WriteLine("Invalid server HTTP code");
} else
{
StatusName = codes[Status];
}
}
public void SetText(String Text)
{
Content = Encoding.ASCII.GetBytes(Text);
}
public void SetHtml(String Html)
{
Content = Encoding.ASCII.GetBytes(Html);
ContentType = "text/html";
}
public static Response SendText(String Text)
{
Response Result = new Response();
Result.SetText(Text);
return Result;
}
public static Response SendHtml(String Text)
{
Response Result = new Response();
Result.SetHtml(Text);
return Result;
}
}
public WebServer(int port)
{
server = new System.Net.Sockets.TcpListener(IPAddress.Loopback, port);
}
protected void MoveToNewProcessingThread(Socket Client)
{
Thread Current = new Thread(() =>
{
string Content;
List<byte> Bytes = new List<byte>();
byte[] Data;
int Size = BUFFER_SIZE;
while (Size >= BUFFER_SIZE)
{
Data = new byte[BUFFER_SIZE];
Size = Client.Receive(Data);
Bytes.AddRange(Data);
}
Content = Encoding.ASCII.GetString(Bytes.ToArray()).Replace("\0", "");
if (Content == "")
{
// Unsure exactly what to do in this use case.
Client.Close();
return;
}
List<string> HttpMessage = Content.Split(Char.Parse("\n")).ToList();
Request Request = new Request();
string[] Action = HttpMessage[0].TrimEnd(Char.Parse("\r")).Split(Char.Parse(" "));
if (Action.Length > 3)
{
throw new InvalidOperationException("Too many fields in HTTP1/1 spec?");
}
int Count = HttpMessage.Count;
for (int i = 1; i < Count; i++)
{
if (HttpMessage[i] == "\r")
{
HttpMessage.RemoveRange(0, i + 1);
break;
}
string[] Header = HttpMessage[i].TrimEnd(Char.Parse("\r")).Split(Char.Parse(":"));
string key = Header[0];
Header[0] = "";
Request.Headers.Add(key, String.Join(":", Header).TrimStart(Char.Parse(":")).TrimStart(Char.Parse(" ")));
}
Request.Method = Action[0].ToUpper();
string[] Path = Action[1].Split(Char.Parse("?"));
Request.Path = Path[0];
Path[0] = "";
Request.QueryString = String.Join("?", Path).TrimStart(Char.Parse("?"));
Request.HttpVersion = Action[2];
Request.Body = String.Join("\n", HttpMessage.ToArray());
HttpMessage = null;
Content = null;
Request.Client = Client;
if (OnRequest != null)
{
OnRequest(this, Request);
} else
{
Send(Client, new Response() { Status = 500, Content = Encoding.ASCII.GetBytes("Internal Server Error") });
}
});
Current.Start();
Threads.Add(Current);
}
public void Send(Socket Client, Response Response)
{
List<byte> Content = Response.GetBytes().ToList();
int OriginalSize = Content.Count;
int Size = BUFFER_SIZE;
int Count = 0;
while(Content.Count > 0)
{
// Try to send as much as our buffer size allows
byte[] Chunk = Content.GetRange(0, Math.Min(BUFFER_SIZE, Content.Count)).ToArray();
// Get how much the client has received
Size = Client.Send(Chunk);
// Only remove what the client was able to accept
// (so that anything left over can be tried again)
Content.RemoveRange(0, Size);
Count++;
if (Count > (OriginalSize * 4))
{
// We've been here to long. Stop!
Client.Close();
Debug.WriteLine("Client Timeout?");
return;
}
}
Client.Close();
}
public void Start()
{
// Main listener thread
listener = new Thread(() =>
{
server.Start();
while (!Quit)
{
Socket Client = null;
try
{
Client = server.AcceptSocket();
} catch (/*Socket*/Exception e)
{
// Ignore when quiting since the socket
// will have to be interrupted
if (!Quit)
{
Debug.WriteLine(e.Message);
return;
}
}
if (Client != null)
{
MoveToNewProcessingThread(Client);
}
}
});
listener.Start();
}
public void Stop()
{
Quit = true;
foreach(Thread Thread in Threads)
{
Thread.Abort();
}
server.Stop();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment