Skip to content

Commit 68e20a1

Browse files
Merge pull request #221 from aura-systems/impl-httpserver
Basic HTTP Server Implementation.
2 parents 99b1304 + bd2afdf commit 68e20a1

File tree

11 files changed

+1345
-0
lines changed

11 files changed

+1345
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* PROJECT: Aura Operating System Development
3+
* CONTENT: Http default responses
4+
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
5+
* David Jeske
6+
* Barend Erasmus
7+
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
8+
*/
9+
10+
using SimpleHttpServer.Models;
11+
12+
namespace SimpleHttpServer
13+
{
14+
class HttpBuilder
15+
{
16+
public static HttpResponse InternalServerError()
17+
{
18+
string content = "<h1>500 Internal Server Error</h1><a href=\"http://141.94.79.247\">Back to home page</a>";
19+
20+
return new HttpResponse()
21+
{
22+
ReasonPhrase = "InternalServerError",
23+
StatusCode = "500",
24+
ContentAsUTF8 = content
25+
};
26+
}
27+
28+
public static HttpResponse NotFound()
29+
{
30+
string content = "<h1>404 Not Found</h1><a href=\"http://141.94.79.247\">Back to home page</a>";
31+
32+
return new HttpResponse()
33+
{
34+
ReasonPhrase = "NotFound",
35+
StatusCode = "404",
36+
ContentAsUTF8 = content
37+
};
38+
}
39+
}
40+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* PROJECT: Aura Operating System Development
3+
* CONTENT: HttpProcessor class
4+
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
5+
* David Jeske
6+
* Barend Erasmus
7+
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
8+
*/
9+
10+
using Cosmos.System.Network.IPv4;
11+
using Cosmos.System.Network.IPv4.TCP;
12+
using SimpleHttpServer.Models;
13+
using System;
14+
using System.Collections.Generic;
15+
using System.Linq;
16+
using System.Text;
17+
18+
namespace SimpleHttpServer
19+
{
20+
//used because Event with return type is not supported by Cosmos.
21+
public class HttpDiscussion
22+
{
23+
public HttpRequest Request;
24+
public HttpResponse Response;
25+
}
26+
27+
public class HttpProcessor
28+
{
29+
private List<Route> Routes = new List<Route>();
30+
31+
public HttpProcessor()
32+
{
33+
34+
}
35+
36+
public void HandleClient(TcpClient tcpClient)
37+
{
38+
//get request from client
39+
var request = GetRequest(tcpClient);
40+
41+
// route and handle the request...
42+
var response = RouteRequest(tcpClient, request);
43+
44+
Console.WriteLine("{0} {1}", response.StatusCode, request.Url);
45+
46+
// build a default response for errors
47+
if (response.Content == null)
48+
{
49+
if (response.StatusCode != "200")
50+
{
51+
response.ContentAsUTF8 = string.Format("{0} {1} <p> {2}", response.StatusCode, request.Url, response.ReasonPhrase);
52+
}
53+
}
54+
55+
WriteResponse(tcpClient, response);
56+
57+
tcpClient.Close();
58+
}
59+
60+
private HttpRequest GetRequest(TcpClient client)
61+
{
62+
var ep = new EndPoint(Address.Zero, 0);
63+
64+
//Read Request Line
65+
string request = Encoding.ASCII.GetString(client.Receive(ref ep));
66+
67+
var lines = request.Split("\r\n");
68+
69+
string[] tokens = lines[0].Split(' ');
70+
71+
if (tokens.Length != 3)
72+
{
73+
throw new Exception("invalid http request line");
74+
}
75+
76+
string method = tokens[0].ToUpper();
77+
string url = tokens[1];
78+
string protocolVersion = tokens[2];
79+
80+
//Read Headers
81+
Dictionary<string, string> headers = new Dictionary<string, string>();
82+
83+
for (int i = 1; i < lines.Length; i++)
84+
{
85+
if (lines[i].Equals(""))
86+
{
87+
break;
88+
}
89+
90+
int separator = lines[i].IndexOf(':');
91+
if (separator == -1)
92+
{
93+
throw new Exception("invalid http header line: " + lines[i]);
94+
}
95+
string name = lines[i].Substring(0, separator);
96+
int pos = separator + 1;
97+
while ((pos < lines[i].Length) && (lines[i][pos] == ' '))
98+
{
99+
pos++;
100+
}
101+
102+
string value = lines[i].Substring(pos, lines[i].Length - pos);
103+
headers.Add(name, value);
104+
}
105+
106+
/*
107+
108+
string content = null;
109+
if (headers.ContainsKey("Content-Length"))
110+
{
111+
int totalBytes = Convert.ToInt32(headers["Content-Length"]);
112+
int bytesLeft = totalBytes;
113+
byte[] bytes = new byte[totalBytes];
114+
115+
while(bytesLeft > 0)
116+
{
117+
byte[] buffer = new byte[bytesLeft > 1024? 1024 : bytesLeft];
118+
int n = inputStream.Read(buffer, 0, buffer.Length);
119+
buffer.CopyTo(bytes, totalBytes - bytesLeft);
120+
121+
bytesLeft -= n;
122+
}
123+
124+
content = Encoding.ASCII.GetString(bytes);
125+
}*/
126+
127+
return new HttpRequest()
128+
{
129+
Method = method,
130+
Url = url,
131+
Headers = headers,
132+
Content = "content" //TODO: add content
133+
};
134+
}
135+
136+
protected virtual HttpResponse RouteRequest(TcpClient client, HttpRequest request)
137+
{
138+
if (!Routes.Any())
139+
{
140+
return HttpBuilder.NotFound();
141+
}
142+
143+
//Search Route
144+
var route = GetRoute(request);
145+
146+
if (route == null)
147+
{
148+
return HttpBuilder.NotFound();
149+
}
150+
151+
// trigger the route handler...
152+
try
153+
{
154+
var discussion = new HttpDiscussion() { Request = request, Response = null };
155+
156+
route.Callable(discussion);
157+
158+
return discussion.Response;
159+
}
160+
catch (Exception ex)
161+
{
162+
Console.WriteLine(ex);
163+
164+
return HttpBuilder.InternalServerError();
165+
}
166+
}
167+
168+
private Route GetRoute(HttpRequest request)
169+
{
170+
foreach (var route in Routes)
171+
{
172+
if (route.Url == request.Url)
173+
{
174+
return route;
175+
}
176+
}
177+
178+
//no hardcoded route, get filesystem root
179+
180+
if (request.Url == "/")
181+
{
182+
request.Url = "/index.html";
183+
}
184+
185+
foreach (var route in Routes)
186+
{
187+
if (route.Url == "")
188+
{
189+
return route;
190+
}
191+
}
192+
193+
return null;
194+
}
195+
196+
// this formats the HTTP response...
197+
private static void WriteResponse(TcpClient client, HttpResponse response)
198+
{
199+
if (response.Content == null)
200+
{
201+
response.Content = new byte[] { };
202+
}
203+
204+
// default to text/html content type
205+
if (!response.Headers.ContainsKey("Content-Type"))
206+
{
207+
response.Headers["Content-Type"] = "text/html";
208+
}
209+
210+
response.Headers["Content-Length"] = response.Content.Length.ToString();
211+
212+
//make response
213+
var sb = new StringBuilder();
214+
sb.Append(string.Format("HTTP/1.0 {0} {1}\r\n", response.StatusCode, response.ReasonPhrase));
215+
foreach (var header in response.Headers)
216+
{
217+
sb.Append(header.Key + ": " + header.Value + "\r\n");
218+
}
219+
sb.Append("\r\n");
220+
sb.Append(Encoding.ASCII.GetString(response.Content));
221+
222+
client.Send(Encoding.ASCII.GetBytes(sb.ToString()));
223+
}
224+
225+
public void AddRoute(Route route)
226+
{
227+
Routes.Add(route);
228+
}
229+
230+
}
231+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* PROJECT: Aura Operating System Development
3+
* CONTENT: HttpServer class
4+
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
5+
* David Jeske
6+
* Barend Erasmus
7+
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
8+
*/
9+
10+
using Cosmos.System.Network.IPv4.TCP;
11+
using SimpleHttpServer.Models;
12+
using System;
13+
using System.Collections.Generic;
14+
15+
namespace SimpleHttpServer
16+
{
17+
public class HttpServer
18+
{
19+
#region Fields
20+
21+
private int Port;
22+
private TcpListener Listener;
23+
private HttpProcessor Processor;
24+
private bool IsActive = true;
25+
26+
#endregion
27+
28+
#region Public Methods
29+
30+
public HttpServer(int port, List<Route> routes)
31+
{
32+
Port = port;
33+
Processor = new HttpProcessor();
34+
35+
foreach (var route in routes)
36+
{
37+
Processor.AddRoute(route);
38+
}
39+
}
40+
41+
public void Listen()
42+
{
43+
Listener = new TcpListener((ushort)Port);
44+
Listener.Start();
45+
46+
Console.WriteLine("HTTP Server Listening on port 80...");
47+
48+
while (IsActive)
49+
{
50+
try
51+
{
52+
var s = Listener.AcceptTcpClient();
53+
Processor.HandleClient(s);
54+
}
55+
catch
56+
{
57+
58+
}
59+
}
60+
}
61+
62+
#endregion
63+
64+
}
65+
}
66+
67+
68+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* PROJECT: Aura Operating System Development
3+
* CONTENT: HttpRequest class
4+
* PROGRAMMERS: Valentin Charbonnier <[email protected]>
5+
* David Jeske
6+
* Barend Erasmus
7+
* LICENSE: LICENSES\SimpleHttpServer\LICENSE.md
8+
*/
9+
10+
using System;
11+
using System.Collections.Generic;
12+
using System.Text;
13+
14+
namespace SimpleHttpServer.Models
15+
{
16+
public class HttpRequest
17+
{
18+
public string Method { get; set; }
19+
public string Url { get; set; }
20+
public string Content { get; set; }
21+
public Dictionary<string, string> Headers { get; set; }
22+
23+
public HttpRequest()
24+
{
25+
this.Headers = new Dictionary<string, string>();
26+
}
27+
28+
public override string ToString()
29+
{
30+
if (!string.IsNullOrWhiteSpace(this.Content))
31+
{
32+
if (!Headers.ContainsKey("Content-Length"))
33+
{
34+
Headers.Add("Content-Length", Content.Length.ToString());
35+
}
36+
37+
}
38+
39+
//make string from fields
40+
var sb = new StringBuilder();
41+
sb.Append(Method + " " + Url + " HTTP/1.0\r\n");
42+
foreach (var header in Headers)
43+
{
44+
sb.Append(header.Key + ": " + header.Value + "\r\n");
45+
}
46+
sb.Append("\r\n");
47+
sb.Append(Content);
48+
49+
return sb.ToString();
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)