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