Skip to content

Commit a0b6ee2

Browse files
committed
Добавьте файлы проекта.
1 parent d852346 commit a0b6ee2

6 files changed

+455
-0
lines changed

HttpRequestHeaderExtensions.cs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Net;
2+
3+
namespace SimpleMultithreadedAsuncHttpServer
4+
{
5+
internal static class HttpRequestHeaderExtensions
6+
{
7+
private static readonly string[] s_names = {
8+
"Cache-Control",
9+
"Connection",
10+
"Date",
11+
"Keep-Alive",
12+
"Pragma",
13+
"Trailer",
14+
"Transfer-Encoding",
15+
"Upgrade",
16+
"Via",
17+
"Warning",
18+
"Allow",
19+
"Content-Length",
20+
"Content-Type",
21+
"Content-Encoding",
22+
"Content-Language",
23+
"Content-Location",
24+
"Content-MD5",
25+
"Content-Range",
26+
"Expires",
27+
"Last-Modified",
28+
"Accept",
29+
"Accept-Charset",
30+
"Accept-Encoding",
31+
"Accept-Language",
32+
"Authorization",
33+
"Cookie",
34+
"Expect",
35+
"From",
36+
"Host",
37+
"If-Match",
38+
"If-Modified-Since",
39+
"If-None-Match",
40+
"If-Range",
41+
"If-Unmodified-Since",
42+
"Max-Forwards",
43+
"Proxy-Authorization",
44+
"Referer",
45+
"Range",
46+
"Te",
47+
"Translate",
48+
"User-Agent",
49+
};
50+
51+
public static string GetName(this HttpRequestHeader header)
52+
{
53+
return s_names[(int)header];
54+
}
55+
}
56+
}

HttpServer.cs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Net.Sockets;
2+
using System.Net;
3+
4+
namespace SimpleMultithreadedAsuncHttpServer
5+
{
6+
class HttpServer : IDisposable
7+
{
8+
private readonly TcpListener _listener;
9+
private readonly List<HttpServerClient> _clients;
10+
11+
public HttpServer(int port)
12+
{
13+
_listener = new TcpListener(IPAddress.Any, port);
14+
_clients = new List<HttpServerClient>();
15+
}
16+
17+
public async Task ListenAsync()
18+
{
19+
try
20+
{
21+
_listener.Start();
22+
Console.WriteLine("Сервер стартовал на " + _listener.LocalEndpoint);
23+
while (true)
24+
{
25+
try
26+
{
27+
TcpClient client = await _listener.AcceptTcpClientAsync();
28+
Console.WriteLine("Подключение: " + client.Client.RemoteEndPoint + " > " + client.Client.LocalEndPoint);
29+
lock (_clients)
30+
{
31+
_clients.Add(new HttpServerClient(client, c => { lock (_clients) { _clients.Remove(c); } c.Dispose(); }));
32+
}
33+
}
34+
catch (Exception ex) { Console.WriteLine(ex.Message); break; }
35+
}
36+
}
37+
catch (ObjectDisposedException ex)
38+
{
39+
if (ex.ObjectName.EndsWith("Socket"))
40+
Console.WriteLine("Сервер остановлен.");
41+
else
42+
throw ex;
43+
}
44+
}
45+
46+
public void Stop()
47+
{
48+
_listener.Stop();
49+
}
50+
51+
public void Dispose()
52+
{
53+
Dispose(true);
54+
GC.SuppressFinalize(this);
55+
}
56+
57+
bool disposed;
58+
protected virtual void Dispose(bool disposing)
59+
{
60+
if (disposed)
61+
throw new ObjectDisposedException(typeof(HttpServer).FullName);
62+
disposed = true;
63+
_listener.Stop();
64+
if (disposing)
65+
{
66+
Console.WriteLine("Отключаю подключенных клиентов...");
67+
lock (_clients)
68+
{
69+
foreach (HttpServerClient client in _clients)
70+
{
71+
client.Dispose();
72+
}
73+
}
74+
Console.WriteLine("Клиенты отключены.");
75+
}
76+
}
77+
78+
~HttpServer() => Dispose(false);
79+
}
80+
}

HttpServerClient.cs

