-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.cs
161 lines (140 loc) · 7.05 KB
/
server.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Globalization;
namespace Webserver
{
class Program
{
private const int port = 5050;
private const string WebServerPath = @"www";
private static readonly string serverEtag = Guid.NewGuid().ToString("N");
/// <summary>
/// starts a tcp listener & a thread to listen for incoming requests
/// </summary>
static void Main(string[] args)
{
try
{
var myListener = new TcpListener(IPAddress.Loopback, port);
myListener.Start();
Console.WriteLine($"Server running on http://{IPAddress.Loopback}:{port}/ \nQuit the server with CTRL-BREAK");
var th = new Thread(new ThreadStart(StartListen));
th.Start();
/// <summary>
/// a loop that waits for a client to connect, then reads the request from the client
/// </summary>
void StartListen()
{
while (true)
{
using var client = myListener.AcceptTcpClient();
using var stream = client.GetStream();
// read request
var requestBytes = new byte[1024];
var bytesRead = stream.Read(requestBytes, 0, requestBytes.Length);
var request = Encoding.UTF8.GetString(requestBytes, 0, bytesRead);
// parse the headers from the request
var (headers, requestType) = ParseHeaders(request);
var requestFirstLine = requestType.Split(" ");
var httpVersion = requestFirstLine.LastOrDefault();
var contentType = headers.GetValueOrDefault("Accept");
var contentEncoding = headers.GetValueOrDefault("Accept-Encoding");
// request type check
if (!requestType.StartsWith("GET"))
{
WriteResponse(stream, httpVersion, 405, "Method Not Allowed", contentType, contentEncoding, null, null);
}
else
{
var requestedPath = requestFirstLine[1];
var fileContent = GetContent(requestedPath);
if (fileContent != null)
{
var requestLines = request.Split('\n');
var firstLine = requestLines[0].TrimEnd('\r', '\n');
WriteResponse(stream, httpVersion, 200, "OK", contentType, contentEncoding, firstLine, fileContent);
}
else
{
var requestLines = request.Split('\n');
var firstLine = requestLines[0].TrimEnd('\r', '\n');
WriteResponse(stream, httpVersion, 404, "Page Not Found", contentType, contentEncoding, firstLine, null);
}
}
}
}
}
catch (Exception error)
{
Console.Error.WriteLine($"Err: {error.Message}");
}
}
/// <summary>
/// returns the content of the requested file if it exists
/// </summary>
private static byte[]? GetContent(string requestedPath)
{
if (requestedPath == "/") requestedPath = "index.html";
var filePath = Path.Combine(WebServerPath, requestedPath.TrimStart('/'));
if (!File.Exists(filePath)) return null;
return File.ReadAllBytes(filePath);
}
/// <summary>
/// writing the response to the client
/// </summary>
private static void WriteResponse(NetworkStream networkStream, string? httpVersion, int statusCode, string statusMsg, string? contentType, string? contentEncoding, string? firstLine, byte[]? content)
{
var currentDateTime = DateTime.Now;
var formattedDateTime = currentDateTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
if (!string.IsNullOrEmpty(firstLine))
{
Console.WriteLine($"[{formattedDateTime}] '{firstLine}' {statusCode} {statusMsg}");
}
var contentLength = content?.Length;
WriteResponseHeaders(networkStream, httpVersion, statusCode, statusMsg, contentType, contentEncoding, contentLength);
if (content != null)
{
networkStream.Write(content, 0, content.Length);
}
}
/// <summary>
/// writing the response headers to the network stream
/// </summary>
private static void WriteResponseHeaders(NetworkStream networkStream, string? httpVersion, int statusCode, string statusMsg, string? contentType, string? contentEncoding, int? contentLength)
{
var responseHeaderBuffer = $"HTTP/1.1 {statusCode} {statusMsg}\r\n" +
$"Connection: Keep-Alive\r\n" +
$"Date: {DateTime.UtcNow.ToString()}\r\n" +
$"Server: Win10 PC \r\n" +
$"Etag: \"{serverEtag}\"\r\n" +
$"Content-Encoding: {contentEncoding}\r\n" +
$"Content-Length: {contentLength}\r\n" +
$"Content-Type: {contentType}\r\n\r\n";
var responseBytes = Encoding.UTF8.GetBytes(responseHeaderBuffer);
networkStream.Write(responseBytes, 0, responseBytes.Length);
}
/// <summary>
/// parsing the headers from the request string
/// </summary>
private static (Dictionary<string, string> headers, string requestType) ParseHeaders(string headerString)
{
var headerLines = headerString.Split('\r', '\n');
var firstLine = headerLines[0];
var headerValues = new Dictionary<string, string>();
for (int i = 1; i < headerLines.Length; i++)
{
var headerLine = headerLines[i];
if (string.IsNullOrWhiteSpace(headerLine)) break;
var delimiterIndex = headerLine.IndexOf(':');
if (delimiterIndex >= 0)
{
var headerName = headerLine.Substring(0, delimiterIndex).Trim();
var headerValue = headerLine.Substring(delimiterIndex + 1).Trim();
headerValues.Add(headerName, headerValue);
}
}
return (headerValues, firstLine);
}
}
}