Skip to content

Commit

Permalink
Merge pull request #221 from aura-systems/impl-httpserver
Browse files Browse the repository at this point in the history
Basic HTTP Server Implementation.
  • Loading branch information
valentinbreiz authored Dec 26, 2021
2 parents 99b1304 + bd2afdf commit 68e20a1
Show file tree
Hide file tree
Showing 11 changed files with 1,345 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* PROJECT: Aura Operating System Development
* CONTENT: Http default responses
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
* David Jeske
* Barend Erasmus
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
*/

using SimpleHttpServer.Models;

namespace SimpleHttpServer
{
class HttpBuilder
{
public static HttpResponse InternalServerError()
{
string content = "<h1>500 Internal Server Error</h1><a href=\"http://141.94.79.247\">Back to home page</a>";

return new HttpResponse()
{
ReasonPhrase = "InternalServerError",
StatusCode = "500",
ContentAsUTF8 = content
};
}

public static HttpResponse NotFound()
{
string content = "<h1>404 Not Found</h1><a href=\"http://141.94.79.247\">Back to home page</a>";

return new HttpResponse()
{
ReasonPhrase = "NotFound",
StatusCode = "404",
ContentAsUTF8 = content
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* PROJECT: Aura Operating System Development
* CONTENT: HttpProcessor class
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
* David Jeske
* Barend Erasmus
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
*/

using Cosmos.System.Network.IPv4;
using Cosmos.System.Network.IPv4.TCP;
using SimpleHttpServer.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SimpleHttpServer
{
//used because Event with return type is not supported by Cosmos.
public class HttpDiscussion
{
public HttpRequest Request;
public HttpResponse Response;
}

public class HttpProcessor
{
private List<Route> Routes = new List<Route>();

public HttpProcessor()
{

}

public void HandleClient(TcpClient tcpClient)
{
//get request from client
var request = GetRequest(tcpClient);

// route and handle the request...
var response = RouteRequest(tcpClient, request);

Console.WriteLine("{0} {1}", response.StatusCode, request.Url);

// build a default response for errors
if (response.Content == null)
{
if (response.StatusCode != "200")
{
response.ContentAsUTF8 = string.Format("{0} {1} <p> {2}", response.StatusCode, request.Url, response.ReasonPhrase);
}
}

WriteResponse(tcpClient, response);

tcpClient.Close();
}

private HttpRequest GetRequest(TcpClient client)
{
var ep = new EndPoint(Address.Zero, 0);

//Read Request Line
string request = Encoding.ASCII.GetString(client.Receive(ref ep));

var lines = request.Split("\r\n");

string[] tokens = lines[0].Split(' ');

if (tokens.Length != 3)
{
throw new Exception("invalid http request line");
}

string method = tokens[0].ToUpper();
string url = tokens[1];
string protocolVersion = tokens[2];

//Read Headers
Dictionary<string, string> headers = new Dictionary<string, string>();

for (int i = 1; i < lines.Length; i++)
{
if (lines[i].Equals(""))
{
break;
}

int separator = lines[i].IndexOf(':');
if (separator == -1)
{
throw new Exception("invalid http header line: " + lines[i]);
}
string name = lines[i].Substring(0, separator);
int pos = separator + 1;
while ((pos < lines[i].Length) && (lines[i][pos] == ' '))
{
pos++;
}

string value = lines[i].Substring(pos, lines[i].Length - pos);
headers.Add(name, value);
}

/*
string content = null;
if (headers.ContainsKey("Content-Length"))
{
int totalBytes = Convert.ToInt32(headers["Content-Length"]);
int bytesLeft = totalBytes;
byte[] bytes = new byte[totalBytes];
while(bytesLeft > 0)
{
byte[] buffer = new byte[bytesLeft > 1024? 1024 : bytesLeft];
int n = inputStream.Read(buffer, 0, buffer.Length);
buffer.CopyTo(bytes, totalBytes - bytesLeft);
bytesLeft -= n;
}
content = Encoding.ASCII.GetString(bytes);
}*/

return new HttpRequest()
{
Method = method,
Url = url,
Headers = headers,
Content = "content" //TODO: add content
};
}

protected virtual HttpResponse RouteRequest(TcpClient client, HttpRequest request)
{
if (!Routes.Any())
{
return HttpBuilder.NotFound();
}

//Search Route
var route = GetRoute(request);

if (route == null)
{
return HttpBuilder.NotFound();
}

// trigger the route handler...
try
{
var discussion = new HttpDiscussion() { Request = request, Response = null };

route.Callable(discussion);

return discussion.Response;
}
catch (Exception ex)
{
Console.WriteLine(ex);

return HttpBuilder.InternalServerError();
}
}

private Route GetRoute(HttpRequest request)
{
foreach (var route in Routes)
{
if (route.Url == request.Url)
{
return route;
}
}

//no hardcoded route, get filesystem root

if (request.Url == "/")
{
request.Url = "/index.html";
}

foreach (var route in Routes)
{
if (route.Url == "")
{
return route;
}
}

return null;
}

// this formats the HTTP response...
private static void WriteResponse(TcpClient client, HttpResponse response)
{
if (response.Content == null)
{
response.Content = new byte[] { };
}

// default to text/html content type
if (!response.Headers.ContainsKey("Content-Type"))
{
response.Headers["Content-Type"] = "text/html";
}

response.Headers["Content-Length"] = response.Content.Length.ToString();

//make response
var sb = new StringBuilder();
sb.Append(string.Format("HTTP/1.0 {0} {1}\r\n", response.StatusCode, response.ReasonPhrase));
foreach (var header in response.Headers)
{
sb.Append(header.Key + ": " + header.Value + "\r\n");
}
sb.Append("\r\n");
sb.Append(Encoding.ASCII.GetString(response.Content));

client.Send(Encoding.ASCII.GetBytes(sb.ToString()));
}

public void AddRoute(Route route)
{
Routes.Add(route);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* PROJECT: Aura Operating System Development
* CONTENT: HttpServer class
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
* David Jeske
* Barend Erasmus
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
*/

using Cosmos.System.Network.IPv4.TCP;
using SimpleHttpServer.Models;
using System;
using System.Collections.Generic;

namespace SimpleHttpServer
{
public class HttpServer
{
#region Fields

private int Port;
private TcpListener Listener;
private HttpProcessor Processor;
private bool IsActive = true;

#endregion

#region Public Methods

public HttpServer(int port, List<Route> routes)
{
Port = port;
Processor = new HttpProcessor();

foreach (var route in routes)
{
Processor.AddRoute(route);
}
}

public void Listen()
{
Listener = new TcpListener((ushort)Port);
Listener.Start();

Console.WriteLine("HTTP Server Listening on port 80...");

while (IsActive)
{
try
{
var s = Listener.AcceptTcpClient();
Processor.HandleClient(s);
}
catch
{

}
}
}

#endregion

}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* PROJECT: Aura Operating System Development
* CONTENT: HttpRequest class
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
* David Jeske
* Barend Erasmus
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
*/

using System;
using System.Collections.Generic;
using System.Text;

namespace SimpleHttpServer.Models
{
public class HttpRequest
{
public string Method { get; set; }
public string Url { get; set; }
public string Content { get; set; }
public Dictionary<string, string> Headers { get; set; }

public HttpRequest()
{
this.Headers = new Dictionary<string, string>();
}

public override string ToString()
{
if (!string.IsNullOrWhiteSpace(this.Content))
{
if (!Headers.ContainsKey("Content-Length"))
{
Headers.Add("Content-Length", Content.Length.ToString());
}

}

//make string from fields
var sb = new StringBuilder();
sb.Append(Method + " " + Url + " HTTP/1.0\r\n");
foreach (var header in Headers)
{
sb.Append(header.Key + ": " + header.Value + "\r\n");
}
sb.Append("\r\n");
sb.Append(Content);

return sb.ToString();
}
}
}
Loading

0 comments on commit 68e20a1

Please sign in to comment.