+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
using System.Buffers;
2+
using System.Net.Sockets;
3+
using System.Net;
4+
using System.Text;
5+
6+
namespace SimpleMultithreadedAsuncHttpServer
7+
{
8+
class HttpServerClient : IDisposable
9+
{
10+
private readonly TcpClient _client;
11+
private readonly NetworkStream _stream;
12+
private readonly EndPoint _remoteEndPoint;
13+
private readonly Task _clientTask;
14+
private readonly Action<HttpServerClient> _disposeCallback;
15+
16+
public HttpServerClient(TcpClient client, Action<HttpServerClient> disposeCallback)
17+
{
18+
_client = client;
19+
_stream = client.GetStream();
20+
_remoteEndPoint = client.Client.RemoteEndPoint;
21+
_disposeCallback = disposeCallback;
22+
_clientTask = RunReadingLoop();
23+
}
24+
25+
const string errorTemplate = "<html><head><title>{0}</title></head><body><center><h1>{0}</h1></center><hr><center>TcpListener server</center></body></html>";
26+
27+
private async Task RunReadingLoop()
28+
{
29+
try
30+
{
31+
while (true)
32+
{
33+
(HttpRequestMessage request, HttpStatusCode status) = await ReceivePacket().ConfigureAwait(false);
34+
if (request != null)
35+
Console.WriteLine($"<< {request.Method.Method} {request.RequestUri}");
36+
else
37+
Console.WriteLine($"<< ??");
38+
//Console.WriteLine(request);
39+
using HttpResponseMessage response = new HttpResponseMessage(status);
40+
if (request != null)
41+
foreach (var c in request?.Headers.Connection)
42+
response.Headers.Connection.Add(c);
43+
else
44+
response.Headers.Connection.Add("close");
45+
if (status == HttpStatusCode.OK)
46+
{
47+
if (request.RequestUri.ToString() == "/")
48+
{
49+
Console.WriteLine(">> /");
50+
response.Content = CreateHtmlContent($"<html><head><title>Главная страница</title></head><body>Привет, {_remoteEndPoint}!</body></html>");
51+
}
52+
else
53+
{
54+
response.StatusCode = HttpStatusCode.NotFound;
55+
Console.WriteLine($">> {(int)response.StatusCode} {response.ReasonPhrase}");
56+
response.Content = CreateHtmlContent(string.Format(errorTemplate, $"{(int)response.StatusCode} {response.ReasonPhrase}"));
57+
}
58+
}
59+
else
60+
{
61+
Console.WriteLine($">> {(int)response.StatusCode} {response.ReasonPhrase}");
62+
response.Content = CreateHtmlContent(string.Format(errorTemplate, $"{(int)response.StatusCode} {response.ReasonPhrase}"));
63+
}
64+
// Console.WriteLine(response);
65+
await SendResponse(response).ConfigureAwait(false);
66+
if (response.Headers.Connection.Contains("close"))
67+
break;
68+
}
69+
Console.WriteLine("Подключение к " + _remoteEndPoint + " закрыто клиентом.");
70+
_stream.Close();
71+
}
72+
catch (HttpRequestException)
73+
{
74+
Console.WriteLine("Подключение к " + _remoteEndPoint + " разорвано клиентом.");
75+
}
76+
catch (IOException)
77+
{
78+
Console.WriteLine("Подключение к " + _remoteEndPoint + " закрыто сервером.");
79+
}
80+
catch (Exception ex)
81+
{
82+
Console.WriteLine(ex.GetType().Name + ": " + ex.Message);
83+
}
84+
if (!disposed)
85+
_disposeCallback(this);
86+
}
87+
88+
private HttpContent CreateHtmlContent(string text)
89+
{
90+
StringContent content = new StringContent(text, Encoding.UTF8, "text/html");
91+
content.Headers.ContentLength = content.Headers.ContentLength;
92+
return content;
93+
}
94+
95+
private async Task SendResponse(HttpResponseMessage response)
96+
{
97+
using (StreamWriter sw = new StreamWriter(_stream, leaveOpen: true))
98+
{
99+
sw.WriteLine($"HTTP/{response.Version} {(int)response.StatusCode} {response.ReasonPhrase}");
100+
sw.Write(response.Headers);
101+
sw.WriteLine(response.Content?.Headers.ToString() ?? "");
102+
}
103+
if (response.Content != null)
104+
await response.Content.CopyToAsync(_stream);
105+
}
106+
107+
private async Task<(HttpRequestMessage, HttpStatusCode)> ReceivePacket()
108+
{
109+
try
110+
{
111+
HttpRequestMessage request = new HttpRequestMessage();
112+
string requestHeader = await ReadLineAsync().ConfigureAwait(false);
113+
string[] headerTokens = requestHeader.Split(" ");
114+
if (headerTokens.Length != 3)
115+
return (null, HttpStatusCode.BadRequest);
116+
request.Method = new HttpMethod(headerTokens[0]);
117+
request.RequestUri = new Uri(headerTokens[1], UriKind.Relative);
118+
string[] protocolTokens = headerTokens[2].Split('/');
119+
if (protocolTokens.Length != 2 || protocolTokens[0] != "HTTP")
120+
return (null, HttpStatusCode.BadRequest);
121+
request.Version = Version.Parse(protocolTokens[1]);
122+
MemoryStream ms = new MemoryStream();
123+
HttpContent content = new StreamContent(ms);
124+
request.Content = content;
125+
while (true)
126+
{
127+
string headerLine = await ReadLineAsync().ConfigureAwait(false);
128+
if (headerLine.Length == 0)
129+
break;
130+
string[] tokens = headerLine.Split(":", 2);
131+
if (tokens.Length == 2)
132+
{
133+
foreach (HttpRequestHeader h in Enum.GetValues(typeof(HttpRequestHeader)))
134+
{
135+
if (tokens[0].ToLower() == h.GetName().ToLower())
136+
{
137+
if ((int)h >= 10 && (int)h <= 19) // if Entity Header
138+
request.Content.Headers.Add(tokens[0], tokens[1]);
139+
else
140+
request.Headers.Add(tokens[0], tokens[1]);
141+
break;
142+
}
143+
}
144+
}
145+
}
146+
long length = request.Content.Headers?.ContentLength ?? 0;
147+
148+
if (length > 0)
149+
{
150+
await CopyBytesAsync(_stream, ms, (int)length);
151+
ms.Position = 0;
152+
}
153+
return (request, HttpStatusCode.OK);
154+
}
155+
catch (HttpRequestException)
156+
{
157+
throw;
158+
}
159+
catch (IOException)
160+
{
161+
throw;
162+
}
163+
catch
164+
{
165+
return (null, HttpStatusCode.InternalServerError);
166+
}
167+
}
168+
169+
private async Task CopyBytesAsync(Stream source, Stream target, int count)
170+
{
171+
const int bufferSize = 65536;
172+
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
173+
try
174+
{
175+
while (count > 0)
176+
{
177+
int bytesReceived = await source.ReadAsync(buffer.AsMemory(0, Math.Min(count, bufferSize)));
178+
if (bytesReceived == 0)
179+
break;
180+
await target.WriteAsync(buffer.AsMemory(0, bytesReceived));
181+
count -= bytesReceived;
182+
}
183+
}
184+
finally
185+
{
186+
ArrayPool<byte>.Shared.Return(buffer);
187+
}
188+
}
189+
190+
private async Task<string> ReadLineAsync() => await Task.Run(ReadLine);
191+
192+
private string ReadLine()
193+
{
194+
LineState lineState = LineState.None;
195+
StringBuilder sb = new StringBuilder(128);
196+
while (true)
197+
{
198+
int b = _stream.ReadByte();
199+
switch (b)
200+
{
201+
case -1:
202+
throw new HttpRequestException("Подключение разорвано.");
203+
case '\r':
204+
if (lineState == LineState.None)
205+
lineState = LineState.CR;
206+
else
207+
throw new ProtocolViolationException("Неожиданный CR в заголовке запроса.");
208+
break;
209+
case '\n':
210+
if (lineState == LineState.CR)
211+
lineState = LineState.LF;
212+
else
213+
throw new ProtocolViolationException("Неожиданный LF в заголовке запроса.");
214+
break;
215+
default:
216+
lineState = LineState.None;
217+
sb.Append((char)b);
218+
break;
219+
}
220+
if (lineState == LineState.LF)
221+
break;
222+
}
223+
return sb.ToString();
224+
}
225+
226+
public void Dispose()
227+
{
228+
Dispose(true);
229+
GC.SuppressFinalize(this);
230+
}
231+
232+
bool disposed;
233+
protected virtual void Dispose(bool disposing)
234+
{
235+
if (disposed)
236+
throw new ObjectDisposedException(typeof(HttpServer).FullName);
237+
disposed = true;
238+
if (_client.Connected)
239+
{
240+
_stream.Close();
241+
_clientTask.Wait();
242+
}
243+
if (disposing)
244+
{
245+
_client.Dispose();
246+
}
247+
}
248+
249+
~HttpServerClient() => Dispose(false);
250+
}
251+
}

0 commit comments

Comments
 (0)